Enforce that a derivative references only decls it owns. - #1894
Conversation
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.
| // 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> { |
There was a problem hiding this comment.
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; } |
There was a problem hiding this comment.
warning: function 'shouldVisitImplicitCode' should be marked [[nodiscard]] [modernize-use-nodiscard]
| 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; } |
There was a problem hiding this comment.
warning: method 'shouldVisitImplicitCode' can be made static [readability-convert-member-functions-to-static]
| 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()); |
There was a problem hiding this comment.
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 && |
There was a problem hiding this comment.
warning: no header providing "clang::isa" is directly included [misc-include-cleaner]
if (isa<VarDecl>(D) && DC &&
^f79fec4 to
e23e7c5
Compare
| << VD << L; | ||
| } | ||
|
|
||
| #if CLANG_VERSION_MAJOR > 16 |
There was a problem hiding this comment.
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 Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
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.