Index: include/clang/Analysis/CFG.h =================================================================== --- include/clang/Analysis/CFG.h +++ include/clang/Analysis/CFG.h @@ -280,13 +280,15 @@ /// at the end of full expression for temporary object. class CFGTemporaryDtor : public CFGImplicitDtor { public: - CFGTemporaryDtor(CXXBindTemporaryExpr *expr) - : CFGImplicitDtor(TemporaryDtor, expr, nullptr) {} + CFGTemporaryDtor(CXXBindTemporaryExpr *expr, bool BindsParameter) + : CFGImplicitDtor(TemporaryDtor, expr, BindsParameter ? this : nullptr) {} const CXXBindTemporaryExpr *getBindTemporaryExpr() const { return static_cast(Data1.getPointer()); } + bool bindsParameter() const { return Data2.getPointer(); } + private: friend class CFGElement; CFGTemporaryDtor() {} @@ -676,8 +678,9 @@ Elements.push_back(CFGMemberDtor(FD), C); } - void appendTemporaryDtor(CXXBindTemporaryExpr *E, BumpVectorContext &C) { - Elements.push_back(CFGTemporaryDtor(E), C); + void appendTemporaryDtor(CXXBindTemporaryExpr *E, BumpVectorContext &C, + bool BindsParameter) { + Elements.push_back(CFGTemporaryDtor(E, BindsParameter), C); } void appendAutomaticObjDtor(VarDecl *VD, Stmt *S, BumpVectorContext &C) { Index: include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h =================================================================== --- include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h +++ include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h @@ -290,7 +290,8 @@ static inline bool isLocType(QualType T) { return T->isAnyPointerType() || T->isBlockPointerType() || - T->isReferenceType() || T->isNullPtrType(); + T->isReferenceType() || T->isNullPtrType() || + T->isRecordType(); } private: Index: lib/Analysis/CFG.cpp =================================================================== --- lib/Analysis/CFG.cpp +++ lib/Analysis/CFG.cpp @@ -484,7 +484,8 @@ CFGBlock *VisitBinaryOperatorForTemporaryDtors(BinaryOperator *E, TempDtorContext &Context); CFGBlock *VisitCXXBindTemporaryExprForTemporaryDtors( - CXXBindTemporaryExpr *E, bool BindToTemporary, TempDtorContext &Context); + CXXBindTemporaryExpr *E, bool BindToTemporary, TempDtorContext &Context, + bool BindsParameter = false); CFGBlock *VisitConditionalOperatorForTemporaryDtors( AbstractConditionalOperator *E, bool BindToTemporary, TempDtorContext &Context); @@ -505,6 +506,9 @@ return Visit(S, AddStmtChoice::AlwaysAdd); } CFGBlock *addInitializer(CXXCtorInitializer *I); + void addAutomaticObjDtorsForLifetimeExtendedTemporaries( + Stmt *E, bool LifetimeExtended = false); + void addAutomaticObjDtorsForVarDecl(VarDecl *VD, Stmt* S); void addAutomaticObjDtors(LocalScope::const_iterator B, LocalScope::const_iterator E, Stmt *S); void addImplicitDtorsForDestructor(const CXXDestructorDecl *DD); @@ -515,6 +519,9 @@ void addLocalScopeForStmt(Stmt *S); LocalScope* addLocalScopeForDeclStmt(DeclStmt *DS, LocalScope* Scope = nullptr); + bool needsLocalScopeForLifetimeExtendedTemporary(const Stmt* E); + bool needsLocalScopeForVarDecl(const VarDecl* VD); + bool needsLocalScopeForType(QualType QT); LocalScope* addLocalScopeForVarDecl(VarDecl *VD, LocalScope* Scope = nullptr); void addLocalScopeAndDtors(Stmt *S); @@ -540,8 +547,9 @@ void appendMemberDtor(CFGBlock *B, FieldDecl *FD) { B->appendMemberDtor(FD, cfg->getBumpVectorContext()); } - void appendTemporaryDtor(CFGBlock *B, CXXBindTemporaryExpr *E) { - B->appendTemporaryDtor(E, cfg->getBumpVectorContext()); + void appendTemporaryDtor(CFGBlock *B, CXXBindTemporaryExpr *E, + bool BindsParameter) { + B->appendTemporaryDtor(E, cfg->getBumpVectorContext(), BindsParameter); } void appendAutomaticObjDtor(CFGBlock *B, VarDecl *VD, Stmt *S) { B->appendAutomaticObjDtor(VD, S, cfg->getBumpVectorContext()); @@ -1101,52 +1109,87 @@ return Block; } -/// \brief Retrieve the type of the temporary object whose lifetime was -/// extended by a local reference with the given initializer. -static QualType getReferenceInitTemporaryType(ASTContext &Context, - const Expr *Init) { - while (true) { - // Skip parentheses. - Init = Init->IgnoreParens(); - - // Skip through cleanups. - if (const ExprWithCleanups *EWC = dyn_cast(Init)) { - Init = EWC->getSubExpr(); - continue; - } - - // Skip through the temporary-materialization expression. - if (const MaterializeTemporaryExpr *MTE - = dyn_cast(Init)) { - Init = MTE->GetTemporaryExpr(); - continue; +void CFGBuilder::addAutomaticObjDtorsForLifetimeExtendedTemporaries( + Stmt *E, bool LifetimeExtended) { + switch (E->getStmtClass()) { + // FIXME: What do we want to do for conditional operators and binary + // conditional operators? + case Stmt::MaterializeTemporaryExprClass: { + const MaterializeTemporaryExpr * MTE = cast(E); + if (MTE->getStorageDuration() != SD_FullExpression) { + SmallVector CommaLHSs; + SmallVector Adjustments; + assert(MTE->GetTemporaryExpr()); + const Expr *ExtendedE = + MTE->GetTemporaryExpr()->skipRValueSubobjectAdjustments(CommaLHSs, + Adjustments); + addAutomaticObjDtorsForLifetimeExtendedTemporaries( + const_cast(ExtendedE), /*LifetimeExtended=*/true); } - - // Skip derived-to-base and no-op casts. - if (const CastExpr *CE = dyn_cast(Init)) { - if ((CE->getCastKind() == CK_DerivedToBase || - CE->getCastKind() == CK_UncheckedDerivedToBase || - CE->getCastKind() == CK_NoOp) && - Init->getType()->isRecordType()) { - Init = CE->getSubExpr(); - continue; + } break; + case Stmt::CXXBindTemporaryExprClass: + // If the temporary is not lifetime extended, we'll handle it in + // VisitForTemporaryDtors instead. + if (LifetimeExtended) { + const CXXDestructorDecl *Dtor = + cast(E)->getTemporary()->getDestructor(); + if (Dtor->isNoReturn()) { + Block = createNoReturnBlock(); + } else { + autoCreateBlock(); } + + appendTemporaryDtor(Block, cast(E), false); } - - // Skip member accesses into rvalues. - if (const MemberExpr *ME = dyn_cast(Init)) { - if (!ME->isArrow() && ME->getBase()->isRValue()) { - Init = ME->getBase(); - continue; - } + break; + case Stmt::ConditionalOperatorClass: + case Stmt::BinaryConditionalOperatorClass: + // FIXME: Implement. If both the true and false branch yield lifetime + // extended temporaries, we need to add a branch that triggers on which + // temporary constructor was executed. + break; + default: + LifetimeExtended = false; + // Fallthrough. + case Stmt::ParenExprClass: + case Stmt::CXXFunctionalCastExprClass: + case Stmt::ImplicitCastExprClass: + case Stmt::ExprWithCleanupsClass: + case Stmt::CXXDefaultArgExprClass: + case Stmt::CXXDefaultInitExprClass: + for (Stmt *Child : E->children()) { + if (Child) + addAutomaticObjDtorsForLifetimeExtendedTemporaries(Child, + LifetimeExtended); } - + break; + case Stmt::BlockExprClass: + case Stmt::LambdaExprClass: break; } +} + +void CFGBuilder::addAutomaticObjDtorsForVarDecl(VarDecl *VD, + Stmt *S) { + if (!VD->getType()->isReferenceType() && + needsLocalScopeForType(VD->getType())) { + QualType QT = VD->getType(); + QT = Context->getBaseElementType(QT); - return Init->getType(); + const CXXDestructorDecl *Dtor = QT->getAsCXXRecordDecl()->getDestructor(); + if (Dtor->isNoReturn()) + Block = createNoReturnBlock(); + else + autoCreateBlock(); + + appendAutomaticObjDtor(Block, VD, S); + } + if (const Expr *Init = VD->getInit()) { + addAutomaticObjDtorsForLifetimeExtendedTemporaries( + const_cast(Init)); + } } - + /// addAutomaticObjDtors - Add to current block automatic objects destructors /// for objects in range of local scope positions. Use S as trigger statement /// for destructors. @@ -1170,22 +1213,7 @@ for (SmallVectorImpl::reverse_iterator I = Decls.rbegin(), E = Decls.rend(); I != E; ++I) { - // If this destructor is marked as a no-return destructor, we need to - // create a new block for the destructor which does not have as a successor - // anything built thus far: control won't flow out of this block. - QualType Ty = (*I)->getType(); - if (Ty->isReferenceType()) { - Ty = getReferenceInitTemporaryType(*Context, (*I)->getInit()); - } - Ty = Context->getBaseElementType(Ty); - - const CXXDestructorDecl *Dtor = Ty->getAsCXXRecordDecl()->getDestructor(); - if (Dtor->isNoReturn()) - Block = createNoReturnBlock(); - else - autoCreateBlock(); - - appendAutomaticObjDtor(Block, *I, S); + addAutomaticObjDtorsForVarDecl(*I, S); } } @@ -1283,6 +1311,58 @@ return Scope; } +bool CFGBuilder::needsLocalScopeForType(QualType QT) { + // Check for constant size array. Set type to array element type. + while (const ConstantArrayType *AT = Context->getAsConstantArrayType(QT)) { + if (AT->getSize() == 0) + return false; + QT = AT->getElementType(); + } + + // Check if type is a C++ class with non-trivial destructor. + if (const CXXRecordDecl *CD = QT->getAsCXXRecordDecl()) + return !CD->hasTrivialDestructor(); + return false; +} + +bool CFGBuilder::needsLocalScopeForLifetimeExtendedTemporary(const Stmt* E) { + // FIXME: Handle cases where the scope would be introduced by a nested + // lambda or block. + switch (E->getStmtClass()) { + case Stmt::MaterializeTemporaryExprClass: { + const MaterializeTemporaryExpr * MTE = cast(E); + if (MTE->getStorageDuration() != SD_FullExpression) { + SmallVector CommaLHSs; + SmallVector Adjustments; + assert(MTE->GetTemporaryExpr()); + const Expr *ExtendedE = + MTE->GetTemporaryExpr()->skipRValueSubobjectAdjustments(CommaLHSs, + Adjustments); + return needsLocalScopeForType(ExtendedE->getType()) || + needsLocalScopeForLifetimeExtendedTemporary(ExtendedE); + } + } break; + default: + for (const Stmt *Child : E->children()) { + if (Child && needsLocalScopeForLifetimeExtendedTemporary(Child)) + return true; + } + break; + } + return false; +} + +bool CFGBuilder::needsLocalScopeForVarDecl(const VarDecl* VD) { + if (!VD->getType()->isReferenceType() && + needsLocalScopeForType(VD->getType())) { + return true; + } + if (const Expr *Init = VD->getInit()) { + return needsLocalScopeForLifetimeExtendedTemporary(Init); + } + return false; +} + /// addLocalScopeForVarDecl - Add LocalScope for variable declaration. It will /// create add scope for automatic objects and temporary objects bound to /// const reference. Will reuse Scope if not NULL. @@ -1300,43 +1380,11 @@ default: return Scope; } - // Check for const references bound to temporary. Set type to pointee. - QualType QT = VD->getType(); - if (QT.getTypePtr()->isReferenceType()) { - // Attempt to determine whether this declaration lifetime-extends a - // temporary. - // - // FIXME: This is incorrect. Non-reference declarations can lifetime-extend - // temporaries, and a single declaration can extend multiple temporaries. - // We should look at the storage duration on each nested - // MaterializeTemporaryExpr instead. - const Expr *Init = VD->getInit(); - if (!Init) - return Scope; - if (const ExprWithCleanups *EWC = dyn_cast(Init)) - Init = EWC->getSubExpr(); - if (!isa(Init)) - return Scope; - - // Lifetime-extending a temporary. - QT = getReferenceInitTemporaryType(*Context, Init); - } - - // Check for constant size array. Set type to array element type. - while (const ConstantArrayType *AT = Context->getAsConstantArrayType(QT)) { - if (AT->getSize() == 0) - return Scope; - QT = AT->getElementType(); - } - - // Check if type is a C++ class with non-trivial destructor. - if (const CXXRecordDecl *CD = QT->getAsCXXRecordDecl()) - if (!CD->hasTrivialDestructor()) { - // Add the variable to scope - Scope = createOrReuseLocalScope(Scope); - Scope->addVar(VD); - ScopePos = Scope->begin(); - } + if (!needsLocalScopeForVarDecl(VD)) + return Scope; + Scope = createOrReuseLocalScope(Scope); + Scope->addVar(VD); + ScopePos = Scope->begin(); return Scope; } @@ -3614,6 +3662,22 @@ case Stmt::CXXDefaultInitExprClass: E = cast(E)->getExpr(); goto tryAgain; + + case Stmt::CallExprClass: { + CFGBlock *B = Block; + for (Expr *Arg : cast(E)->arguments()) { + if (!Arg) continue; + if (CXXBindTemporaryExpr *BTE = dyn_cast(Arg)) { + if (CFGBlock *R = VisitCXXBindTemporaryExprForTemporaryDtors( + BTE, false, Context, /*BindsParameter=*/true)) { + B = R; + } + } else if (CFGBlock *R = VisitForTemporaryDtors(Arg, false, Context)) { + B = R; + } + } + return B; + } } } @@ -3673,7 +3737,8 @@ } CFGBlock *CFGBuilder::VisitCXXBindTemporaryExprForTemporaryDtors( - CXXBindTemporaryExpr *E, bool BindToTemporary, TempDtorContext &Context) { + CXXBindTemporaryExpr *E, bool BindToTemporary, TempDtorContext &Context, + bool BindsParameter) { // First add destructors for temporaries in subexpression. CFGBlock *B = VisitForTemporaryDtors(E->getSubExpr(), false, Context); if (!BindToTemporary) { @@ -3700,7 +3765,7 @@ if (Context.needsTempDtorBranch()) { Context.setDecisionPoint(Succ, E); } - appendTemporaryDtor(Block, E); + appendTemporaryDtor(Block, E, BindsParameter); B = Block; } Index: lib/Analysis/LiveVariables.cpp =================================================================== --- lib/Analysis/LiveVariables.cpp +++ lib/Analysis/LiveVariables.cpp @@ -462,6 +462,16 @@ val.liveDecls = DSetFact.add(val.liveDecls, Dtor->getVarDecl()); continue; } + if (Optional Dtor = + elem.getAs()) { + //llvm::errs() << "keep it live...\n"; + //Dtor->getBindTemporaryExpr()->dump(); +// llvm::errs() << "\n"; + // Temporary objects need to survive until the destructor is called. + val.liveStmts = SSetFact.add(val.liveStmts, + Dtor->getBindTemporaryExpr()->getSubExpr()); + continue; + } if (!elem.getAs()) continue; Index: lib/StaticAnalyzer/Core/CallEvent.cpp =================================================================== --- lib/StaticAnalyzer/Core/CallEvent.cpp +++ lib/StaticAnalyzer/Core/CallEvent.cpp @@ -959,7 +959,6 @@ CFGElement E = (*B)[CalleeCtx->getIndex()]; assert(E.getAs() && "All other CFG elements should have exprs"); - assert(!E.getAs() && "We don't handle temporaries yet"); SValBuilder &SVB = State->getStateManager().getSValBuilder(); const CXXDestructorDecl *Dtor = cast(CalleeCtx->getDecl()); Index: lib/StaticAnalyzer/Core/CoreEngine.cpp =================================================================== --- lib/StaticAnalyzer/Core/CoreEngine.cpp +++ lib/StaticAnalyzer/Core/CoreEngine.cpp @@ -228,6 +228,8 @@ void CoreEngine::dispatchWorkItem(ExplodedNode* Pred, ProgramPoint Loc, const WorkListUnit& WU) { + //Pred->getState()->dump(); + //llvm::errs() << "\n"; // Dispatch on the location type. switch (Loc.getKind()) { case ProgramPoint::BlockEdgeKind: Index: lib/StaticAnalyzer/Core/ExprEngine.cpp =================================================================== --- lib/StaticAnalyzer/Core/ExprEngine.cpp +++ lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -191,6 +191,9 @@ return State; Result = Ex; } else { + // if (const MemRegion *MR = V.getAsRegion()) + // if (isa(MR->getBaseRegion())) + // return State; // We need to create a region no matter what. For sanity, make sure we don't // try to stuff a Loc into a non-pointer temporary region. assert(!V.getAs() || Loc::isLocType(Result->getType()) || @@ -422,6 +425,8 @@ void ExprEngine::ProcessStmt(const CFGStmt S, ExplodedNode *Pred) { + //S.getStmt()->dump(); + //llvm::errs() << "\n"; // Reclaim any unnecessary nodes in the ExplodedGraph. G.reclaimRecentlyAllocatedNodes(); @@ -686,9 +691,25 @@ assert(CleanDtorState.size() <= 1); ExplodedNode *CleanPred = CleanDtorState.empty() ? Pred : *CleanDtorState.begin(); + + const LocationContext *LCtx = CleanPred->getLocationContext(); + SVal Val = CleanPred->getState()->getSVal( + D.getBindTemporaryExpr()->getSubExpr(), LCtx->getCurrentStackFrame()); + const MemRegion *Region = Val.getAsRegion(); + //llvm::errs() << "huh?\n"; + //if (Region != nullptr) { + // llvm::errs() << "HEEEEEEEEEEEEEEEY!!!!!\n"; + // } + // If the class does not have any members, there will not be a region + // for it bound in the environment. + //if (Optional MRV = + // Val.getAs()) { + // Region = LCV->getRegion(); + //} + // FIXME: Inlining of temporary destructors is not supported yet anyway, so // we just put a NULL region for now. This will need to be changed later. - VisitCXXDestructor(varType, nullptr, D.getBindTemporaryExpr(), + VisitCXXDestructor(varType, Region, D.getBindTemporaryExpr(), /*IsBase=*/false, CleanPred, Dst); } Index: lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp =================================================================== --- lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp +++ lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp @@ -204,7 +204,17 @@ if (!MR) return false; - return isa(MR); + return false; + /*( + if (isa(MR) && !isa(E)) { + if (const CXXConstructExpr* CE = llvm::dyn_cast(E)) { + return true; + E->dump(); + llvm::errs() << "\n"; + } + } + return false; +*/ } /// The call exit is simulated with a sequence of nodes, which occur between @@ -637,12 +647,6 @@ if (!Opts.mayInlineCXXMemberFunction(CIMK_Destructors)) return CIP_DisallowedAlways; - // FIXME: This is a hack. We don't handle temporary destructors - // right now, so we shouldn't inline their constructors. - if (CtorExpr->getConstructionKind() == CXXConstructExpr::CK_Complete) - if (!Target || !isa(Target)) - return CIP_DisallowedOnce; - break; } case CE_CXXDestructor: { @@ -807,12 +811,15 @@ AnalysisDeclContextManager &ADCMgr = AMgr.getAnalysisDeclContextManager(); AnalysisDeclContext *CalleeADC = ADCMgr.getContext(D); - // Temporary object destructor processing is currently broken, so we never - // inline them. - // FIXME: Remove this once temp destructors are working. if (isa(Call)) { - if ((*currBldrCtx->getBlock())[currStmtIdx].getAs()) - return false; + if (Optional Dtor = + (*currBldrCtx->getBlock())[currStmtIdx].getAs()) { + // We must not inline temporary destructors for temporaries that are + // bound to parameters, as we currently don't support correctly + // invalidating our knowledge about them when they escape. + //if (Dtor->bindsParameter()) + // return false; + } } // The auto-synthesized bodies are essential to inline as they are Index: lib/StaticAnalyzer/Core/RegionStore.cpp =================================================================== --- lib/StaticAnalyzer/Core/RegionStore.cpp +++ lib/StaticAnalyzer/Core/RegionStore.cpp @@ -1900,8 +1900,9 @@ QualType Ty = TR->getValueType(); if (Ty->isArrayType()) return bindArray(B, TR, V); - if (Ty->isStructureOrClassType()) + if (Ty->isStructureOrClassType()) { return bindStruct(B, TR, V); + } if (Ty->isVectorType()) return bindVector(B, TR, V); if (Ty->isUnionType()) @@ -2111,8 +2112,9 @@ // Handle lazy compound values and symbolic values. if (Optional LCV = V.getAs()) { - if (Optional NewB = tryBindSmallStruct(B, R, RD, *LCV)) + if (Optional NewB = tryBindSmallStruct(B, R, RD, *LCV)) { return *NewB; + } return bindAggregate(B, R, V); } if (V.getAs()) Index: test/Analysis/auto-obj-dtors-cfg-output.cpp =================================================================== --- test/Analysis/auto-obj-dtors-cfg-output.cpp +++ test/Analysis/auto-obj-dtors-cfg-output.cpp @@ -42,7 +42,7 @@ // CHECK-NEXT: 8: [B1.7] (ImplicitCastExpr, NoOp, const class A) // CHECK-NEXT: 9: [B1.8] // CHECK: 10: const A &c = A(); -// CHECK: 11: [B1.10].~A() (Implicit destructor) +// CHECK: 11: ~A() // CHECK: 12: [B1.2].~A() (Implicit destructor) // CHECK-NEXT: Preds (1): B2 // CHECK-NEXT: Succs (1): B0 Index: test/Analysis/cfg.cpp =================================================================== --- test/Analysis/cfg.cpp +++ test/Analysis/cfg.cpp @@ -1,6 +1,8 @@ // RUN: %clang_cc1 -analyze -analyzer-checker=debug.DumpCFG -triple x86_64-apple-darwin12 -analyzer-config cfg-temporary-dtors=true -std=c++11 %s > %t 2>&1 // RUN: FileCheck --input-file=%t %s +#include "Inputs/system-header-simulator-cxx.h" + // CHECK-LABEL: void checkWrap(int i) // CHECK: ENTRY // CHECK-NEXT: Succs (1): B1 @@ -376,10 +378,9 @@ MyClass* obj = new (buffer) MyClass[5]; } - // CHECK-LABEL: void test_lifetime_extended_temporaries() // CHECK: [B1] -struct LifetimeExtend { LifetimeExtend(int); ~LifetimeExtend(); }; +struct LifetimeExtend { LifetimeExtend(int); ~LifetimeExtend(); int i; }; struct Aggregate { const LifetimeExtend a; const LifetimeExtend b; }; struct AggregateRef { const LifetimeExtend &a; const LifetimeExtend &b; }; void test_lifetime_extended_temporaries() { @@ -420,15 +421,36 @@ } // CHECK: LifetimeExtend(5) // CHECK-NEXT: : 5 - // FIXME: We want to emit the destructors of the lifetime - // extended variables here. + // CHECK-NEXT: ~LifetimeExtend() + // CHECK-NEXT: ~LifetimeExtend() // CHECK-NOT: ~LifetimeExtend() { AggregateRef a{LifetimeExtend(5), LifetimeExtend(5)}; 5; } - // FIXME: Add tests for lifetime extension via subobject - // references (LifetimeExtend().some_member). + // CHECK: LifetimeExtend(6) + // CHECK-NEXT: : 6 + // CHECK-NEXT: ~LifetimeExtend() + // CHECK-NOT: ~LifetimeExtend() + { + const int &i = LifetimeExtend(6).i; + 6; + } +} + + +// CHECK-LABEL: void test_lifetime_extended_temporaries_for_range() +void test_lifetime_extended_temporaries_for_range() { + // CHECK: [B1] + // CHECK-NEXT: ~const LifetimeExtend [1]() + // CHECK-NEXT: Preds (1): B2 + // CHECK: auto &&__range = { LifetimeExtend(7) }; + // CHECK-NEXT: ~LifetimeExtend() (Temporary object destructor) + { + for (LifetimeExtend e : {LifetimeExtend(7)}) { + 1; + } + } } Index: test/Analysis/temp-obj-dtors-cfg-output.cpp =================================================================== --- test/Analysis/temp-obj-dtors-cfg-output.cpp +++ test/Analysis/temp-obj-dtors-cfg-output.cpp @@ -982,7 +982,7 @@ // CHECK: 12: [B1.7]([B1.11]) // CHECK: 13: ~A() (Temporary object destructor) // CHECK: 14: int b; -// CHECK: 15: [B1.5].~A() (Implicit destructor) +// CHECK: 15: ~A() (Temporary object destructor) // CHECK: Preds (1): B2 // CHECK: Succs (1): B0 // CHECK: [B0 (EXIT)] @@ -1026,7 +1026,7 @@ // CHECK: 16: [B1.9]([B1.15]) // CHECK: 17: ~A() (Temporary object destructor) // CHECK: 18: int b; -// CHECK: 19: [B1.7].~A() (Implicit destructor) +// CHECK: 19: ~A() (Temporary object destructor) // CHECK: Preds (1): B2 // CHECK: Succs (1): B0 // CHECK: [B0 (EXIT)] Index: test/Analysis/temporaries.cpp =================================================================== --- test/Analysis/temporaries.cpp +++ test/Analysis/temporaries.cpp @@ -104,9 +104,7 @@ #if __cplusplus >= 201103L clang_analyzer_eval(((HasCtor){1, 42}).y == 42); // expected-warning{{TRUE}} - // FIXME: should be TRUE, but we don't inline the constructors of - // temporaries because we can't model their destructors yet. - clang_analyzer_eval(((HasCtorDtor){1, 42}).y == 42); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(((HasCtorDtor){1, 42}).y == 42); // expected-warning{{TRUE}} #endif } } @@ -347,6 +345,49 @@ } } + struct NoWarnDerefDtor { + NoWarnDerefDtor(int *p) : p(p) {} + ~NoWarnDerefDtor() { *p = 23; } // no warning + int *p; + }; + void testDtorInlining() { + int x; + (NoWarnDerefDtor(&x)); + clang_analyzer_eval(x == 23); // expected-warning{{TRUE}} + } + void use(NoWarnDerefDtor); + void testArgumentDtorInliningPreventedByValue() { + int x; + NoWarnDerefDtor p(nullptr); + use(p); + p.p = &x; + } + + struct WarnDerefDtor { + WarnDerefDtor(int *p) : p(p) {} + ~WarnDerefDtor() { + *p = 23; // expected-warning{{Dereference of null pointer}} + } + int *p; + }; + void useRef(WarnDerefDtor& p) { + p.p = nullptr; + } + void testArgumentDtorInliningWorksByReference() { + int x; + WarnDerefDtor p(&x); + useRef(p); + } + void useConstRef(const NoWarnDerefDtor& p) { + const_cast(p).p = nullptr; + } + void testArgumentDtorInliningWorksByConstReference() { + int x; + // FIXME: This should warn. We need to implement correct handling of + // temporaries bound to parameters first. + useConstRef(NoWarnDerefDtor(&x)); + } + void testIfAtEndOfLoop() { int y = 0; while (true) {