Skip to content

Enforce that a derivative references only decls it owns. - #1894

Merged
vgvassilev merged 1 commit into
masterfrom
stray-ref-guardrail
Jul 16, 2026
Merged

Enforce that a derivative references only decls it owns.#1894
vgvassilev merged 1 commit into
masterfrom
stray-ref-guardrail

Conversation

@vgvassilev

Copy link
Copy Markdown
Owner

A generated derivative must not reference the original function's own parameters or locals -- they do not exist in the derivative, so such a reference is a forgotten reference-remap (a primal clone never registered in m_DeclReplacements) that would miscompile. Until now this was masked by the scope-dependent name-lookup fallback in ReferencesUpdater.

Add the check as a hard integrity invariant, alongside the shared-node and primal-splice checks, via a single ASTIntegrity entry point: verifyDerivative walks the body once and returns an IntegrityReport, while DerivativeBuilder keeps the policy -- assert in debug, diagnose in release. The stray-reference check fires only for a clean differentiation: an unsupported construct is cloned wholesale and knowingly keeps such references, so it is skipped when this function's differentiation emitted any diagnostic. A preserved lambda's own parameter is excluded by matching the declaration's immediate function context, not mere enclosure.

A generated derivative must not reference the original function's own
parameters or locals -- they do not exist in the derivative, so such a
reference is a forgotten reference-remap (a primal clone never registered
in m_DeclReplacements) that would miscompile. Until now this was masked
by the scope-dependent name-lookup fallback in ReferencesUpdater.

Add the check as a hard integrity invariant, alongside the shared-node
and primal-splice checks, via a single ASTIntegrity entry point:
verifyDerivative walks the body once and returns an IntegrityReport, while
DerivativeBuilder keeps the policy -- assert in debug, diagnose in release.
The stray-reference check fires only for a clean differentiation: an
unsupported construct is cloned wholesale and knowingly keeps such
references, so it is skipped when this function's differentiation emitted
any diagnostic. A preserved lambda's own parameter is excluded by matching
the declaration's immediate function context, not mere enclosure.

@github-actions github-actions Bot left a comment

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.

clang-tidy made some suggestions

// reference still bound to one of Original's own decls means its remap was
// forgotten (the primal clone was never registered in m_DeclReplacements).
// Walk the finished body and flag the first such reference.
struct Finder : RecursiveASTVisitor<Finder> {

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.

warning: constructor does not initialize these fields: Original [cppcoreguidelines-pro-type-member-init]

lib/Differentiator/ASTIntegrity.cpp:73:

-     const FunctionDecl* Original;
+     const FunctionDecl* Original{};

struct Finder : RecursiveASTVisitor<Finder> {
const FunctionDecl* Original;
const ValueDecl* Stray = nullptr;
bool shouldVisitImplicitCode() const { return true; }

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.

warning: function 'shouldVisitImplicitCode' should be marked [[nodiscard]] [modernize-use-nodiscard]

Suggested change
bool shouldVisitImplicitCode() const { return true; }
[[nodiscard]] bool shouldVisitImplicitCode() const { return true; }

struct Finder : RecursiveASTVisitor<Finder> {
const FunctionDecl* Original;
const ValueDecl* Stray = nullptr;
bool shouldVisitImplicitCode() const { return true; }

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.

warning: method 'shouldVisitImplicitCode' can be made static [readability-convert-member-functions-to-static]

Suggested change
bool shouldVisitImplicitCode() const { return true; }
static bool shouldVisitImplicitCode() { return true; }

// parameter (context is the lambda's CXXMethod, not Original) is
// referenced by design when the lambda is preserved, not a forgotten
// clone, so exact-context match excludes it.
const auto* DC = dyn_cast<FunctionDecl>(D->getDeclContext());

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.

warning: no header providing "clang::dyn_cast" is directly included [misc-include-cleaner]

lib/Differentiator/ASTIntegrity.cpp:18:

+ #include <clang/Basic/LLVM.h>

// referenced by design when the lambda is preserved, not a forgotten
// clone, so exact-context match excludes it.
const auto* DC = dyn_cast<FunctionDecl>(D->getDeclContext());
if (isa<VarDecl>(D) && DC &&

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.

warning: no header providing "clang::isa" is directly included [misc-include-cleaner]

      if (isa<VarDecl>(D) && DC &&
          ^

@vgvassilev
vgvassilev force-pushed the stray-ref-guardrail branch from f79fec4 to e23e7c5 Compare July 15, 2026 23:55

@github-actions github-actions Bot left a comment

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.

clang-tidy made some suggestions

<< VD << L;
}

#if CLANG_VERSION_MAJOR > 16

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.

warning: no header providing "CLANG_VERSION_MAJOR" is directly included [misc-include-cleaner]

lib/Differentiator/DerivativeBuilder.cpp:51:

- #include <cstddef>
+ #include <clang/Basic/Version.h>
+ #include <cstddef>

@codecov

codecov Bot commented Jul 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.55556% with 11 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
lib/Differentiator/DerivativeBuilder.cpp 57.89% 8 Missing ⚠️
lib/Differentiator/Version.cpp 0.00% 2 Missing ⚠️
lib/Differentiator/ASTIntegrity.cpp 95.83% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@vgvassilev
vgvassilev merged commit db3ae21 into master Jul 16, 2026
39 of 40 checks passed
@vgvassilev
vgvassilev deleted the stray-ref-guardrail branch July 16, 2026 10:54
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.

1 participant