From 1ccc43d50ef7cc9e2acbe002b0360e7b46a781c3 Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Wed, 25 Sep 2013 16:06:17 +0000 Subject: [PATCH] [analyzer] Handle destructors for the argument to C++ 'delete'. Now that the CFG includes nodes for the destructors in a delete-expression, process them in the analyzer using the same common destructor interface currently used for local, member, and base destructors. Also, check for when the value is known to be null, in which case no destructor is actually run. This does not yet handle destructors for deleted /arrays/, which may need more CFG work. It also causes a slight regression in the location of double delete warnings; the double delete is detected at the destructor call, which is implicit, and so is reported on the first access within the destructor instead of at the 'delete' statement. This will be fixed soon. Patch by Karthik Bhat! llvm-svn: 191381 --- clang/include/clang/Analysis/CFG.h | 2 +- .../StaticAnalyzer/Checkers/MallocChecker.cpp | 3 +- clang/lib/StaticAnalyzer/Core/CallEvent.cpp | 2 + clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 25 ++- .../lib/StaticAnalyzer/Core/ExprEngineCXX.cpp | 4 +- .../test/Analysis/NewDelete-checker-test.cpp | 25 +++ clang/test/Analysis/new.cpp | 145 ++++++++++++++++++ 7 files changed, 202 insertions(+), 4 deletions(-) diff --git a/clang/include/clang/Analysis/CFG.h b/clang/include/clang/Analysis/CFG.h index 2a6013560014..14b7ab842675 100644 --- a/clang/include/clang/Analysis/CFG.h +++ b/clang/include/clang/Analysis/CFG.h @@ -200,7 +200,7 @@ public: } // Get Delete expression which triggered the destructor call. - const CXXDeleteExpr *getDeleteExpr() { + const CXXDeleteExpr *getDeleteExpr() const { return static_cast(Data2.getPointer()); } diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index 1fa15d09cbc5..c7aa0fb150cb 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -1803,7 +1803,8 @@ bool MallocChecker::isReleased(SymbolRef Sym, CheckerContext &C) const { bool MallocChecker::checkUseAfterFree(SymbolRef Sym, CheckerContext &C, const Stmt *S) const { - if (isReleased(Sym, C)) { + // FIXME: Handle destructor called from delete more precisely. + if (isReleased(Sym, C) && S) { ReportUseAfterFree(C, S->getSourceRange(), Sym); return true; } diff --git a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp index 556f93b7cfb2..ce66363413fc 100644 --- a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp +++ b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -964,6 +964,8 @@ CallEventManager::getCaller(const StackFrameContext *CalleeCtx, const Stmt *Trigger; if (Optional AutoDtor = E.getAs()) Trigger = AutoDtor->getTriggerStmt(); + else if (Optional DeleteDtor = E.getAs()) + Trigger = cast(DeleteDtor->getDeleteExpr()); else Trigger = Dtor->getBody(); diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index 91acd55bc7fb..411e2f6aea3b 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -569,7 +569,30 @@ void ExprEngine::ProcessAutomaticObjDtor(const CFGAutomaticObjDtor Dtor, void ExprEngine::ProcessDeleteDtor(const CFGDeleteDtor Dtor, ExplodedNode *Pred, ExplodedNodeSet &Dst) { - //TODO: Handle DeleteDtor + ProgramStateRef State = Pred->getState(); + const LocationContext *LCtx = Pred->getLocationContext(); + const CXXDeleteExpr *DE = Dtor.getDeleteExpr(); + const Stmt *Arg = DE->getArgument(); + SVal ArgVal = State->getSVal(Arg, LCtx); + + // If the argument to delete is known to be a null value, + // don't run destructor. + if (State->isNull(ArgVal).isConstrainedTrue()) { + QualType DTy = DE->getDestroyedType(); + QualType BTy = getContext().getBaseElementType(DTy); + const CXXRecordDecl *RD = BTy->getAsCXXRecordDecl(); + const CXXDestructorDecl *Dtor = RD->getDestructor(); + + PostImplicitCall PP(Dtor, DE->getLocStart(), LCtx); + NodeBuilder Bldr(Pred, Dst, *currBldrCtx); + Bldr.generateNode(PP, Pred->getState(), Pred); + return; + } + + VisitCXXDestructor(DE->getDestroyedType(), + ArgVal.getAsRegion(), + DE, /*IsBase=*/ false, + Pred, Dst); } void ExprEngine::ProcessBaseDtor(const CFGBaseDtor D, diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp index b128bee1429c..eba4f94d80e6 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp @@ -296,7 +296,9 @@ void ExprEngine::VisitCXXDestructor(QualType ObjectType, // FIXME: We need to run the same destructor on every element of the array. // This workaround will just run the first destructor (which will still // invalidate the entire array). - SVal DestVal = loc::MemRegionVal(Dest); + SVal DestVal = UnknownVal(); + if (Dest) + DestVal = loc::MemRegionVal(Dest); DestVal = makeZeroElementRegion(State, DestVal, ObjectType); Dest = DestVal.getAsRegion(); diff --git a/clang/test/Analysis/NewDelete-checker-test.cpp b/clang/test/Analysis/NewDelete-checker-test.cpp index 442b8fbef09a..cc9725128bc8 100644 --- a/clang/test/Analysis/NewDelete-checker-test.cpp +++ b/clang/test/Analysis/NewDelete-checker-test.cpp @@ -333,3 +333,28 @@ namespace reference_count { } } +// Test double delete +class DerefClass{ +public: + int *x; + DerefClass() {} + ~DerefClass() {*x = 1;} //expected-warning {{Use of memory after it is freed}} +}; + +void testDoubleDeleteClassInstance() { + DerefClass *foo = new DerefClass(); + delete foo; + delete foo; // FIXME: We should ideally report warning here instead of inside the destructor. +} + +class EmptyClass{ +public: + EmptyClass() {} + ~EmptyClass() {} +}; + +void testDoubleDeleteEmptyClass() { + EmptyClass *foo = new EmptyClass(); + delete foo; + delete foo; //expected-warning {{Attempt to free released memory}} +} diff --git a/clang/test/Analysis/new.cpp b/clang/test/Analysis/new.cpp index 27cbb0816b2f..105a973ccc92 100644 --- a/clang/test/Analysis/new.cpp +++ b/clang/test/Analysis/new.cpp @@ -206,3 +206,148 @@ int testNoInitializationPlacement() { } return 1; } + +// Test modelling destructor call on call to delete +class IntPair{ +public: + int x; + int y; + IntPair() {}; + ~IntPair() {x = x/y;}; //expected-warning {{Division by zero}} +}; + +void testCallToDestructor() { + IntPair *b = new IntPair(); + b->x = 1; + b->y = 0; + delete b; // This results in divide by zero in destructor +} + +// Test Deleting a value that's passed as an argument. +class DerefClass{ +public: + int *x; + DerefClass() {}; + ~DerefClass() {*x = 1;}; //expected-warning {{Dereference of null pointer (loaded from field 'x')}} +}; + +void testDestCall(DerefClass *arg) { + delete arg; +} + +void test_delete_dtor_Arg() { + DerefClass *pair = new DerefClass(); + pair->x = 0; + testDestCall(pair); +} + +//Deleting the address of a local variable, null pointer +void abort(void) __attribute__((noreturn)); + +class NoReturnDtor { +public: + NoReturnDtor() {} + ~NoReturnDtor() {abort();} +}; + +void test_delete_dtor_LocalVar() { + NoReturnDtor test; + delete &test; // no warn or crash +} + +class DerivedNoReturn:public NoReturnDtor { +public: + DerivedNoReturn() {}; + ~DerivedNoReturn() {}; +}; + +void testNullDtorDerived() { + DerivedNoReturn *p = new DerivedNoReturn(); + delete p; // Calls the base destructor which aborts, checked below + clang_analyzer_eval(true); // no warn +} + +//Deleting a non class pointer should not crash/warn +void test_var_delete() { + int *v = new int; + delete v; // no crash/warn + clang_analyzer_eval(true); // expected-warning{{TRUE}} +} + +void testDeleteNull() { + NoReturnDtor *foo = 0; + delete foo; // should not call destructor, checked below + clang_analyzer_eval(true); // expected-warning{{TRUE}} +} + +void testNullAssigneddtor() { + NoReturnDtor *p = 0; + NoReturnDtor *s = p; + delete s; // should not call destructor, checked below + clang_analyzer_eval(true); // expected-warning{{TRUE}} +} + +void deleteArg(NoReturnDtor *test) { + delete test; +} + +void testNulldtorArg() { + NoReturnDtor *p = 0; + deleteArg(p); + clang_analyzer_eval(true); // expected-warning{{TRUE}} +} + +void testDeleteUnknown(NoReturnDtor *foo) { + delete foo; // should assume non-null and call noreturn destructor + clang_analyzer_eval(true); // no-warning +} + +void testArrayNull() { + NoReturnDtor *fooArray = 0; + delete[] fooArray; // should not call destructor, checked below + clang_analyzer_eval(true); // expected-warning{{TRUE}} +} + +void testArrayDestr() { + NoReturnDtor *p = new NoReturnDtor[2]; + delete[] p; // Calls the base destructor which aborts, checked below + //TODO: clang_analyzer_eval should not be called + clang_analyzer_eval(true); // expected-warning{{TRUE}} +} + +// Invalidate Region even in case of default destructor +class InvalidateDestTest { +public: + int x; + int *y; + ~InvalidateDestTest(); +}; + +int test_member_invalidation() { + + //test invalidation of member variable + InvalidateDestTest *test = new InvalidateDestTest(); + test->x = 5; + int *k = &(test->x); + clang_analyzer_eval(*k == 5); // expected-warning{{TRUE}} + delete test; + clang_analyzer_eval(*k == 5); // expected-warning{{UNKNOWN}} + + //test invalidation of member pointer + int localVar = 5; + test = new InvalidateDestTest(); + test->y = &localVar; + delete test; + clang_analyzer_eval(localVar == 5); // expected-warning{{UNKNOWN}} + + // Test aray elements are invalidated. + int Var1 = 5; + int Var2 = 5; + InvalidateDestTest *a = new InvalidateDestTest[2]; + a[0].y = &Var1; + a[1].y = &Var2; + delete[] a; + clang_analyzer_eval(Var1 == 5); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(Var2 == 5); // expected-warning{{UNKNOWN}} + return 0; +} -- GitLab