[analyzer] Add sink after construction of temporary with no-return destructor.

The analyzer's CFG currently doesn't have nodes for calls to temporary
destructors. This causes the analyzer to explore infeasible paths in which
a no-return destructor would have stopped exploration and so results in false
positives when no-return destructors are used to implement assertions.

To mitigate these false positives, this patch stops generates a sink after
evaluating a constructor on a temporary object that has a no-return destructor.
This results in a loss of coverage because the time at which the destructor is
called may be after the time of construction (especially for lifetime-extended
temporaries).

This addresses PR15599.

rdar://problem/29131566

llvm-svn: 290140
This commit is contained in:
Devin Coughlin 2016-12-19 22:23:22 +00:00
parent d9430944f4
commit 5b1ee2fad9
2 changed files with 50 additions and 0 deletions

View File

@ -346,6 +346,30 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE,
defaultEvalCall(Bldr, *I, *Call);
}
// If the CFG was contructed without elements for temporary destructors
// and the just-called constructor created a temporary object then
// stop exploration if the temporary object has a noreturn constructor.
// This can lose coverage because the destructor, if it were present
// in the CFG, would be called at the end of the full expression or
// later (for life-time extended temporaries) -- but avoids infeasible
// paths when no-return temporary destructors are used for assertions.
const AnalysisDeclContext *ADC = LCtx->getAnalysisDeclContext();
if (!ADC->getCFGBuildOptions().AddTemporaryDtors) {
const MemRegion *Target = Call->getCXXThisVal().getAsRegion();
if (Target && isa<CXXTempObjectRegion>(Target) &&
Call->getDecl()->getParent()->isAnyDestructorNoReturn()) {
for (ExplodedNode *N : DstEvaluated) {
Bldr.generateSink(CE, N, N->getState());
}
// There is no need to run the PostCall and PostStmtchecker
// callbacks because we just generated sinks on all nodes in th
// frontier.
return;
}
}
ExplodedNodeSet DstPostCall;
getCheckerManager().runCheckersForPostCall(DstPostCall, DstEvaluated,
*Call, *this);

View File

@ -413,6 +413,32 @@ namespace destructors {
value ? DefaultParam(42) : DefaultParam(42);
}
}
#else // !TEMPORARY_DTORS
// Test for fallback logic that conservatively stops exploration after
// executing a temporary constructor for a class with a no-return destructor
// when temporary destructors are not enabled in the CFG.
struct CtorWithNoReturnDtor {
CtorWithNoReturnDtor() = default;
~CtorWithNoReturnDtor() __attribute__((noreturn));
};
void testDefaultContructorWithNoReturnDtor() {
CtorWithNoReturnDtor();
clang_analyzer_warnIfReached(); // no-warning
}
void testLifeExtensionWithNoReturnDtor() {
const CtorWithNoReturnDtor &c = CtorWithNoReturnDtor();
// This represents an (expected) loss of coverage, since the destructor
// of the lifetime-exended temporary is executed at at the end of
// scope.
clang_analyzer_warnIfReached(); // no-warning
}
#endif // TEMPORARY_DTORS
}