xazax.hun updated this revision to Diff 208439.
xazax.hun marked 12 inline comments as done.
xazax.hun added a comment.
- Address review comments.
CHANGES SINCE LAST ACTION
https://reviews.llvm.org/D64256/new/
https://reviews.llvm.org/D64256
Files:
clang/include/clang/Basic/DiagnosticSemaKinds.td
clang/lib/Sema/SemaInit.cpp
clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
Index: clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
===================================================================
--- /dev/null
+++ clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
@@ -0,0 +1,108 @@
+// RUN: %clang_cc1 -fsyntax-only -Wdangling -Wdangling-field -Wreturn-stack-address -verify %s
+struct [[gsl::Owner(int)]] MyOwner {
+ MyOwner();
+ int &operator*();
+};
+
+struct OwnerWithConv;
+
+// Conversion operator and constructor conversion will result in two
+// different shaped ASTs. Thus we have two owner types in the tests.
+struct [[gsl::Pointer(int)]] MyPointer {
+ MyPointer(int *p = nullptr);
+ MyPointer(const MyOwner &);
+ int &operator*();
+ OwnerWithConv toOwner();
+};
+
+struct [[gsl::Owner(int)]] OwnerWithConv {
+ OwnerWithConv();
+ operator MyPointer();
+ int &operator*();
+ MyPointer releaseAsMyPointer();
+ int *releaseAsRawPointer();
+ int *c_str() const;
+};
+
+void danglingHeapObject() {
+ new MyPointer(OwnerWithConv{}); // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}}
+ new MyPointer(MyOwner{}); // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}}
+}
+
+void intentionalFalseNegative() {
+ int i;
+ MyPointer p{&i};
+ // In this case we do not have enough information in a statement local
+ // analysis to detect the problem.
+ new MyPointer(MyPointer{p});
+}
+
+MyPointer ownershipTransferToMyPointer() {
+ OwnerWithConv t;
+ return t.releaseAsMyPointer(); // ok
+}
+
+int *ownershipTransferToRawPointer() {
+ OwnerWithConv t;
+ return t.releaseAsRawPointer(); // ok
+}
+
+int *danglingRawPtrFromLocal() {
+ OwnerWithConv t;
+ return t.c_str(); // TODO
+}
+
+int *danglingRawPtrFromTemp() {
+ MyPointer p;
+ return p.toOwner().c_str(); // TODO
+}
+
+struct Y {
+ int a[4];
+};
+
+void dangligGslPtrFromTemporary() {
+ MyPointer p = Y{}.a; // expected-warning {{temporary whose address is used as value of local variable 'p' will be destroyed at the end of the full-expression}}
+ (void)p;
+}
+
+struct DanglingGslPtrField {
+ MyPointer p; // expected-note 3{{pointer member declared here}}
+ DanglingGslPtrField(int i) : p(&i) {} // expected-warning {{initializing pointer member 'p' with the stack address of parameter 'i'}}
+ DanglingGslPtrField() : p(OwnerWithConv{}) {} // expected-warning {{initializing pointer member 'p' to point to a temporary object whose lifetime is shorter than the lifetime of the constructed object}}
+ DanglingGslPtrField(double) : p(MyOwner{}) {} // expected-warning {{initializing pointer member 'p' to point to a temporary object whose lifetime is shorter than the lifetime of the constructed object}}
+};
+
+MyPointer danglingGslPtrFromLocal() {
+ int j;
+ return &j; // expected-warning {{address of stack memory associated with local variable 'j' returned}}
+}
+
+MyPointer daglingGslPtrFromLocalOwner() {
+ OwnerWithConv t;
+ return t; // TODO
+}
+
+MyPointer danglingGslPtrFromTemporary() {
+ return OwnerWithConv{}; // expected-warning {{returning address of local temporary object}}
+}
+
+MyPointer global;
+
+void initLocalGslPtrWithTempOwner() {
+ MyPointer p = MyOwner{}; // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}}
+ p = MyOwner{}; // TODO ?
+ global = MyOwner{}; // TODO ?
+ p = OwnerWithConv{}; // TODO ?
+ global = OwnerWithConv{}; // TODO ?
+}
+
+struct IntVector {
+ int *begin();
+ int *end();
+};
+
+void modelIterators() {
+ int *it = IntVector{}.begin(); // TODO ?
+ (void)it;
+}
Index: clang/lib/Sema/SemaInit.cpp
===================================================================
--- clang/lib/Sema/SemaInit.cpp
+++ clang/lib/Sema/SemaInit.cpp
@@ -6507,6 +6507,8 @@
VarInit,
LValToRVal,
LifetimeBoundCall,
+ GslPointerInit,
+ GslTempPointer
} Kind;
Expr *E;
const Decl *D = nullptr;
@@ -6543,6 +6545,13 @@
});
}
+static bool
+pathInitializeLifetimePointer(llvm::ArrayRef<IndirectLocalPathEntry> Path) {
+ return Path.size() > 0 && llvm::all_of(Path, [=](IndirectLocalPathEntry E) {
+ return E.Kind == IndirectLocalPathEntry::GslPointerInit;
+ });
+}
+
static void visitLocalsRetainedByInitializer(IndirectLocalPath &Path,
Expr *Init, LocalVisitor Visit,
bool RevisitSubinits);
@@ -6551,6 +6560,47 @@
Expr *Init, ReferenceKind RK,
LocalVisitor Visit);
+template<typename T>
+static bool recordHasAttr(QualType Type) {
+ if (auto *RD = Type->getAsCXXRecordDecl())
+ return RD->getCanonicalDecl()->hasAttr<T>();
+ return false;
+}
+
+static void handleGslAnnotatedTypes(IndirectLocalPath &Path, Expr *Call,
+ LocalVisitor Visit) {
+ auto VisitPointerArg = [&](const Decl *D, Expr *Arg) {
+ Path.push_back({IndirectLocalPathEntry::GslPointerInit, Arg, D});
+ if (Arg->isGLValue())
+ visitLocalsRetainedByReferenceBinding(Path, Arg, RK_ReferenceBinding,
+ Visit);
+ else
+ visitLocalsRetainedByInitializer(Path, Arg, Visit, true);
+ Path.pop_back();
+ };
+
+ if (auto *MCE = dyn_cast<CXXMemberCallExpr>(Call)) {
+ const FunctionDecl *Callee = MCE->getDirectCallee();
+ if (auto *Conv = dyn_cast_or_null<CXXConversionDecl>(Callee))
+ if (recordHasAttr<PointerAttr>(Conv->getConversionType()))
+ VisitPointerArg(Callee, MCE->getImplicitObjectArgument());
+ return;
+ }
+
+ if (auto *CCE = dyn_cast<CXXConstructExpr>(Call)) {
+ const auto* Ctor = CCE->getConstructor();
+ const CXXRecordDecl *RD = Ctor->getParent()->getCanonicalDecl();
+ if (RD->hasAttr<OwnerAttr>() && isa<CXXTemporaryObjectExpr>(Call)) {
+ Path.push_back({IndirectLocalPathEntry::GslTempPointer, Call, RD});
+ Visit(Path, Call, RK_ReferenceBinding);
+ Path.pop_back();
+ } else {
+ if (RD->hasAttr<PointerAttr>() && CCE->getNumArgs() > 0)
+ VisitPointerArg(Ctor->getParamDecl(0), CCE->getArgs()[0]);
+ }
+ }
+}
+
static bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
const TypeSourceInfo *TSI = FD->getTypeSourceInfo();
if (!TSI)
@@ -6672,8 +6722,10 @@
true);
}
- if (isa<CallExpr>(Init))
+ if (isa<CallExpr>(Init)) {
+ handleGslAnnotatedTypes(Path, Init, Visit);
return visitLifetimeBoundArguments(Path, Init, Visit);
+ }
switch (Init->getStmtClass()) {
case Stmt::DeclRefExprClass: {
@@ -6896,8 +6948,10 @@
}
}
- if (isa<CallExpr>(Init) || isa<CXXConstructExpr>(Init))
+ if (isa<CallExpr>(Init) || isa<CXXConstructExpr>(Init)) {
+ handleGslAnnotatedTypes(Path, Init, Visit);
return visitLifetimeBoundArguments(Path, Init, Visit);
+ }
switch (Init->getStmtClass()) {
case Stmt::UnaryOperatorClass: {
@@ -6979,6 +7033,8 @@
case IndirectLocalPathEntry::AddressOf:
case IndirectLocalPathEntry::LValToRVal:
case IndirectLocalPathEntry::LifetimeBoundCall:
+ case IndirectLocalPathEntry::GslPointerInit:
+ case IndirectLocalPathEntry::GslTempPointer:
// These exist primarily to mark the path as not permitting or
// supporting lifetime extension.
break;
@@ -7007,12 +7063,32 @@
SourceRange DiagRange = nextPathEntryRange(Path, 0, L);
SourceLocation DiagLoc = DiagRange.getBegin();
+ // Skipping a chain of initializing gsl::Pointer annotated objects.
+ // We are looking only for the final value to find out if it was
+ // a temporary owner or the address of a local variable/param.
+ if (pathInitializeLifetimePointer(Path))
+ return true;
+
+ bool IsLifetimePtrInitWithTempOwner = false;
+ if (!Path.empty() &&
+ Path.back().Kind == IndirectLocalPathEntry::GslTempPointer) {
+ auto Prefix = llvm::makeArrayRef(Path).drop_back();
+ if (pathInitializeLifetimePointer(Prefix))
+ IsLifetimePtrInitWithTempOwner = true;
+ else
+ return false;
+ }
+
switch (LK) {
case LK_FullExpression:
llvm_unreachable("already handled this");
case LK_Extended: {
auto *MTE = dyn_cast<MaterializeTemporaryExpr>(L);
+ if (IsLifetimePtrInitWithTempOwner) {
+ Diag(DiagLoc, diag::warn_dangling_lifetime_pointer) << DiagRange;
+ return false;
+ }
if (!MTE) {
// The initialized entity has lifetime beyond the full-expression,
// and the local entity does too, so don't warn.
@@ -7048,10 +7124,10 @@
if (pathContainsInit(Path))
return false;
+ const ValueDecl *ExtendingDecl = ExtendingEntity->getDecl();
Diag(DiagLoc, diag::warn_dangling_variable)
- << RK << !Entity.getParent()
- << ExtendingEntity->getDecl()->isImplicit()
- << ExtendingEntity->getDecl() << Init->isGLValue() << DiagRange;
+ << RK << !Entity.getParent() << ExtendingDecl->isImplicit()
+ << ExtendingDecl << Init->isGLValue() << DiagRange;
}
break;
}
@@ -7093,17 +7169,26 @@
if (pathContainsInit(Path))
return false;
+ auto *Member = ExtendingEntity ? ExtendingEntity->getDecl() : nullptr;
auto *DRE = dyn_cast<DeclRefExpr>(L);
auto *VD = DRE ? dyn_cast<VarDecl>(DRE->getDecl()) : nullptr;
if (!VD) {
+ if (IsLifetimePtrInitWithTempOwner && Member) {
+ Diag(DiagLoc, diag::warn_dangling_lifetime_pointer_member)
+ << Member << DiagRange;
+ Diag(Member->getLocation(),
+ diag::note_ref_or_ptr_member_declared_here)
+ << true;
+ return false;
+ }
// A member was initialized to a local block.
// FIXME: Warn on this.
return false;
}
- if (auto *Member =
- ExtendingEntity ? ExtendingEntity->getDecl() : nullptr) {
- bool IsPointer = Member->getType()->isAnyPointerType();
+ if (Member) {
+ bool IsPointer = Member->getType()->isAnyPointerType() ||
+ recordHasAttr<PointerAttr>(Member->getType());
Diag(DiagLoc, IsPointer ? diag::warn_init_ptr_member_to_parameter_addr
: diag::warn_bind_ref_member_to_parameter)
<< Member << VD << isa<ParmVarDecl>(VD) << DiagRange;
@@ -7122,6 +7207,8 @@
: diag::warn_new_dangling_initializer_list)
<< !Entity.getParent() << DiagRange;
} else {
+ if (IsLifetimePtrInitWithTempOwner)
+ Diag(DiagLoc, diag::warn_dangling_lifetime_pointer) << DiagRange;
// We can't determine if the allocation outlives the local declaration.
return false;
}
@@ -7163,7 +7250,9 @@
break;
case IndirectLocalPathEntry::LifetimeBoundCall:
- // FIXME: Consider adding a note for this.
+ case IndirectLocalPathEntry::GslPointerInit:
+ case IndirectLocalPathEntry::GslTempPointer:
+ // FIXME: Consider adding a note for these.
break;
case IndirectLocalPathEntry::DefaultInit: {
Index: clang/include/clang/Basic/DiagnosticSemaKinds.td
===================================================================
--- clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -8056,6 +8056,10 @@
"%select{binds to|is}2 a temporary object "
"whose lifetime is shorter than the lifetime of the constructed object">,
InGroup<DanglingField>;
+def warn_dangling_lifetime_pointer_member : Warning<
+ "initializing pointer member %0 to point to a temporary object "
+ "whose lifetime is shorter than the lifetime of the constructed object">,
+ InGroup<DanglingField>;
def note_lifetime_extending_member_declared_here : Note<
"%select{%select{reference|'std::initializer_list'}0 member|"
"member with %select{reference|'std::initializer_list'}0 subobject}1 "
@@ -8074,6 +8078,10 @@
"temporary bound to reference member of allocated object "
"will be destroyed at the end of the full-expression">,
InGroup<DanglingField>;
+def warn_dangling_lifetime_pointer : Warning<
+ "object backing the pointer "
+ "will be destroyed at the end of the full-expression">,
+ InGroup<Dangling>;
def warn_new_dangling_initializer_list : Warning<
"array backing "
"%select{initializer list subobject of the allocated object|"
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits