mirror of
https://github.com/intel/llvm.git
synced 2026-01-17 06:40:01 +08:00
[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.
This commit is contained in:
@@ -501,6 +501,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
|
||||
|
||||
@@ -1964,6 +1964,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<SetTrue, [], [CC1Option], "Enable">,
|
||||
NegFlag<SetFalse, [], [CC1Option], "Disable">,
|
||||
BothFlags<[], [CC1Option],
|
||||
" experimental lifetime safety inference for C++">>;
|
||||
|
||||
defm addrsig : BoolFOption<"addrsig",
|
||||
CodeGenOpts<"Addrsig">, DefaultFalse,
|
||||
PosFlag<SetTrue, [], [ClangOption, CC1Option], "Emit">,
|
||||
|
||||
@@ -55,13 +55,14 @@ private:
|
||||
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<PostOrderCFGView>())
|
||||
for (const Fact *F : FactMgr.getFacts(B))
|
||||
if (const auto *EF = F->getAs<ExpireFact>())
|
||||
@@ -70,6 +71,11 @@ public:
|
||||
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 @@ public:
|
||||
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<ParmVarDecl *>(ConstPVD);
|
||||
if (!PVD->hasAttr<LifetimeBoundAttr>())
|
||||
PVD->addAttr(
|
||||
LifetimeBoundAttr::CreateImplicit(AST, PVD->getLocation()));
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -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<typename T>
|
||||
T* template_identity(T* a) { // expected-warning {{param should be marked [[clang::lifetimebound]]}}.
|
||||
return a; // expected-note {{param returned here}}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T* template_caller(T* a) {
|
||||
return template_identity(a); // expected-note {{in instantiation of function template specialization 'inference_with_templates::template_identity<MyObj>' 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<MyObj>' requested here}}
|
||||
}
|
||||
} // namespace inference_with_templates
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Negative Test Cases
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
Reference in New Issue
Block a user