https://github.com/NeKon69 updated https://github.com/llvm/llvm-project/pull/204536
>From 571fa96d31e733fe1168a1b1ce1ad013a41a74aa Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Thu, 18 Jun 2026 12:47:03 +0300 Subject: [PATCH 1/2] Revert "Revert "[LifetimeSafety] Support C Language in LifetimeSafety" (#204481)" --- .gitignore | 8 +- .../Analyses/LifetimeSafety/FactsGenerator.h | 5 +- clang/include/clang/Basic/Attr.td | 4 +- clang/include/clang/Basic/LangOptions.def | 2 + clang/include/clang/Options/Options.td | 7 + .../LifetimeSafety/FactsGenerator.cpp | 24 ++- clang/lib/Sema/AnalysisBasedWarnings.cpp | 9 +- clang/lib/Sema/SemaLifetimeSafety.h | 19 ++ clang/test/Sema/LifetimeSafety/safety-c.c | 181 ++++++++++++++++++ clang/test/Sema/attr-lifetime-capture-by.c | 28 +++ clang/test/Sema/attr-lifetimebound.c | 21 ++ 11 files changed, 297 insertions(+), 11 deletions(-) create mode 100644 clang/test/Sema/LifetimeSafety/safety-c.c create mode 100644 clang/test/Sema/attr-lifetime-capture-by.c create mode 100644 clang/test/Sema/attr-lifetimebound.c diff --git a/.gitignore b/.gitignore index 9d4e86ab10caa..0420627d58a56 100644 --- a/.gitignore +++ b/.gitignore @@ -27,7 +27,7 @@ /*/CMakeUserPresets.json # Nested build directory -/build* +/build_* #==============================================================================# # Explicit files to ignore (only matches one). @@ -66,6 +66,12 @@ AGENTS.md .cursor .cursorignore .cursorindexingignore +/.opencode/ +/.nvim/ +/.nvimlog +/dhw-extension/ +/install*/ +/tmp/ #==============================================================================# # Directories to ignore (do not add trailing '/'s, they skip symlinks). diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h index 2c961bd305fac..a021c09dcd34b 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h @@ -29,7 +29,9 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> { public: FactsGenerator(FactManager &FactMgr, AnalysisDeclContext &AC) - : FactMgr(FactMgr), AC(AC) {} + : FactMgr(FactMgr), AC(AC), + IsCMode(!AC.getASTContext().getLangOpts().CPlusPlus && + !AC.getASTContext().getLangOpts().ObjC) {} void run(); @@ -160,6 +162,7 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> { // exempting it from the check. llvm::DenseMap<const Expr *, UseFact *> UseFacts; const CFGBlock *CurrentBlock; + bool IsCMode = false; }; } // namespace clang::lifetimes::internal diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 1745c5a4f4c2a..a222092cd42cf 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -2129,14 +2129,14 @@ def ExplicitInit : InheritableAttr { } def LifetimeBound : DeclOrTypeAttr { - let Spellings = [Clang<"lifetimebound", 0>]; + let Spellings = [Clang<"lifetimebound", 1>]; let Subjects = SubjectList<[ParmVar, ImplicitObjectParameter], ErrorDiag>; let Documentation = [LifetimeBoundDocs]; let SimpleHandler = 1; } def LifetimeCaptureBy : DeclOrTypeAttr { - let Spellings = [Clang<"lifetime_capture_by", 0>]; + let Spellings = [Clang<"lifetime_capture_by", 1>]; let Subjects = SubjectList<[ParmVar, ImplicitObjectParameter], ErrorDiag>; let Args = [VariadicParamOrParamIdxArgument<"Params">]; let Documentation = [LifetimeCaptureByDocs]; diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index 75267ffea941d..319fd18cddb36 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -521,6 +521,8 @@ LANGOPT(BoundsSafety, 1, 0, NotCompatible, "Bounds safety extension for C") LANGOPT(DebugRunLifetimeSafety, 1, 0, Benign, "Run lifetime safety analysis for C++. Does not enable warnings.") +LANGOPT(EnableLifetimeSafetyInC, 1, 0, Benign, "Lifetime safety analysis for C") + LANGOPT(LifetimeSafetyMaxCFGBlocks, 32, 0, Benign, "Skip LifetimeSafety analysis for functions with CFG block count exceeding this threshold. Specify 0 for no limit") LANGOPT(EnableLifetimeSafetyInference, 1, 0, Benign, "Lifetime safety inference analysis for C++") diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index ecd66325bf8da..0a24f8b35a3ea 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -2017,6 +2017,13 @@ defm debug_run_lifetime_safety : BoolFOption< NegFlag<SetFalse, [], [CC1Option], "Disable">, BothFlags<[], [CC1Option], " lifetime safety analysis for C++. Warnings are still controlled by -Wlifetime-safety. Primarily used to surface crashes or compile-time regressions without showing analysis findings.">>; +defm experimental_lifetime_safety_c : BoolFOption< + "experimental-lifetime-safety-c", + LangOpts<"EnableLifetimeSafetyInC">, DefaultFalse, + PosFlag<SetTrue, [], [CC1Option], "Enable">, + NegFlag<SetFalse, [], [CC1Option], "Disable">, + BothFlags<[], [CC1Option], " experimental lifetime safety analysis for C">>; + def lifetime_safety_max_cfg_blocks : Joined<["-"], "lifetime-safety-max-cfg-blocks=">, Group<m_Group>, diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index 10339a9f2e3c5..d56703a4b29c4 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -324,6 +324,10 @@ void FactsGenerator::VisitCastExpr(const CastExpr *CE) { flow(Dest, Src, /*Kill=*/true); return; case CK_ArrayToPointerDecay: + // va_arg(ap, array_type) is UB and does not provide addressable array + // storage to model. + if (isa<VAArgExpr>(SubExpr->IgnoreParens())) + return; assert(Src && "Array expression should have origins as it is GL value"); CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>( Dest->getOuterOriginID(), Src->getOuterOriginID(), /*Kill=*/true)); @@ -347,6 +351,15 @@ void FactsGenerator::VisitUnaryOperator(const UnaryOperator *UO) { switch (UO->getOpcode()) { case UO_AddrOf: { const Expr *SubExpr = UO->getSubExpr(); + // Function addresses do not need lifetime tracking. + if (SubExpr->getType()->isFunctionType()) + return; + // Skip address-of on void expressions: GNU C permits them, but void itself + // has no origins to track. + if (IsCMode && SubExpr->getType()->isVoidType()) + return; + assert(!SubExpr->getType()->isVoidType() && + "Taking address of void is not valid in C++"); // The origin of an address-of expression (e.g., &x) is the origin of // its sub-expression (x). This fact will cause the dataflow analysis // to propagate any loans held by the sub-expression's origin to the @@ -435,7 +448,12 @@ void FactsGenerator::handleAssignment(const Expr *TargetExpr, // Kill the old loans of the destination origin and flow the new loans // from the source origin. flow(LHSList->peelOuterOrigin(), RHSList, /*Kill=*/true); - killAndFlowOrigin(*TargetExpr, *LHSExpr); + + // In C, assignment expressions are not GLValues, so the assignment result has + // the assigned value origins, not the LHS storage origin. + if (IsCMode) + LHSList = getRValueOrigins(LHSExpr, LHSList); + flow(getOriginsList(*TargetExpr), LHSList, /*Kill=*/true); } void FactsGenerator::handlePointerArithmetic(const BinaryOperator *BO) { @@ -631,6 +649,10 @@ void FactsGenerator::VisitLambdaExpr(const LambdaExpr *LE) { } void FactsGenerator::VisitArraySubscriptExpr(const ArraySubscriptExpr *ASE) { + // Some C subscripts do not refer to addressable storage with origins, such as + // GNU void-pointer subscripts and vector element extraction from rvalues. + if (IsCMode && !ASE->isGLValue()) + return; assert(ASE->isGLValue() && "Array subscript should be a GL value"); OriginList *Dst = getOriginsList(*ASE); assert(Dst && "Array subscript should have origins as it is a GL value"); diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp index 207ce373339f3..52fe7765c6c6a 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -2999,8 +2999,7 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings( } } - if (S.getLangOpts().CPlusPlus && - S.getLangOpts().EnableLifetimeSafetyTUAnalysis) + if (lifetimes::IsLifetimeSafetyEnabled(S, TU)) LifetimeSafetyTUAnalysis(S, TU, LSStats); } @@ -3049,9 +3048,7 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings( AC.getCFGBuildOptions().AddCXXNewAllocator = false; AC.getCFGBuildOptions().AddCXXDefaultInitExprInCtors = true; - bool EnableLifetimeSafetyAnalysis = - !S.getLangOpts().EnableLifetimeSafetyTUAnalysis && - lifetimes::IsLifetimeSafetyEnabled(S, D); + bool EnableLifetimeSafetyAnalysis = lifetimes::IsLifetimeSafetyEnabled(S, D); // Force that certain expressions appear as CFGElements in the CFG. This // is used to speed up various analyses. @@ -3164,7 +3161,7 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings( // TODO: Enable lifetime safety analysis for other languages once it is // stable. - if (EnableLifetimeSafetyAnalysis && S.getLangOpts().CPlusPlus) { + if (EnableLifetimeSafetyAnalysis) { if (AC.getCFG()) { lifetimes::LifetimeSafetySemaHelperImpl LifetimeSafetySemaHelper(S); lifetimes::runLifetimeSafetyAnalysis(AC, &LifetimeSafetySemaHelper, diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h index 91f1290f38723..a8bde363e3397 100644 --- a/clang/lib/Sema/SemaLifetimeSafety.h +++ b/clang/lib/Sema/SemaLifetimeSafety.h @@ -25,6 +25,25 @@ namespace clang::lifetimes { inline bool IsLifetimeSafetyEnabled(Sema &S, const Decl *D) { + // TODO: Enable ObjectiveC later when we know it's stable enough. + if (S.getLangOpts().ObjC) + return false; + + // TODO: Default this flag to on in the future. + if (!S.getLangOpts().CPlusPlus && !S.getLangOpts().EnableLifetimeSafetyInC) + return false; + + // Translation-unit mode: whole-program analysis runs once on TU. + // Individual function analysis is disabled when TU mode is enabled. + if (S.getLangOpts().EnableLifetimeSafetyTUAnalysis) + return isa<TranslationUnitDecl>(D); + + // Per-function mode: analysis runs on each function/method individually. + // Skip TU-level calls when per-function mode is enabled. + if (isa<TranslationUnitDecl>(D)) + return false; + + // Enable per-function mode via debug flag or specific diagnostics. if (S.getLangOpts().DebugRunLifetimeSafety) return true; DiagnosticsEngine &Diags = S.getDiagnostics(); diff --git a/clang/test/Sema/LifetimeSafety/safety-c.c b/clang/test/Sema/LifetimeSafety/safety-c.c new file mode 100644 index 0000000000000..95c8cf7bb00c7 --- /dev/null +++ b/clang/test/Sema/LifetimeSafety/safety-c.c @@ -0,0 +1,181 @@ +// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -Wno-varargs -Wno-non-pod-varargs -verify -fexperimental-lifetime-safety-c %s +// RUN: %clang_cc1 -fsyntax-only -Werror=lifetime-safety -Wno-dangling -Wno-varargs -Wno-non-pod-varargs %s + +int *identity(int *p __attribute__((lifetimebound))) { return p; } + +struct PointerField { + int *ptr; +}; + +struct IntField { + int field; +}; + +void simple_case(void) { + int *p; + { + int i; + p = &i; // expected-warning {{local variable 'i' does not live long enough}} + } // expected-note {{destroyed here}} + (void)*p; // expected-note {{later used here}} +} + +void chained_assignment(void) { + int *p, *q, *r; + { + int i; + p = q = r = &i; // expected-warning {{local variable 'i' does not live long enough}} + } // expected-note {{destroyed here}} + (void)*p; // expected-note {{later used here}} +} + +void conditional_branch(int cond) { + int safe; + int *p = &safe; + if (cond) { + int i; + p = &i; // expected-warning {{local variable 'i' does not live long enough}} + } // expected-note {{destroyed here}} + (void)*p; // expected-note {{later used here}} +} + +void loop_with_break(int cond) { + int safe; + int *p = &safe; + for (int n = 0; n != 10; ++n) { + if (cond) { + int i; + p = &i; // expected-warning {{local variable 'i' does not live long enough}} + break; // expected-note {{destroyed here}} + } + } + (void)*p; // expected-note {{later used here}} +} + +int *return_stack_address(void) { + int i; + int *p = &i; // expected-warning {{stack memory associated with local variable 'i' is returned}} + return p; // expected-note {{returned here}} +} + +void lifetimebound_call(void) { + int *p; + { + int i; + p = identity(&i); // expected-warning {{local variable 'i' does not live long enough}} \ + // expected-note {{result of call to 'identity' aliases the storage of local variable 'i'}} + } // expected-note {{destroyed here}} + (void)*p; // expected-note {{later used here}} +} + +void struct_pointer_field(void) { + int *p; + { + int i; + struct PointerField holder; + // FIXME: Track origins stored in struct pointer fields. + holder.ptr = &i; + p = holder.ptr; + } + (void)*p; +} + +void struct_address_of_field(void) { + int *p; + { + struct IntField holder; + p = &holder.field; // expected-warning {{local variable 'holder' does not live long enough}} + } // expected-note {{destroyed here}} + (void)*p; // expected-note {{later used here}} +} + +void conditional_operator_lifetimebound(int cond) { + int *p; + { + int a, b; + p = identity(cond ? &a // expected-warning {{local variable 'a' does not live long enough}} + : &b); // expected-warning {{local variable 'b' does not live long enough}} + } // expected-note 2 {{destroyed here}} + (void)*p; // expected-note 2 {{later used here}} +} + +union IntOrPtr { + int i; + int *p; +}; + +void union_member(void) { + int *p; + { + union IntOrPtr u; + p = &u.i; // expected-warning {{local variable 'u' does not live long enough}} + } // expected-note {{destroyed here}} + (void)*p; // expected-note {{later used here}} +} + +struct AnonymousUnion { + union { + int i; + float f; + }; +}; + +void anonymous_union_member(void) { + int *p; + { + struct AnonymousUnion u; + p = &u.i; // expected-warning {{local variable 'u' does not live long enough}} + } // expected-note {{destroyed here}} + (void)*p; // expected-note {{later used here}} +} + +void function_address_regression(void) { + extern void function_address_target(void); + char *p = (char *)&function_address_target; + (void)p; +} + +int void_pointer_subscript_regression(void *bytes) { + return &bytes[0] == &bytes[1]; +} + +typedef __attribute__((vector_size(16))) int v4i32; +v4i32 (*vector_factory)(int); + +int vector_subscript_regression(void) { + return (*vector_factory)(0)[0]; +} + +void va_arg_array_regression(int n, ...) { + __builtin_va_list ap; + __builtin_va_start(ap, n); + int *p = __builtin_va_arg(ap, int[4]); + (void)p; +} + +void take(int* q); +void va_arg_array_paren_regression(int n, ...) { + __builtin_va_list ap; + take((__builtin_va_arg(ap, int[4]))); +} + +void va_arg_function_regression(int n, ...) { + __builtin_va_list ap; + __builtin_va_start(ap, n); + int (*p)(void) = __builtin_va_arg(ap, int(void)); + (void)p; +} + +// FIXME: We miss the origins of void* after dereference, so we miss to warn here. +void *void_pointer_dereference(void) { + int value; + void *bytes = &value; + return &*bytes; +} + +// FIXME: Atomics are not modeled yet. +int *atomic_pointer_declref(void) { + int value; + _Atomic(int *) p = &value; + return p; +} diff --git a/clang/test/Sema/attr-lifetime-capture-by.c b/clang/test/Sema/attr-lifetime-capture-by.c new file mode 100644 index 0000000000000..f847a5bbca7c5 --- /dev/null +++ b/clang/test/Sema/attr-lifetime-capture-by.c @@ -0,0 +1,28 @@ +// RUN: %clang_cc1 -std=c2x -verify %s + +struct S { + int *x; +}; + +void capture(int *x [[clang::lifetime_capture_by(s, t)]], + struct S *s, + struct S *t); +void capture_gnu(int *x __attribute__((lifetime_capture_by(s, t))), + struct S *s, + struct S *t); + +[[clang::lifetime_capture_by(s)]] // expected-error {{'clang::lifetime_capture_by' attribute only applies to parameters and implicit object parameters}} +void attr_on_function(int *x); +void attr_on_function_gnu(int *x) __attribute__((lifetime_capture_by(s))); // expected-error {{'lifetime_capture_by' attribute only applies to parameters and implicit object parameters}} + +void invalid_args(int *x1 [[clang::lifetime_capture_by(12345 + 12)]], // expected-error {{'lifetime_capture_by' attribute argument '12345 + 12' is not a known function parameter; must be a function parameter, 'this', 'global' or 'unknown'}} + int *x2 [[clang::lifetime_capture_by(no_such_param)]], // expected-error {{'lifetime_capture_by' attribute argument 'no_such_param' is not a known function parameter; must be a function parameter, 'this', 'global' or 'unknown'}} + int *x3 [[clang::lifetime_capture_by("no_such_param")]], // expected-error {{'lifetime_capture_by' attribute argument '"no_such_param"' is not a known function parameter; must be a function parameter, 'this', 'global' or 'unknown'}} + int *x4 [[clang::lifetime_capture_by()]], // expected-error {{'lifetime_capture_by' attribute specifies no capturing entity}} + int *x5 [[clang::lifetime_capture_by(x5)]]); // expected-error {{'lifetime_capture_by' argument references itself}} + +void invalid_args_gnu(int *x1 __attribute__((lifetime_capture_by(12345 + 12))), // expected-error {{'lifetime_capture_by' attribute argument '12345 + 12' is not a known function parameter; must be a function parameter, 'this', 'global' or 'unknown'}} + int *x2 __attribute__((lifetime_capture_by(no_such_param))), // expected-error {{'lifetime_capture_by' attribute argument 'no_such_param' is not a known function parameter; must be a function parameter, 'this', 'global' or 'unknown'}} + int *x3 __attribute__((lifetime_capture_by("no_such_param"))), // expected-error {{'lifetime_capture_by' attribute argument '"no_such_param"' is not a known function parameter; must be a function parameter, 'this', 'global' or 'unknown'}} + int *x4 __attribute__((lifetime_capture_by())), // expected-error {{'lifetime_capture_by' attribute specifies no capturing entity}} + int *x5 __attribute__((lifetime_capture_by(x5)))); // expected-error {{'lifetime_capture_by' argument references itself}} diff --git a/clang/test/Sema/attr-lifetimebound.c b/clang/test/Sema/attr-lifetimebound.c new file mode 100644 index 0000000000000..6292fe90bdf68 --- /dev/null +++ b/clang/test/Sema/attr-lifetimebound.c @@ -0,0 +1,21 @@ +// RUN: %clang_cc1 -std=c2x -verify %s + +int *ptr_param(int *param [[clang::lifetimebound]]); +int *ptr_param_gnu(int *param __attribute__((lifetimebound))); +int *ptr_param_redecl(int *param); +int *ptr_param_redecl(int *param [[clang::lifetimebound]]); +int *ptr_param_redecl(int *param) { return param; } +int *ptr_param_redecl_gnu(int *param); +int *ptr_param_redecl_gnu(int *param __attribute__((lifetimebound))); +int *ptr_param_redecl_gnu(int *param) { return param; } + +void void_return(int *param [[clang::lifetimebound]]); // expected-error {{'lifetimebound' attribute cannot be applied to a parameter of a function that returns void; did you mean 'lifetime_capture_by(X)'}} +void void_return_gnu(int *param __attribute__((lifetimebound))); // expected-error {{'lifetimebound' attribute cannot be applied to a parameter of a function that returns void; did you mean 'lifetime_capture_by(X)'}} + +int *attr_with_arg(int *param [[clang::lifetimebound(1)]]); // expected-error {{'clang::lifetimebound' attribute takes no arguments}} +int *attr_with_arg_gnu(int *param __attribute__((lifetimebound(1)))); // expected-error {{'lifetimebound' attribute takes no arguments}} + +int attr_on_var [[clang::lifetimebound]]; // expected-error {{'clang::lifetimebound' attribute only applies to parameters and implicit object parameters}} +int attr_on_var_gnu __attribute__((lifetimebound)); // expected-error {{'lifetimebound' attribute only applies to parameters and implicit object parameters}} +int * [[clang::lifetimebound]] attr_on_pointee; // expected-error {{'clang::lifetimebound' attribute only applies to parameters and implicit object parameters}} +int (*func_ptr)(int) [[clang::lifetimebound]]; // expected-error {{'clang::lifetimebound' attribute only applies to parameters and implicit object parameters}} >From abeed159e8a4959c97d956315bbf64c7a72819fe Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Thu, 18 Jun 2026 12:47:08 +0300 Subject: [PATCH 2/2] revert local gitignore changes --- .gitignore | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 0420627d58a56..9d4e86ab10caa 100644 --- a/.gitignore +++ b/.gitignore @@ -27,7 +27,7 @@ /*/CMakeUserPresets.json # Nested build directory -/build_* +/build* #==============================================================================# # Explicit files to ignore (only matches one). @@ -66,12 +66,6 @@ AGENTS.md .cursor .cursorignore .cursorindexingignore -/.opencode/ -/.nvim/ -/.nvimlog -/dhw-extension/ -/install*/ -/tmp/ #==============================================================================# # Directories to ignore (do not add trailing '/'s, they skip symlinks). _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
