Skip to content

Coverity static analysis fixes - May 2026#6333

Merged
jantonguirao merged 2 commits into
NVIDIA:mainfrom
jantonguirao:coverity_fixes_20250505
May 5, 2026
Merged

Coverity static analysis fixes - May 2026#6333
jantonguirao merged 2 commits into
NVIDIA:mainfrom
jantonguirao:coverity_fixes_20250505

Conversation

@jantonguirao

@jantonguirao jantonguirao commented May 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

Three changes:

  • complex_pipeline_test.cc — Coverity-flagged use-after-free in RunCheckpointingTest. Split checkpoint_h into distinct checkpoint1_h / checkpoint2_h locals so there is no apparent reuse of a handle after a RAII transfer. The test keeps using the raw C API for output handles (it is intentionally a C API test).
  • pipeline_test_utils.h — initialize daliPipelineOutputs_h raw_out_h = nullptr in the PopOutputs and PopOutputsAsync test helpers. CHECK_DALI here is EXPECT_EQ (non-fatal), so on a failing call the helpers were wrapping an uninitialized handle in PipelineOutputsHandle and the destructor would call daliPipelineOutputsDestroy on garbage. With this init the destructor sees nullptr on the failure path and the original EXPECT_EQ failure stays visible. The C API itself is unchanged: per the convention shared with the CUDA runtime, output parameters are not modified on non-success returns (only DALI_NO_DATA writes nullptr, which these functions do not return), so initialization is the caller's responsibility.
  • operator_factory.h — Coverity-flagged copy-when-could-move in Registerer. Take Registerer::name by value and std::move both name and creator into OperatorRegistry::Register, eliminating two copies on each operator registration. Also std::move(name) in the Register<Backend> template overload so the name reaches the inner Register without an extra copy.

The remaining Coverity findings from the same snapshot (an unchecked-return-value in FileLabelLoaderBase and an uncaught-exception in vendored third_party/pybind11) are intentionally not addressed here.

Test plan

  • DALI CI green (build + tests) on this branch
  • No new regressions in CAPI2_SerializedPipelineTest.Checkpointing / CAPI2_PipelineBuilderTest.Checkpointing
  • Next Coverity scan no longer flags the use-after-free in RunCheckpointingTest or the copy-when-could-move in Registerer

@jantonguirao jantonguirao force-pushed the coverity_fixes_20250505 branch 2 times, most recently from dd02952 to 7e4fbaa Compare May 5, 2026 09:00
@jantonguirao jantonguirao changed the title Coverity static analysis fixes (2026-05-05) Fix use-after-free in C API checkpointing test and avoid unnecessary copies in OperatorRegistry May 5, 2026
@jantonguirao jantonguirao force-pushed the coverity_fixes_20250505 branch from 7e4fbaa to e915521 Compare May 5, 2026 09:16
@jantonguirao jantonguirao changed the title Fix use-after-free in C API checkpointing test and avoid unnecessary copies in OperatorRegistry Coverity Fixes May 2026: Fix use-after-free in C API checkpointing test and avoid unnecessary copies in OperatorRegistry May 5, 2026
@jantonguirao jantonguirao changed the title Coverity Fixes May 2026: Fix use-after-free in C API checkpointing test and avoid unnecessary copies in OperatorRegistry Coverity static analysis fixes - May 2026 May 5, 2026
@jantonguirao jantonguirao marked this pull request as ready for review May 5, 2026 09:19
Copilot AI review requested due to automatic review settings May 5, 2026 09:19
@jantonguirao

Copy link
Copy Markdown
Collaborator Author

!build

@greptile-apps

greptile-apps Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR addresses three Coverity static-analysis findings across test and registration infrastructure. All changes are narrow, mechanical, and leave public API and runtime behaviour unchanged.

  • complex_pipeline_test.cc: Replaces the single reused checkpoint_h raw-handle variable with two distinct locals (checkpoint1_h / checkpoint2_h) so the RAII wrapper is clearly constructed from a fresh handle each time, eliminating the apparent use-after-transfer.
  • pipeline_test_utils.h: Zero-initialises raw_out_h in PopOutputs and PopOutputsAsync so that when the non-fatal EXPECT_EQ inside CHECK_DALI fires, the PipelineOutputsHandle RAII wrapper receives nullptr rather than an indeterminate value and the destructor does not call daliPipelineOutputsDestroy on garbage.
  • operator_factory.h: Changes Registerer's constructor to accept name by value and std::move both name and creator into OperatorRegistry::Register, and also moves name in the Register<Backend> template overload, removing two per-registration copies.

Confidence Score: 5/5

All three changes are narrow Coverity fixes with no functional impact — safe to merge.

Each change is a targeted, well-scoped fix: renaming a reused raw-handle local, adding a defensive null initialisation in test helpers, and converting constructor arguments to move semantics. None of them alter observable behaviour or touch production data paths beyond the registration fast-path at static-init time.

No files require special attention.

Important Files Changed

Filename Overview
dali/c_api_2/op_test/complex_pipeline_test.cc Splits reused checkpoint_h into distinct checkpoint1_h/checkpoint2_h locals to eliminate Coverity use-after-RAII-transfer warning; logic is unchanged.
dali/c_api_2/pipeline_test_utils.h Initialises raw_out_h = nullptr in PopOutputs and PopOutputsAsync; prevents RAII wrapper from calling destroy on an uninitialised handle when the non-fatal CHECK_DALI assertion fires.
dali/pipeline/operator/operator_factory.h Takes Registerer::name by value and moves both name and creator into Register; also moves name in the Register<Backend> template overload, eliminating redundant copies on every operator registration.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    subgraph complex_pipeline_test.cc
        A[daliPipelineGetCheckpoint] -->|writes| B[checkpoint1_h]
        B --> C[CheckpointHandle checkpoint1]
        D[daliPipelineDeserializeCheckpoint] -->|writes| E[checkpoint2_h]
        E --> F[CheckpointHandle checkpoint2]
    end

    subgraph pipeline_test_utils.h
        G["raw_out_h = nullptr"] --> H[daliPipelinePopOutputs]
        H -->|success| I[PipelineOutputsHandle wraps valid ptr]
        H -->|EXPECT_EQ fails| J[PipelineOutputsHandle wraps nullptr - safe destructor]
    end

    subgraph operator_factory.h
        K["Registerer(std::string name, Creator creator)"] -->|std::move both| L["OperatorRegistry::Register(std::string, Creator, ...)"]
        M["Register-Backend-(std::string name, Creator creator)"] -->|std::move both| L
    end
Loading

Reviews (5): Last reviewed commit: "Address review: drop pipeline.cc change,..." | Re-trigger Greptile

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses two Coverity findings in DALI: it updates operator registration to avoid extra copies during static registration, and it refactors a checkpointing test to use RAII for output handles and distinct checkpoint variables.

Changes:

  • Changed Registerer to take operator names by value and move both the name and creator into OperatorRegistry::Register.
  • Replaced manual output-pop/destroy code in RunCheckpointingTest with the PopOutputs RAII helper.
  • Split a reused checkpoint raw handle into separate checkpoint1_h and checkpoint2_h variables in the checkpointing test.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
dali/pipeline/operator/operator_factory.h Updates registration helper argument passing to reduce copies during operator registration.
dali/c_api_2/op_test/complex_pipeline_test.cc Refactors checkpointing test resource management and raw checkpoint handle naming.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +264 to +268
PopOutputs(pipe1); // discard
CHECK_DALI(daliPipelineRun(pipe1));
CHECK_DALI(daliPipelinePopOutputs(pipe1, &out1_h));
CHECK_DALI(daliPipelineOutputsDestroy(out1_h));
PopOutputs(pipe1); // discard
CHECK_DALI(daliPipelineRun(pipe1));
CHECK_DALI(daliPipelinePopOutputs(pipe1, &out1_h));
CHECK_DALI(daliPipelineOutputsDestroy(out1_h));
PopOutputs(pipe1); // discard

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. The test was meant to be run on raw C API. It's a C API test, after all.
  2. The comment about daliPipelinePopOutputs is correct - the function is broken and needs fixing.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@mzient mzient self-assigned this May 5, 2026
@dali-automaton

Copy link
Copy Markdown
Collaborator

CI MESSAGE: [50302622]: BUILD STARTED

@jantonguirao jantonguirao force-pushed the coverity_fixes_20250505 branch 2 times, most recently from 79eb5d9 to 30f33cd Compare May 5, 2026 10:37
…stry, broken PopOutputs contract

complex_pipeline_test.cc: split the reused checkpoint_h into distinct
checkpoint1_h / checkpoint2_h locals so there is no apparent reuse of
a handle after a RAII transfer, addressing the Coverity-flagged
use-after-free in RunCheckpointingTest. The test keeps using the raw
C API for output handles (it is intentionally a C API test).

pipeline.cc: in daliPipelinePopOutputs and daliPipelinePopOutputsAsync,
set *out = nullptr after CHECK_OUTPUT and before any operation that
may throw, so the failure-path contract is well-defined. Without this,
if ToPointer(pipeline) or pipe->PopOutputs() throws, DALI_EPILOG
returns an error code without ever writing *out, leaving the caller's
output variable in an indeterminate state.

operator_factory.h: take Registerer::name by value and std::move both
name and creator into OperatorRegistry::Register, eliminating two
copies on each operator registration. Also std::move(name) in the
Register<Backend> template overload so the name reaches the inner
Register without an extra copy.

Signed-off-by: Joaquin Anton Guirao <janton@nvidia.com>
Comment thread dali/c_api_2/pipeline.cc Outdated
Comment on lines +424 to +425
*out = nullptr;
auto pipe = ToPointer(pipeline);

@mzient mzient May 5, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is against the convention. If the function fails, it should not modify the output variable.
FWIW, CUDA runtime APIs behave the same way - if the arguments are invalid, no output is returned:

#include <cuda_runtime.h>
#include <stdio.h>

int main() {
  cudaStream_t s = (cudaStream_t)0x12345678;
  cudaError_t err = cudaStreamCreateWithFlags(&s, 0x1234568);
  printf("%i %p\n", err, s);
  return 0;
}

Output:

1 0x12345678

Comment thread dali/c_api_2/pipeline.cc Outdated
Comment on lines +436 to +437
*out = nullptr;
auto pipe = ToPointer(pipeline);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise.

@dali-automaton

Copy link
Copy Markdown
Collaborator

CI MESSAGE: [50302622]: BUILD PASSED

Per review feedback: writing *out = nullptr inside daliPipelinePopOutputs
and daliPipelinePopOutputsAsync violates the C API convention shared with
the CUDA runtime - on a non-success return, output parameters are not
modified (only DALI_NO_DATA is allowed to write nullptr to *out, and
neither of these functions returns that status). Revert the pipeline.cc
change in full so the surface stays consistent with the rest of the file.

The actual issue Copilot pointed at lives on the caller side: the
PopOutputs / PopOutputsAsync helpers in pipeline_test_utils.h declared
daliPipelineOutputs_h raw_out_h; uninitialized, and CHECK_DALI in this
codebase is EXPECT_EQ (non-fatal). On a failed call the helpers wrapped
an indeterminate handle in PipelineOutputsHandle and the destructor
would call daliPipelineOutputsDestroy on garbage. Initialize
raw_out_h = nullptr in both helpers so the destructor sees nullptr on
the failure path and the original EXPECT_EQ failure stays visible.

Signed-off-by: Joaquin Anton Guirao <janton@nvidia.com>
@jantonguirao

Copy link
Copy Markdown
Collaborator Author

!build

@dali-automaton

Copy link
Copy Markdown
Collaborator

CI MESSAGE: [50328210]: BUILD STARTED

@dali-automaton

Copy link
Copy Markdown
Collaborator

CI MESSAGE: [50328210]: BUILD PASSED

@jantonguirao jantonguirao merged commit 55b9ca3 into NVIDIA:main May 5, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants