Skip to content

Commit 86f8445

Browse files
authored
[LifetimeSafety] Infer [[clang::lifetimebound]] annotation (#171081)
Adding Annotation Inference in Lifetime Analysis. This PR implicitly adds lifetime bound annotations to the AST which is then used by functions which are parsed later to detect UARs etc. Example: ```cpp std::string_view f1(std::string_view a) { return a; } std::string_view f2(std::string_view a) { return f1(a); } std::string_view ff(std::string_view a) { std::string stack = "something on stack"; return f2(stack); // warning: address of stack memory is returned } ``` Note: 1. We only add lifetime bound annotations to the functions being analyzed currently. 2. Currently, both annotation suggestion and inference work simultaneously. This can be modified based on requirements. 3. The current approach works given that functions are already present in the correct order (callee-before-caller). For not so ideal cases, we can create a CallGraph prior to calling the analysis. This can be done in the next PR.
1 parent d15ff59 commit 86f8445

File tree

4 files changed

+124
-2
lines changed

4 files changed

+124
-2
lines changed

clang/include/clang/Basic/LangOptions.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,8 @@ LANGOPT(BoundsSafety, 1, 0, NotCompatible, "Bounds safety extension for C")
501501

502502
LANGOPT(EnableLifetimeSafety, 1, 0, NotCompatible, "Experimental lifetime safety analysis for C++")
503503

504+
LANGOPT(EnableLifetimeSafetyInference, 1, 0, NotCompatible, "Experimental lifetime safety inference analysis for C++")
505+
504506
LANGOPT(PreserveVec3Type, 1, 0, NotCompatible, "Preserve 3-component vector type")
505507

506508
#undef LANGOPT

clang/include/clang/Options/Options.td

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1964,6 +1964,14 @@ defm lifetime_safety : BoolFOption<
19641964
BothFlags<[], [CC1Option],
19651965
" experimental lifetime safety for C++">>;
19661966

1967+
defm lifetime_safety_inference
1968+
: BoolFOption<"experimental-lifetime-safety-inference",
1969+
LangOpts<"EnableLifetimeSafetyInference">, DefaultFalse,
1970+
PosFlag<SetTrue, [], [CC1Option], "Enable">,
1971+
NegFlag<SetFalse, [], [CC1Option], "Disable">,
1972+
BothFlags<[], [CC1Option],
1973+
" experimental lifetime safety inference for C++">>;
1974+
19671975
defm addrsig : BoolFOption<"addrsig",
19681976
CodeGenOpts<"Addrsig">, DefaultFalse,
19691977
PosFlag<SetTrue, [], [ClangOption, CC1Option], "Emit">,

clang/lib/Analysis/LifetimeSafety/Checker.cpp

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,14 @@ class LifetimeChecker {
5555
const LiveOriginsAnalysis &LiveOrigins;
5656
const FactManager &FactMgr;
5757
LifetimeSafetyReporter *Reporter;
58+
ASTContext &AST;
5859

5960
public:
6061
LifetimeChecker(const LoanPropagationAnalysis &LoanPropagation,
6162
const LiveOriginsAnalysis &LiveOrigins, const FactManager &FM,
6263
AnalysisDeclContext &ADC, LifetimeSafetyReporter *Reporter)
6364
: LoanPropagation(LoanPropagation), LiveOrigins(LiveOrigins), FactMgr(FM),
64-
Reporter(Reporter) {
65+
Reporter(Reporter), AST(ADC.getASTContext()) {
6566
for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>())
6667
for (const Fact *F : FactMgr.getFacts(B))
6768
if (const auto *EF = F->getAs<ExpireFact>())
@@ -70,6 +71,11 @@ class LifetimeChecker {
7071
checkAnnotations(OEF);
7172
issuePendingWarnings();
7273
suggestAnnotations();
74+
// Annotation inference is currently guarded by a frontend flag. In the
75+
// future, this might be replaced by a design that differentiates between
76+
// explicit and inferred findings with separate warning groups.
77+
if (AST.getLangOpts().EnableLifetimeSafetyInference)
78+
inferAnnotations();
7379
}
7480

7581
/// Checks if an escaping origin holds a placeholder loan, indicating a
@@ -160,6 +166,20 @@ class LifetimeChecker {
160166
for (const auto &[PVD, EscapeExpr] : AnnotationWarningsMap)
161167
Reporter->suggestAnnotation(PVD, EscapeExpr);
162168
}
169+
170+
void inferAnnotations() {
171+
// FIXME: To maximise inference propagation, functions should be analyzed in
172+
// post-order of the call graph, allowing inferred annotations to propagate
173+
// through the call chain
174+
// FIXME: Add the inferred attribute to all redeclarations of the function,
175+
// not just the definition being analyzed.
176+
for (const auto &[ConstPVD, EscapeExpr] : AnnotationWarningsMap) {
177+
ParmVarDecl *PVD = const_cast<ParmVarDecl *>(ConstPVD);
178+
if (!PVD->hasAttr<LifetimeBoundAttr>())
179+
PVD->addAttr(
180+
LifetimeBoundAttr::CreateImplicit(AST, PVD->getLocation()));
181+
}
182+
}
163183
};
164184
} // namespace
165185

clang/test/Sema/warn-lifetime-safety-suggestions.cpp

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %clang_cc1 -fsyntax-only -fexperimental-lifetime-safety -Wexperimental-lifetime-safety-suggestions -verify %s
1+
// RUN: %clang_cc1 -fsyntax-only -fexperimental-lifetime-safety -fexperimental-lifetime-safety-inference -Wexperimental-lifetime-safety-suggestions -Wexperimental-lifetime-safety -verify %s
22

33
struct MyObj {
44
int id;
@@ -89,6 +89,98 @@ void test_getView_on_temporary() {
8989
(void)sv;
9090
}
9191

92+
//===----------------------------------------------------------------------===//
93+
// Annotation Inference Test Cases
94+
//===----------------------------------------------------------------------===//
95+
96+
namespace correct_order_inference {
97+
View return_view_by_func (View a) { // expected-warning {{param should be marked [[clang::lifetimebound]]}}.
98+
return return_view_directly(a); // expected-note {{param returned here}}
99+
}
100+
101+
MyObj* return_pointer_by_func (MyObj* a) { // expected-warning {{param should be marked [[clang::lifetimebound]]}}.
102+
return return_pointer_object(a); // expected-note {{param returned here}}
103+
}
104+
} // namespace correct_order_inference
105+
106+
namespace incorrect_order_inference_view {
107+
View return_view_callee(View a);
108+
109+
// FIXME: No lifetime annotation suggestion when functions are not present in the callee-before-caller pattern
110+
View return_view_caller(View a) {
111+
return return_view_callee(a);
112+
}
113+
114+
View return_view_callee(View a) { // expected-warning {{param should be marked [[clang::lifetimebound]]}}.
115+
return a; // expected-note {{param returned here}}
116+
}
117+
} // namespace incorrect_order_inference_view
118+
119+
namespace incorrect_order_inference_object {
120+
MyObj* return_object_callee(MyObj* a);
121+
122+
// FIXME: No lifetime annotation suggestion warning when functions are not present in the callee-before-caller pattern
123+
MyObj* return_object_caller(MyObj* a) {
124+
return return_object_callee(a);
125+
}
126+
127+
MyObj* return_object_callee(MyObj* a) { // expected-warning {{param should be marked [[clang::lifetimebound]]}}.
128+
return a; // expected-note {{param returned here}}
129+
}
130+
} // namespace incorrect_order_inference_object
131+
132+
namespace simple_annotation_inference {
133+
View inference_callee_return_identity(View a) { // expected-warning {{param should be marked [[clang::lifetimebound]]}}.
134+
return a; // expected-note {{param returned here}}
135+
}
136+
137+
View inference_caller_forwards_callee(View a) { // expected-warning {{param should be marked [[clang::lifetimebound]]}}.
138+
return inference_callee_return_identity(a); // expected-note {{param returned here}}
139+
}
140+
141+
View inference_top_level_return_stack_view() {
142+
MyObj local_stack;
143+
return inference_caller_forwards_callee(local_stack); // expected-warning {{address of stack memory is returned later}}
144+
// expected-note@-1 {{returned here}}
145+
}
146+
} // namespace simple_annotation_inference
147+
148+
namespace inference_in_order_with_redecls {
149+
View inference_callee_return_identity(View a);
150+
View inference_callee_return_identity(View a) { // expected-warning {{param should be marked [[clang::lifetimebound]]}}.
151+
return a; // expected-note {{param returned here}}
152+
}
153+
154+
View inference_caller_forwards_callee(View a);
155+
View inference_caller_forwards_callee(View a) { // expected-warning {{param should be marked [[clang::lifetimebound]]}}.
156+
return inference_callee_return_identity(a); // expected-note {{param returned here}}
157+
}
158+
159+
View inference_top_level_return_stack_view() {
160+
MyObj local_stack;
161+
return inference_caller_forwards_callee(local_stack); // expected-warning {{address of stack memory is returned later}}
162+
// expected-note@-1 {{returned here}}
163+
}
164+
} // namespace inference_in_order_with_redecls
165+
166+
namespace inference_with_templates {
167+
template<typename T>
168+
T* template_identity(T* a) { // expected-warning {{param should be marked [[clang::lifetimebound]]}}.
169+
return a; // expected-note {{param returned here}}
170+
}
171+
172+
template<typename T>
173+
T* template_caller(T* a) {
174+
return template_identity(a); // expected-note {{in instantiation of function template specialization 'inference_with_templates::template_identity<MyObj>' requested here}}
175+
}
176+
177+
// FIXME: Fails to detect UAR as template instantiations are deferred to the end of the Translation Unit.
178+
MyObj* test_template_inference_with_stack() {
179+
MyObj local_stack;
180+
return template_caller(&local_stack); // expected-note {{in instantiation of function template specialization 'inference_with_templates::template_caller<MyObj>' requested here}}
181+
}
182+
} // namespace inference_with_templates
183+
92184
//===----------------------------------------------------------------------===//
93185
// Negative Test Cases
94186
//===----------------------------------------------------------------------===//

0 commit comments

Comments
 (0)