diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index 40fc66ea12e34..a136432993a3c 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -499,6 +499,8 @@ LANGOPT(BoundsSafety, 1, 0, NotCompatible, "Bounds safety extension for C") LANGOPT(EnableLifetimeSafety, 1, 0, NotCompatible, "Experimental lifetime safety analysis for C++") +LANGOPT(EnableLifetimeSafetyInference, 1, 0, NotCompatible, "Experimental lifetime safety inference analysis for C++") + LANGOPT(PreserveVec3Type, 1, 0, NotCompatible, "Preserve 3-component vector type") #undef LANGOPT diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index c6841937c8d39..39c5753e50aed 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -1955,6 +1955,14 @@ defm lifetime_safety : BoolFOption< BothFlags<[], [CC1Option], " experimental lifetime safety for C++">>; +defm lifetime_safety_inference + : BoolFOption<"experimental-lifetime-safety-inference", + LangOpts<"EnableLifetimeSafetyInference">, DefaultFalse, + PosFlag, + NegFlag, + BothFlags<[], [CC1Option], + " experimental lifetime safety inference for C++">>; + defm addrsig : BoolFOption<"addrsig", CodeGenOpts<"Addrsig">, DefaultFalse, PosFlag, diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index 74792768e2c57..99071d6b46c1e 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -55,13 +55,14 @@ class LifetimeChecker { const LiveOriginsAnalysis &LiveOrigins; const FactManager &FactMgr; LifetimeSafetyReporter *Reporter; + ASTContext &AST; public: LifetimeChecker(const LoanPropagationAnalysis &LoanPropagation, const LiveOriginsAnalysis &LiveOrigins, const FactManager &FM, AnalysisDeclContext &ADC, LifetimeSafetyReporter *Reporter) : LoanPropagation(LoanPropagation), LiveOrigins(LiveOrigins), FactMgr(FM), - Reporter(Reporter) { + Reporter(Reporter), AST(ADC.getASTContext()) { for (const CFGBlock *B : *ADC.getAnalysis()) for (const Fact *F : FactMgr.getFacts(B)) if (const auto *EF = F->getAs()) @@ -70,6 +71,11 @@ class LifetimeChecker { checkAnnotations(OEF); issuePendingWarnings(); suggestAnnotations(); + // Annotation inference is currently guarded by a frontend flag. In the + // future, this might be replaced by a design that differentiates between + // explicit and inferred findings with separate warning groups. + if (AST.getLangOpts().EnableLifetimeSafetyInference) + inferAnnotations(); } /// Checks if an escaping origin holds a placeholder loan, indicating a @@ -160,6 +166,20 @@ class LifetimeChecker { for (const auto &[PVD, EscapeExpr] : AnnotationWarningsMap) Reporter->suggestAnnotation(PVD, EscapeExpr); } + + void inferAnnotations() { + // FIXME: To maximise inference propagation, functions should be analyzed in + // post-order of the call graph, allowing inferred annotations to propagate + // through the call chain + // FIXME: Add the inferred attribute to all redeclarations of the function, + // not just the definition being analyzed. + for (const auto &[ConstPVD, EscapeExpr] : AnnotationWarningsMap) { + ParmVarDecl *PVD = const_cast(ConstPVD); + if (!PVD->hasAttr()) + PVD->addAttr( + LifetimeBoundAttr::CreateImplicit(AST, PVD->getLocation())); + } + } }; } // namespace diff --git a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp index c0f675a301d14..9f3ccb7fca770 100644 --- a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp +++ b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -fsyntax-only -fexperimental-lifetime-safety -Wexperimental-lifetime-safety-suggestions -verify %s +// RUN: %clang_cc1 -fsyntax-only -fexperimental-lifetime-safety -fexperimental-lifetime-safety-inference -Wexperimental-lifetime-safety-suggestions -Wexperimental-lifetime-safety -verify %s struct MyObj { int id; @@ -89,6 +89,98 @@ void test_getView_on_temporary() { (void)sv; } +//===----------------------------------------------------------------------===// +// Annotation Inference Test Cases +//===----------------------------------------------------------------------===// + +namespace correct_order_inference { +View return_view_by_func (View a) { // expected-warning {{param should be marked [[clang::lifetimebound]]}}. + return return_view_directly(a); // expected-note {{param returned here}} +} + +MyObj* return_pointer_by_func (MyObj* a) { // expected-warning {{param should be marked [[clang::lifetimebound]]}}. + return return_pointer_object(a); // expected-note {{param returned here}} +} +} // namespace correct_order_inference + +namespace incorrect_order_inference_view { +View return_view_callee(View a); + +// FIXME: No lifetime annotation suggestion when functions are not present in the callee-before-caller pattern +View return_view_caller(View a) { + return return_view_callee(a); +} + +View return_view_callee(View a) { // expected-warning {{param should be marked [[clang::lifetimebound]]}}. + return a; // expected-note {{param returned here}} +} +} // namespace incorrect_order_inference_view + +namespace incorrect_order_inference_object { +MyObj* return_object_callee(MyObj* a); + +// FIXME: No lifetime annotation suggestion warning when functions are not present in the callee-before-caller pattern +MyObj* return_object_caller(MyObj* a) { + return return_object_callee(a); +} + +MyObj* return_object_callee(MyObj* a) { // expected-warning {{param should be marked [[clang::lifetimebound]]}}. + return a; // expected-note {{param returned here}} +} +} // namespace incorrect_order_inference_object + +namespace simple_annotation_inference { +View inference_callee_return_identity(View a) { // expected-warning {{param should be marked [[clang::lifetimebound]]}}. + return a; // expected-note {{param returned here}} +} + +View inference_caller_forwards_callee(View a) { // expected-warning {{param should be marked [[clang::lifetimebound]]}}. + return inference_callee_return_identity(a); // expected-note {{param returned here}} +} + +View inference_top_level_return_stack_view() { + MyObj local_stack; + return inference_caller_forwards_callee(local_stack); // expected-warning {{address of stack memory is returned later}} + // expected-note@-1 {{returned here}} +} +} // namespace simple_annotation_inference + +namespace inference_in_order_with_redecls { +View inference_callee_return_identity(View a); +View inference_callee_return_identity(View a) { // expected-warning {{param should be marked [[clang::lifetimebound]]}}. + return a; // expected-note {{param returned here}} +} + +View inference_caller_forwards_callee(View a); +View inference_caller_forwards_callee(View a) { // expected-warning {{param should be marked [[clang::lifetimebound]]}}. + return inference_callee_return_identity(a); // expected-note {{param returned here}} +} + +View inference_top_level_return_stack_view() { + MyObj local_stack; + return inference_caller_forwards_callee(local_stack); // expected-warning {{address of stack memory is returned later}} + // expected-note@-1 {{returned here}} +} +} // namespace inference_in_order_with_redecls + +namespace inference_with_templates { +template +T* template_identity(T* a) { // expected-warning {{param should be marked [[clang::lifetimebound]]}}. + return a; // expected-note {{param returned here}} +} + +template +T* template_caller(T* a) { + return template_identity(a); // expected-note {{in instantiation of function template specialization 'inference_with_templates::template_identity' requested here}} +} + +// FIXME: Fails to detect UAR as template instantiations are deferred to the end of the Translation Unit. +MyObj* test_template_inference_with_stack() { + MyObj local_stack; + return template_caller(&local_stack); // expected-note {{in instantiation of function template specialization 'inference_with_templates::template_caller' requested here}} +} +} // namespace inference_with_templates + //===----------------------------------------------------------------------===// // Negative Test Cases //===----------------------------------------------------------------------===//