Author: Baranov Victor Date: 2026-01-26T18:33:20+03:00 New Revision: a78b8328aa093cd242da16ddead446240b99a769
URL: https://github.com/llvm/llvm-project/commit/a78b8328aa093cd242da16ddead446240b99a769 DIFF: https://github.com/llvm/llvm-project/commit/a78b8328aa093cd242da16ddead446240b99a769.diff LOG: [LifetimeSafety] Add report on misuse of clang::noescape (#177260) Closes https://github.com/llvm/llvm-project/issues/170417. Added: clang/test/Sema/warn-lifetime-safety-noescape.cpp Modified: clang/docs/ReleaseNotes.rst clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h clang/include/clang/Basic/DiagnosticGroups.td clang/include/clang/Basic/DiagnosticSemaKinds.td clang/lib/Analysis/LifetimeSafety/Checker.cpp clang/lib/Sema/AnalysisBasedWarnings.cpp Removed: ################################################################################ diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index d03211a200a29..f0d3d81f14e43 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -164,6 +164,24 @@ Improvements to Clang's diagnostics int* p(int *in) { return in; } ^~ +- Added ``-Wlifetime-safety-noescape`` to detect misuse of ``[[clang::noescape]]`` + annotation where the parameter escapes through return. For example: + + .. code-block:: c++ + + int* p(int *in [[clang::noescape]]) { return in; } + + Clang will warn: + + .. code-block:: c++ + + warning: parameter is marked [[clang::noescape]] but escapes + int* p(int *in [[clang::noescape]]) { return in; } + ^~~~~~~ + note: returned here + int* p(int *in [[clang::noescape]]) { return in; } + ^~ + Improvements to Clang's time-trace ---------------------------------- diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h index 25d97b4af1ed5..2ab60d918c8d1 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h @@ -71,6 +71,10 @@ class LifetimeSafetySemaHelper { const ParmVarDecl *ParmToAnnotate, const Expr *EscapeExpr) {} + // Reports misuse of [[clang::noescape]] when parameter escapes through return + virtual void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape, + const Expr *EscapeExpr) {} + // Suggests lifetime bound annotations for implicit this. virtual void suggestLifetimeboundToImplicitThis(SuggestionScope Scope, const CXXMethodDecl *MD, diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 34624dd3eed3a..488f3a94c4fb6 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -553,6 +553,12 @@ def LifetimeSafetySuggestions Lifetime annotation suggestions for function parameters that should be marked [[clang::lifetimebound]] based on lifetime analysis. }]; } +def LifetimeSafetyNoescape + : DiagGroup<"lifetime-safety-noescape"> { + code Documentation = [{ + Detects misuse of [[clang::noescape]] annotation where the parameter escapes (for example, through return). + }]; +} def DistributedObjectModifiers : DiagGroup<"distributed-object-modifiers">; def DllexportExplicitInstantiationDecl : DiagGroup<"dllexport-explicit-instantiation-decl">; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index afe37ab88c5c8..c786eb4486829 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10856,6 +10856,11 @@ def warn_lifetime_safety_cross_tu_this_suggestion def note_lifetime_safety_suggestion_returned_here : Note<"param returned here">; +def warn_lifetime_safety_noescape_escapes + : Warning<"parameter is marked [[clang::noescape]] but escapes">, + InGroup<LifetimeSafetyNoescape>, + DefaultIgnore; + // For non-floating point, expressions of the form x == x or x != x // should result in a warning, since these always evaluate to a constant. // Array comparisons have similar warnings diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index 91fbcc0a98650..c6368786f34fe 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -56,6 +56,7 @@ class LifetimeChecker { private: llvm::DenseMap<LoanID, PendingWarning> FinalWarningsMap; llvm::DenseMap<AnnotationTarget, const Expr *> AnnotationWarningsMap; + llvm::DenseMap<const ParmVarDecl *, const Expr *> NoescapeWarningsMap; const LoanPropagationAnalysis &LoanPropagation; const LiveOriginsAnalysis &LiveOrigins; const FactManager &FactMgr; @@ -77,6 +78,7 @@ class LifetimeChecker { checkAnnotations(OEF); issuePendingWarnings(); suggestAnnotations(); + reportNoescapeViolations(); // Annotation inference is currently guarded by a frontend flag. In the // future, this might be replaced by a design that diff erentiates between // explicit and inferred findings with separate warning groups. @@ -85,7 +87,8 @@ class LifetimeChecker { } /// Checks if an escaping origin holds a placeholder loan, indicating a - /// missing [[clang::lifetimebound]] annotation. + /// missing [[clang::lifetimebound]] annotation or a violation of + /// [[clang::noescape]]. void checkAnnotations(const OriginEscapesFact *OEF) { OriginID EscapedOID = OEF->getEscapedOriginID(); LoanSet EscapedLoans = LoanPropagation.getLoans(EscapedOID, OEF); @@ -93,6 +96,10 @@ class LifetimeChecker { const Loan *L = FactMgr.getLoanMgr().getLoan(LID); if (const auto *PL = dyn_cast<PlaceholderLoan>(L)) { if (const auto *PVD = PL->getParmVarDecl()) { + if (PVD->hasAttr<NoEscapeAttr>()) { + NoescapeWarningsMap.try_emplace(PVD, OEF->getEscapeExpr()); + continue; + } if (PVD->hasAttr<LifetimeBoundAttr>()) continue; AnnotationWarningsMap.try_emplace(PVD, OEF->getEscapeExpr()); @@ -229,6 +236,11 @@ class LifetimeChecker { } } + void reportNoescapeViolations() { + for (auto [PVD, EscapeExpr] : NoescapeWarningsMap) + SemaHelper->reportNoescapeViolation(PVD, EscapeExpr); + } + void inferAnnotations() { for (auto [Target, EscapeExpr] : AnnotationWarningsMap) { if (const auto *MD = Target.dyn_cast<const CXXMethodDecl *>()) { diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp index 39995992eb717..913962dc0c3e0 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -2940,6 +2940,17 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { << EscapeExpr->getSourceRange(); } + void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape, + const Expr *EscapeExpr) override { + S.Diag(ParmWithNoescape->getBeginLoc(), + diag::warn_lifetime_safety_noescape_escapes) + << ParmWithNoescape->getSourceRange(); + + S.Diag(EscapeExpr->getBeginLoc(), + diag::note_lifetime_safety_suggestion_returned_here) + << EscapeExpr->getSourceRange(); + } + void addLifetimeBoundToImplicitThis(const CXXMethodDecl *MD) override { S.addLifetimeBoundToImplicitThis(const_cast<CXXMethodDecl *>(MD)); } @@ -3088,6 +3099,8 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings( !Diags.isIgnored(diag::warn_lifetime_safety_return_stack_addr_permissive, D->getBeginLoc()) || !Diags.isIgnored(diag::warn_lifetime_safety_return_stack_addr_strict, + D->getBeginLoc()) || + !Diags.isIgnored(diag::warn_lifetime_safety_noescape_escapes, D->getBeginLoc()); bool EnableLifetimeSafetyAnalysis = S.getLangOpts().EnableLifetimeSafety && diff --git a/clang/test/Sema/warn-lifetime-safety-noescape.cpp b/clang/test/Sema/warn-lifetime-safety-noescape.cpp new file mode 100644 index 0000000000000..91edd2e33edf8 --- /dev/null +++ b/clang/test/Sema/warn-lifetime-safety-noescape.cpp @@ -0,0 +1,172 @@ +// RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -Wlifetime-safety-noescape -verify %s + +#include "Inputs/lifetime-analysis.h" + +struct [[gsl::Owner]] MyObj { + int id; + ~MyObj() {} // Non-trivial destructor +}; + +struct [[gsl::Pointer()]] View { + View(const MyObj&); // Borrows from MyObj + View(); + void use() const; +}; + +View return_noescape_directly(const MyObj& in [[clang::noescape]]) { // expected-warning {{parameter is marked [[clang::noescape]] but escapes}} + return in; // expected-note {{returned here}} +} + +View return_one_of_two( + const MyObj& a [[clang::noescape]], // expected-warning {{parameter is marked [[clang::noescape]] but escapes}} + const MyObj& b [[clang::noescape]], + bool cond) { + if (cond) + return a; // expected-note {{returned here}} + return View(); +} + +View return_both( + const MyObj& a [[clang::noescape]], // expected-warning {{parameter is marked [[clang::noescape]] but escapes}} + const MyObj& b [[clang::noescape]], // expected-warning {{parameter is marked [[clang::noescape]] but escapes}} + bool cond) { + if (cond) + return a; // expected-note {{returned here}} + return b; // expected-note {{returned here}} +} + +View mixed_noescape_lifetimebound( + const MyObj& a [[clang::noescape]], // expected-warning {{parameter is marked [[clang::noescape]] but escapes}} + const MyObj& b [[clang::lifetimebound]], + bool cond) { + if (cond) + return a; // expected-note {{returned here}} + return b; +} + +View mixed_only_noescape_escapes( + const MyObj& a [[clang::noescape]], + const MyObj& b [[clang::lifetimebound]]) { + (void)a; + return b; +} + +View multiple_reassign( + const MyObj& a [[clang::noescape]], + const MyObj& b [[clang::noescape]], // expected-warning {{parameter is marked [[clang::noescape]] but escapes}} + const MyObj& c [[clang::noescape]], // expected-warning {{parameter is marked [[clang::noescape]] but escapes}} + bool cond) { + View v = a; + if (cond) + v = b; + else + v = c; + return v; // expected-note 2 {{returned here}} +} + +int* return_noescape_pointer(int* p [[clang::noescape]]) { // expected-warning {{parameter is marked [[clang::noescape]] but escapes}} + return p; // expected-note {{returned here}} +} + +MyObj& return_noescape_reference(MyObj& r [[clang::noescape]]) { // expected-warning {{parameter is marked [[clang::noescape]] but escapes}} + return r; // expected-note {{returned here}} +} + +View return_via_local(const MyObj& in [[clang::noescape]]) { // expected-warning {{parameter is marked [[clang::noescape]] but escapes}} + View v = in; + return v; // expected-note {{returned here}} +} + +void use_locally(const MyObj& in [[clang::noescape]]) { + View v = in; + v.use(); +} + +View return_without_noescape(const MyObj& in) { + return in; +} + +View return_with_lifetimebound(const MyObj& in [[clang::lifetimebound]]) { + return in; +} + +void pointer_used_locally(MyObj* p [[clang::noescape]]) { + p->id = 42; +} + +// FIXME: diagnose diff erently when parameter has both '[[clang::noescape]]' and '[[clang::lifetimebound]]'. +View both_noescape_and_lifetimebound( + const MyObj& in [[clang::noescape]] [[clang::lifetimebound]]) { // expected-warning {{parameter is marked [[clang::noescape]] but escapes}} + return in; // expected-note {{returned here}} +} + +View identity_lifetimebound(View v [[clang::lifetimebound]]) { return v; } + +View escape_through_lifetimebound_call( + const MyObj& in [[clang::noescape]]) { // expected-warning {{parameter is marked [[clang::noescape]] but escapes}} + return identity_lifetimebound(in); // expected-note {{returned here}} +} + +View no_annotation_identity(View v) { return v; } + +View escape_through_unannotated_call(const MyObj& in [[clang::noescape]]) { // expected-warning {{parameter is marked [[clang::noescape]] but escapes}} + return no_annotation_identity(in); // expected-note {{returned here}} +} + +View global_view; + +// FIXME: Escaping through a global variable is not detected. +void escape_through_global_var(const MyObj& in [[clang::noescape]]) { + global_view = in; +} + +// FIXME: Escaping through a member variable is not detected. +struct ObjConsumer { + void escape_through_member(const MyObj& in [[clang::noescape]]) { + member_view = in; + } + + View member_view; +}; + +// FIXME: Escaping through another param is not detected. +void escape_through_param(const MyObj& in, std::vector<View> &v) { + v.push_back(in); +} + +View reassign_to_second( + const MyObj& a [[clang::noescape]], + const MyObj& b [[clang::noescape]]) { // expected-warning {{parameter is marked [[clang::noescape]] but escapes}} + View v = a; + v = b; + return v; // expected-note {{returned here}} +} + +struct Container { + MyObj data; + const MyObj& getRef() const [[clang::lifetimebound]] { return data; } +}; + +View access_noescape_field( + const Container& c [[clang::noescape]]) { // expected-warning {{parameter is marked [[clang::noescape]] but escapes}} + return c.data; // expected-note {{returned here}} +} + +View access_noescape_through_getter( + Container& c [[clang::noescape]]) { // expected-warning {{parameter is marked [[clang::noescape]] but escapes}} + return c.getRef(); // expected-note {{returned here}} +} + +MyObj* return_ptr_from_noescape_ref( + MyObj& r [[clang::noescape]]) { // expected-warning {{parameter is marked [[clang::noescape]] but escapes}} + return &r; // expected-note {{returned here}} +} + +MyObj& return_ref_from_noescape_ptr( + MyObj* p [[clang::noescape]]) { // expected-warning {{parameter is marked [[clang::noescape]] but escapes}} + return *p; // expected-note {{returned here}} +} + +int* return_spaced_brackets(int* p [ [clang::noescape] /*some comment*/ ]) { // expected-warning {{parameter is marked [[clang::noescape]] but escapes}} + return p; // expected-note {{returned here}} +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
