Skip to content

Commit 18775fc

Browse files
committedJun 9, 2018
[analyzer] Add dangling internal buffer check.
This check will mark raw pointers to C++ standard library container internal buffers 'released' when the objects themselves are destroyed. Such information can be used by MallocChecker to warn about use-after-free problems. In this first version, 'std::basic_string's are supported. Differential Revision: https://reviews.llvm.org/D47135 llvm-svn: 334348
1 parent 8aada65 commit 18775fc

File tree

6 files changed

+215
-2
lines changed

6 files changed

+215
-2
lines changed
 

‎clang/include/clang/StaticAnalyzer/Checkers/Checkers.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,11 @@ def VirtualCallChecker : Checker<"VirtualCall">,
300300

301301
let ParentPackage = CplusplusAlpha in {
302302

303+
def DanglingInternalBufferChecker : Checker<"DanglingInternalBuffer">,
304+
HelpText<"Check for internal raw pointers of C++ standard library containers "
305+
"used after deallocation">,
306+
DescFile<"DanglingInternalBufferChecker.cpp">;
307+
303308
def DeleteWithNonVirtualDtorChecker : Checker<"DeleteWithNonVirtualDtor">,
304309
HelpText<"Reports destructions of polymorphic objects with a non-virtual "
305310
"destructor in their base class">,
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//===--- AllocationState.h ------------------------------------- *- C++ -*-===//
2+
//
3+
// The LLVM Compiler Infrastructure
4+
//
5+
// This file is distributed under the University of Illinois Open Source
6+
// License. See LICENSE.TXT for details.
7+
//
8+
//===----------------------------------------------------------------------===//
9+
10+
#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_ALLOCATIONSTATE_H
11+
#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_ALLOCATIONSTATE_H
12+
13+
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
14+
15+
namespace clang {
16+
namespace ento {
17+
18+
namespace allocation_state {
19+
20+
ProgramStateRef markReleased(ProgramStateRef State, SymbolRef Sym,
21+
const Expr *Origin);
22+
23+
} // end namespace allocation_state
24+
25+
} // end namespace ento
26+
} // end namespace clang
27+
28+
#endif

‎clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ add_clang_library(clangStaticAnalyzerCheckers
2727
CloneChecker.cpp
2828
ConversionChecker.cpp
2929
CXXSelfAssignmentChecker.cpp
30+
DanglingInternalBufferChecker.cpp
3031
DeadStoresChecker.cpp
3132
DebugCheckers.cpp
3233
DeleteWithNonVirtualDtorChecker.cpp
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
//=== DanglingInternalBufferChecker.cpp ---------------------------*- C++ -*--//
2+
//
3+
// The LLVM Compiler Infrastructure
4+
//
5+
// This file is distributed under the University of Illinois Open Source
6+
// License. See LICENSE.TXT for details.
7+
//
8+
//===----------------------------------------------------------------------===//
9+
//
10+
// This file defines a check that marks a raw pointer to a C++ standard library
11+
// container's inner buffer released when the object is destroyed. This
12+
// information can be used by MallocChecker to detect use-after-free problems.
13+
//
14+
//===----------------------------------------------------------------------===//
15+
16+
#include "ClangSACheckers.h"
17+
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
18+
#include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h"
19+
#include "clang/StaticAnalyzer/Core/Checker.h"
20+
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
21+
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
22+
#include "AllocationState.h"
23+
24+
using namespace clang;
25+
using namespace ento;
26+
27+
namespace {
28+
29+
class DanglingInternalBufferChecker : public Checker<check::PostCall> {
30+
CallDescription CStrFn;
31+
32+
public:
33+
DanglingInternalBufferChecker() : CStrFn("c_str") {}
34+
35+
/// Record the connection between the symbol returned by c_str() and the
36+
/// corresponding string object region in the ProgramState. Mark the symbol
37+
/// released if the string object is destroyed.
38+
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
39+
};
40+
41+
} // end anonymous namespace
42+
43+
// FIXME: c_str() may be called on a string object many times, so it should
44+
// have a list of symbols associated with it.
45+
REGISTER_MAP_WITH_PROGRAMSTATE(RawPtrMap, const MemRegion *, SymbolRef)
46+
47+
void DanglingInternalBufferChecker::checkPostCall(const CallEvent &Call,
48+
CheckerContext &C) const {
49+
const auto *ICall = dyn_cast<CXXInstanceCall>(&Call);
50+
if (!ICall)
51+
return;
52+
53+
SVal Obj = ICall->getCXXThisVal();
54+
const auto *TypedR = dyn_cast_or_null<TypedValueRegion>(Obj.getAsRegion());
55+
if (!TypedR)
56+
return;
57+
58+
auto *TypeDecl = TypedR->getValueType()->getAsCXXRecordDecl();
59+
if (TypeDecl->getName() != "basic_string")
60+
return;
61+
62+
ProgramStateRef State = C.getState();
63+
64+
if (Call.isCalled(CStrFn)) {
65+
SVal RawPtr = Call.getReturnValue();
66+
if (!RawPtr.isUnknown()) {
67+
State = State->set<RawPtrMap>(TypedR, RawPtr.getAsSymbol());
68+
C.addTransition(State);
69+
}
70+
return;
71+
}
72+
73+
if (isa<CXXDestructorCall>(ICall)) {
74+
if (State->contains<RawPtrMap>(TypedR)) {
75+
const SymbolRef *StrBufferPtr = State->get<RawPtrMap>(TypedR);
76+
// FIXME: What if Origin is null?
77+
const Expr *Origin = Call.getOriginExpr();
78+
State = allocation_state::markReleased(State, *StrBufferPtr, Origin);
79+
C.addTransition(State);
80+
return;
81+
}
82+
}
83+
}
84+
85+
void ento::registerDanglingInternalBufferChecker(CheckerManager &Mgr) {
86+
registerNewDeleteChecker(Mgr);
87+
Mgr.registerChecker<DanglingInternalBufferChecker>();
88+
}

‎clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "llvm/ADT/STLExtras.h"
3131
#include "llvm/ADT/SmallString.h"
3232
#include "llvm/ADT/StringExtras.h"
33+
#include "AllocationState.h"
3334
#include <climits>
3435
#include <utility>
3536

@@ -45,7 +46,8 @@ enum AllocationFamily {
4546
AF_CXXNew,
4647
AF_CXXNewArray,
4748
AF_IfNameIndex,
48-
AF_Alloca
49+
AF_Alloca,
50+
AF_InternalBuffer
4951
};
5052

5153
class RefState {
@@ -1467,6 +1469,7 @@ void MallocChecker::printExpectedAllocName(raw_ostream &os, CheckerContext &C,
14671469
case AF_CXXNew: os << "'new'"; return;
14681470
case AF_CXXNewArray: os << "'new[]'"; return;
14691471
case AF_IfNameIndex: os << "'if_nameindex()'"; return;
1472+
case AF_InternalBuffer: os << "container-specific allocator"; return;
14701473
case AF_Alloca:
14711474
case AF_None: llvm_unreachable("not a deallocation expression");
14721475
}
@@ -1479,6 +1482,7 @@ void MallocChecker::printExpectedDeallocName(raw_ostream &os,
14791482
case AF_CXXNew: os << "'delete'"; return;
14801483
case AF_CXXNewArray: os << "'delete[]'"; return;
14811484
case AF_IfNameIndex: os << "'if_freenameindex()'"; return;
1485+
case AF_InternalBuffer: os << "container-specific deallocator"; return;
14821486
case AF_Alloca:
14831487
case AF_None: llvm_unreachable("suspicious argument");
14841488
}
@@ -1653,7 +1657,9 @@ MallocChecker::getCheckIfTracked(AllocationFamily Family,
16531657
return Optional<MallocChecker::CheckKind>();
16541658
}
16551659
case AF_CXXNew:
1656-
case AF_CXXNewArray: {
1660+
case AF_CXXNewArray:
1661+
// FIXME: Add new CheckKind for AF_InternalBuffer.
1662+
case AF_InternalBuffer: {
16571663
if (IsALeakCheck) {
16581664
if (ChecksEnabled[CK_NewDeleteLeaksChecker])
16591665
return CK_NewDeleteLeaksChecker;
@@ -2991,6 +2997,20 @@ void MallocChecker::printState(raw_ostream &Out, ProgramStateRef State,
29912997
}
29922998
}
29932999

3000+
namespace clang {
3001+
namespace ento {
3002+
namespace allocation_state {
3003+
3004+
ProgramStateRef
3005+
markReleased(ProgramStateRef State, SymbolRef Sym, const Expr *Origin) {
3006+
AllocationFamily Family = AF_InternalBuffer;
3007+
return State->set<RegionState>(Sym, RefState::getReleased(Family, Origin));
3008+
}
3009+
3010+
} // end namespace allocation_state
3011+
} // end namespace ento
3012+
} // end namespace clang
3013+
29943014
void ento::registerNewDeleteLeaksChecker(CheckerManager &mgr) {
29953015
registerCStringCheckerBasic(mgr);
29963016
MallocChecker *checker = mgr.registerChecker<MallocChecker>();
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//RUN: %clang_analyze_cc1 -analyzer-checker=alpha.cplusplus.DanglingInternalBuffer %s -analyzer-output=text -verify
2+
3+
namespace std {
4+
5+
template< typename CharT >
6+
class basic_string {
7+
public:
8+
~basic_string();
9+
const CharT *c_str();
10+
};
11+
12+
typedef basic_string<char> string;
13+
typedef basic_string<wchar_t> wstring;
14+
typedef basic_string<char16_t> u16string;
15+
typedef basic_string<char32_t> u32string;
16+
17+
} // end namespace std
18+
19+
void consume(const char *) {}
20+
void consume(const wchar_t *) {}
21+
void consume(const char16_t *) {}
22+
void consume(const char32_t *) {}
23+
24+
void deref_after_scope_char() {
25+
const char *c;
26+
{
27+
std::string s;
28+
c = s.c_str();
29+
}
30+
consume(c); // expected-warning {{Use of memory after it is freed}}
31+
// expected-note@-1 {{Use of memory after it is freed}}
32+
}
33+
34+
void deref_after_scope_wchar_t() {
35+
const wchar_t *w;
36+
{
37+
std::wstring ws;
38+
w = ws.c_str();
39+
}
40+
consume(w); // expected-warning {{Use of memory after it is freed}}
41+
// expected-note@-1 {{Use of memory after it is freed}}
42+
}
43+
44+
void deref_after_scope_char16_t() {
45+
const char16_t *c16;
46+
{
47+
std::u16string s16;
48+
c16 = s16.c_str();
49+
}
50+
consume(c16); // expected-warning {{Use of memory after it is freed}}
51+
// expected-note@-1 {{Use of memory after it is freed}}
52+
}
53+
54+
void deref_after_scope_char32_t() {
55+
const char32_t *c32;
56+
{
57+
std::u32string s32;
58+
c32 = s32.c_str();
59+
}
60+
consume(c32); // expected-warning {{Use of memory after it is freed}}
61+
// expected-note@-1 {{Use of memory after it is freed}}
62+
}
63+
64+
void deref_after_scope_ok() {
65+
const char *c;
66+
std::string s;
67+
{
68+
c = s.c_str();
69+
}
70+
consume(c); // no-warning
71+
}

0 commit comments

Comments
 (0)