Skip to content

Commit

Permalink
[analyzer] operator new: Use the correct region for the constructor.
Browse files Browse the repository at this point in the history
The -analyzer-config c++-allocator-inlining experimental option allows the
analyzer to reason about C++ operator new() similarly to how it reasons about
regular functions. In this mode, operator new() is correctly called before the
construction of an object, with the help of a special CFG element.

However, the subsequent construction of the object was still not performed into
the region of memory returned by operator new(). The patch fixes it.

Passing the value from operator new() to the constructor and then to the
new-expression itself was tricky because operator new() has no call site of its
own in the AST. The new expression itself is not a good call site because it
has an incorrect type (operator new() returns 'void *', while the new expression
is a pointer to the allocated object type). Additionally, lifetime of the new
expression in the environment makes it unsuitable for passing the value.
For that reason, an additional program state trait is introduced to keep track
of the return value.

Finally this patch relaxes restrictions on the memory region class that are
required for inlining the constructor. This change affects the old mode as well
(c++-allocator-inlining=false) and seems safe because these restrictions were
an overkill compared to the actual problems observed.

Differential Revision: https://reviews.llvm.org/D40560
rdar://problem/12180598

llvm-svn: 322774
  • Loading branch information
haoNoQ committed Jan 17, 2018
1 parent a79b062 commit 5579630
Show file tree
Hide file tree
Showing 9 changed files with 395 additions and 49 deletions.
28 changes: 26 additions & 2 deletions clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
Expand Up @@ -642,8 +642,8 @@ class ExprEngine : public SubEngine {
const CXXConstructExpr *findDirectConstructorForCurrentCFGElement();

/// For a CXXConstructExpr, walk forward in the current CFG block to find the
/// CFGElement for the DeclStmt or CXXInitCtorInitializer for which is
/// directly constructed by this constructor. Returns None if the current
/// CFGElement for the DeclStmt or CXXInitCtorInitializer or CXXNewExpr which
/// is directly constructed by this constructor. Returns None if the current
/// constructor expression did not directly construct into an existing
/// region.
Optional<CFGElement> findElementDirectlyInitializedByCurrentConstructor();
Expand All @@ -655,6 +655,30 @@ class ExprEngine : public SubEngine {
/// if not.
const MemRegion *getRegionForConstructedObject(const CXXConstructExpr *CE,
ExplodedNode *Pred);

/// Store the region returned by operator new() so that the constructor
/// that follows it knew what location to initialize. The value should be
/// cleared once the respective CXXNewExpr CFGStmt element is processed.
static ProgramStateRef
setCXXNewAllocatorValue(ProgramStateRef State, const CXXNewExpr *CNE,
const LocationContext *CallerLC, SVal V);

/// Retrieve the location returned by the current operator new().
static SVal
getCXXNewAllocatorValue(ProgramStateRef State, const CXXNewExpr *CNE,
const LocationContext *CallerLC);

/// Clear the location returned by the respective operator new(). This needs
/// to be done as soon as CXXNewExpr CFG block is evaluated.
static ProgramStateRef
clearCXXNewAllocatorValue(ProgramStateRef State, const CXXNewExpr *CNE,
const LocationContext *CallerLC);

/// Check if all allocator values are clear for the given context range
/// (including FromLC, not including ToLC). This is useful for assertions.
static bool areCXXNewAllocatorValuesClear(ProgramStateRef State,
const LocationContext *FromLC,
const LocationContext *ToLC);
};

/// Traits for storing the call processing policy inside GDM.
Expand Down
58 changes: 58 additions & 0 deletions clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
Expand Up @@ -63,6 +63,20 @@ typedef std::pair<const CXXBindTemporaryExpr *, const StackFrameContext *>
REGISTER_TRAIT_WITH_PROGRAMSTATE(InitializedTemporariesSet,
llvm::ImmutableSet<CXXBindTemporaryContext>)

typedef llvm::ImmutableMap<std::pair<const CXXNewExpr *,
const LocationContext *>, SVal>
CXXNewAllocatorValuesTy;

// Keeps track of return values of various operator new() calls between
// evaluation of the inlined operator new(), through the constructor call,
// to the actual evaluation of the CXXNewExpr.
// TODO: Refactor the key for this trait into a LocationContext sub-class,
// which would be put on the stack of location contexts before operator new()
// is evaluated, and removed from the stack when the whole CXXNewExpr
// is fully evaluated.
// Probably do something similar to the previous trait as well.
REGISTER_TRAIT_WITH_PROGRAMSTATE(CXXNewAllocatorValues, CXXNewAllocatorValuesTy)

//===----------------------------------------------------------------------===//
// Engine construction and deletion.
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -308,6 +322,43 @@ ExprEngine::createTemporaryRegionIfNeeded(ProgramStateRef State,
return State;
}

ProgramStateRef
ExprEngine::setCXXNewAllocatorValue(ProgramStateRef State,
const CXXNewExpr *CNE,
const LocationContext *CallerLC, SVal V) {
assert(!State->get<CXXNewAllocatorValues>(std::make_pair(CNE, CallerLC)) &&
"Allocator value already set!");
return State->set<CXXNewAllocatorValues>(std::make_pair(CNE, CallerLC), V);
}

SVal ExprEngine::getCXXNewAllocatorValue(ProgramStateRef State,
const CXXNewExpr *CNE,
const LocationContext *CallerLC) {
return *State->get<CXXNewAllocatorValues>(std::make_pair(CNE, CallerLC));
}

ProgramStateRef
ExprEngine::clearCXXNewAllocatorValue(ProgramStateRef State,
const CXXNewExpr *CNE,
const LocationContext *CallerLC) {
return State->remove<CXXNewAllocatorValues>(std::make_pair(CNE, CallerLC));
}

bool ExprEngine::areCXXNewAllocatorValuesClear(ProgramStateRef State,
const LocationContext *FromLC,
const LocationContext *ToLC) {
const LocationContext *LC = FromLC;
do {
for (auto I : State->get<CXXNewAllocatorValues>())
if (I.first.second == LC)
return false;

LC = LC->getParent();
assert(LC && "ToLC must be a parent of FromLC!");
} while (LC != ToLC);
return true;
}

//===----------------------------------------------------------------------===//
// Top-level transfer function logic (Dispatcher).
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -429,6 +480,13 @@ void ExprEngine::removeDead(ExplodedNode *Pred, ExplodedNodeSet &Out,
const StackFrameContext *SFC = LC ? LC->getCurrentStackFrame() : nullptr;
SymbolReaper SymReaper(SFC, ReferenceStmt, SymMgr, getStoreManager());

for (auto I : CleanedState->get<CXXNewAllocatorValues>()) {
if (SymbolRef Sym = I.second.getAsSymbol())
SymReaper.markLive(Sym);
if (const MemRegion *MR = I.second.getAsRegion())
SymReaper.markElementIndicesLive(MR);
}

getCheckerManager().runCheckersForLiveSymbols(CleanedState, SymReaper);

// Create a state in which dead bindings are removed from the environment
Expand Down
91 changes: 64 additions & 27 deletions clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
Expand Up @@ -115,14 +115,25 @@ ExprEngine::getRegionForConstructedObject(const CXXConstructExpr *CE,

if (auto Elem = findElementDirectlyInitializedByCurrentConstructor()) {
if (Optional<CFGStmt> StmtElem = Elem->getAs<CFGStmt>()) {
auto *DS = cast<DeclStmt>(StmtElem->getStmt());
if (const auto *Var = dyn_cast<VarDecl>(DS->getSingleDecl())) {
if (Var->getInit() && Var->getInit()->IgnoreImplicit() == CE) {
SVal LValue = State->getLValue(Var, LCtx);
QualType Ty = Var->getType();
LValue = makeZeroElementRegion(State, LValue, Ty);
return LValue.getAsRegion();
if (const CXXNewExpr *CNE = dyn_cast<CXXNewExpr>(StmtElem->getStmt())) {
if (AMgr.getAnalyzerOptions().mayInlineCXXAllocator()) {
// TODO: Detect when the allocator returns a null pointer.
// Constructor shall not be called in this case.
if (const MemRegion *MR =
getCXXNewAllocatorValue(State, CNE, LCtx).getAsRegion())
return MR;
}
} else if (auto *DS = dyn_cast<DeclStmt>(StmtElem->getStmt())) {
if (const auto *Var = dyn_cast<VarDecl>(DS->getSingleDecl())) {
if (Var->getInit() && Var->getInit()->IgnoreImplicit() == CE) {
SVal LValue = State->getLValue(Var, LCtx);
QualType Ty = Var->getType();
LValue = makeZeroElementRegion(State, LValue, Ty);
return LValue.getAsRegion();
}
}
} else {
llvm_unreachable("Unexpected directly initialized element!");
}
} else if (Optional<CFGInitializer> InitElem = Elem->getAs<CFGInitializer>()) {
const CXXCtorInitializer *Init = InitElem->getInitializer();
Expand Down Expand Up @@ -166,6 +177,9 @@ static bool canHaveDirectConstructor(CFGElement Elem){
if (isa<DeclStmt>(StmtElem->getStmt())) {
return true;
}
if (isa<CXXNewExpr>(StmtElem->getStmt())) {
return true;
}
}

if (Elem.getKind() == CFGElement::Initializer) {
Expand Down Expand Up @@ -455,12 +469,23 @@ void ExprEngine::VisitCXXNewAllocatorCall(const CXXNewExpr *CNE,
getCheckerManager().runCheckersForPreCall(DstPreCall, Pred,
*Call, *this);

ExplodedNodeSet DstInvalidated;
StmtNodeBuilder Bldr(DstPreCall, DstInvalidated, *currBldrCtx);
for (ExplodedNodeSet::iterator I = DstPreCall.begin(), E = DstPreCall.end();
I != E; ++I)
defaultEvalCall(Bldr, *I, *Call);
getCheckerManager().runCheckersForPostCall(Dst, DstInvalidated,
ExplodedNodeSet DstPostCall;
StmtNodeBuilder CallBldr(DstPreCall, DstPostCall, *currBldrCtx);
for (auto I : DstPreCall)
defaultEvalCall(CallBldr, I, *Call);

// Store return value of operator new() for future use, until the actual
// CXXNewExpr gets processed.
ExplodedNodeSet DstPostValue;
StmtNodeBuilder ValueBldr(DstPostCall, DstPostValue, *currBldrCtx);
for (auto I : DstPostCall) {
ProgramStateRef State = I->getState();
ValueBldr.generateNode(
CNE, I,
setCXXNewAllocatorValue(State, CNE, LCtx, State->getSVal(CNE, LCtx)));
}

getCheckerManager().runCheckersForPostCall(Dst, DstPostValue,
*Call, *this);
}

Expand All @@ -474,7 +499,7 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred,

unsigned blockCount = currBldrCtx->blockCount();
const LocationContext *LCtx = Pred->getLocationContext();
DefinedOrUnknownSVal symVal = UnknownVal();
SVal symVal = UnknownVal();
FunctionDecl *FD = CNE->getOperatorNew();

bool IsStandardGlobalOpNewFunction = false;
Expand All @@ -490,26 +515,37 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred,
IsStandardGlobalOpNewFunction = (FD->getNumParams() == 1);
}

ProgramStateRef State = Pred->getState();

// Retrieve the stored operator new() return value.
if (AMgr.getAnalyzerOptions().mayInlineCXXAllocator()) {
symVal = getCXXNewAllocatorValue(State, CNE, LCtx);
State = clearCXXNewAllocatorValue(State, CNE, LCtx);
}

// We assume all standard global 'operator new' functions allocate memory in
// heap. We realize this is an approximation that might not correctly model
// a custom global allocator.
if (IsStandardGlobalOpNewFunction)
symVal = svalBuilder.getConjuredHeapSymbolVal(CNE, LCtx, blockCount);
else
symVal = svalBuilder.conjureSymbolVal(nullptr, CNE, LCtx, CNE->getType(),
blockCount);
if (symVal.isUnknown()) {
if (IsStandardGlobalOpNewFunction)
symVal = svalBuilder.getConjuredHeapSymbolVal(CNE, LCtx, blockCount);
else
symVal = svalBuilder.conjureSymbolVal(nullptr, CNE, LCtx, CNE->getType(),
blockCount);
}

ProgramStateRef State = Pred->getState();
CallEventManager &CEMgr = getStateManager().getCallEventManager();
CallEventRef<CXXAllocatorCall> Call =
CEMgr.getCXXAllocatorCall(CNE, State, LCtx);

// Invalidate placement args.
// FIXME: Once we figure out how we want allocators to work,
// we should be using the usual pre-/(default-)eval-/post-call checks here.
State = Call->invalidateRegions(blockCount);
if (!State)
return;
if (!AMgr.getAnalyzerOptions().mayInlineCXXAllocator()) {
// Invalidate placement args.
// FIXME: Once we figure out how we want allocators to work,
// we should be using the usual pre-/(default-)eval-/post-call checks here.
State = Call->invalidateRegions(blockCount);
if (!State)
return;
}

// If this allocation function is not declared as non-throwing, failures
// /must/ be signalled by exceptions, and thus the return value will never be
Expand All @@ -522,7 +558,8 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred,
QualType Ty = FD->getType();
if (const FunctionProtoType *ProtoType = Ty->getAs<FunctionProtoType>())
if (!ProtoType->isNothrow(getContext()))
State = State->assume(symVal, true);
if (auto dSymVal = symVal.getAs<DefinedOrUnknownSVal>())
State = State->assume(*dSymVal, true);
}

StmtNodeBuilder Bldr(Pred, Dst, *currBldrCtx);
Expand Down
49 changes: 37 additions & 12 deletions clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
Expand Up @@ -276,6 +276,14 @@ void ExprEngine::processCallExit(ExplodedNode *CEBNode) {

state = state->BindExpr(CCE, callerCtx, ThisV);
}

if (const CXXNewExpr *CNE = dyn_cast<CXXNewExpr>(CE)) {
// We are currently evaluating a CXXNewAllocator CFGElement. It takes a
// while to reach the actual CXXNewExpr element from here, so keep the
// region for later use.
state = setCXXNewAllocatorValue(state, CNE, calleeCtx->getParent(),
state->getSVal(CE, callerCtx));
}
}

// Step 3: BindedRetNode -> CleanedNodes
Expand Down Expand Up @@ -315,6 +323,10 @@ void ExprEngine::processCallExit(ExplodedNode *CEBNode) {
CallExitEnd Loc(calleeCtx, callerCtx);
bool isNew;
ProgramStateRef CEEState = (*I == CEBNode) ? state : (*I)->getState();

// See if we have any stale C++ allocator values.
assert(areCXXNewAllocatorValuesClear(CEEState, calleeCtx, callerCtx));

ExplodedNode *CEENode = G.getNode(Loc, CEEState, false, &isNew);
CEENode->addPredecessor(*I, G);
if (!isNew)
Expand Down Expand Up @@ -596,21 +608,34 @@ static CallInlinePolicy mayInlineCallKind(const CallEvent &Call,

const CXXConstructorCall &Ctor = cast<CXXConstructorCall>(Call);

const CXXConstructExpr *CtorExpr = Ctor.getOriginExpr();

// FIXME: ParentMap is slow and ugly. The callee should provide the
// necessary context. Ideally as part of the call event, or maybe as part of
// location context.
const Stmt *ParentExpr = CurLC->getParentMap().getParent(CtorExpr);

if (ParentExpr && isa<CXXNewExpr>(ParentExpr) &&
!Opts.mayInlineCXXAllocator())
return CIP_DisallowedOnce;

// FIXME: We don't handle constructors or destructors for arrays properly.
// Even once we do, we still need to be careful about implicitly-generated
// initializers for array fields in default move/copy constructors.
// We still allow construction into ElementRegion targets when they don't
// represent array elements.
const MemRegion *Target = Ctor.getCXXThisVal().getAsRegion();
if (Target && isa<ElementRegion>(Target))
return CIP_DisallowedOnce;

// FIXME: This is a hack. We don't use the correct region for a new
// expression, so if we inline the constructor its result will just be
// thrown away. This short-term hack is tracked in <rdar://problem/12180598>
// and the longer-term possible fix is discussed in PR12014.
const CXXConstructExpr *CtorExpr = Ctor.getOriginExpr();
if (const Stmt *Parent = CurLC->getParentMap().getParent(CtorExpr))
if (isa<CXXNewExpr>(Parent))
return CIP_DisallowedOnce;
if (Target && isa<ElementRegion>(Target)) {
if (ParentExpr)
if (const CXXNewExpr *NewExpr = dyn_cast<CXXNewExpr>(ParentExpr))
if (NewExpr->isArray())
return CIP_DisallowedOnce;

if (const TypedValueRegion *TR = dyn_cast<TypedValueRegion>(
cast<SubRegion>(Target)->getSuperRegion()))
if (TR->getValueType()->isArrayType())
return CIP_DisallowedOnce;
}

// Inlining constructors requires including initializers in the CFG.
const AnalysisDeclContext *ADC = CallerSFC->getAnalysisDeclContext();
Expand All @@ -629,7 +654,7 @@ static CallInlinePolicy mayInlineCallKind(const CallEvent &Call,
// 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<DeclRegion>(Target))
if (!Target || isa<CXXTempObjectRegion>(Target))
return CIP_DisallowedOnce;

break;
Expand Down
13 changes: 5 additions & 8 deletions clang/test/Analysis/inline.cpp
Expand Up @@ -315,17 +315,13 @@ namespace OperatorNew {
int value;

IntWrapper(int input) : value(input) {
// We don't want this constructor to be inlined unless we can actually
// use the proper region for operator new.
// See PR12014 and <rdar://problem/12180598>.
clang_analyzer_checkInlined(false); // no-warning
clang_analyzer_checkInlined(true); // expected-warning{{TRUE}}
}
};

void test() {
IntWrapper *obj = new IntWrapper(42);
// should be TRUE
clang_analyzer_eval(obj->value == 42); // expected-warning{{UNKNOWN}}
clang_analyzer_eval(obj->value == 42); // expected-warning{{TRUE}}
delete obj;
}

Expand All @@ -335,8 +331,9 @@ namespace OperatorNew {

clang_analyzer_eval(alias == obj); // expected-warning{{TRUE}}

// should be TRUE
clang_analyzer_eval(obj->value == 42); // expected-warning{{UNKNOWN}}
clang_analyzer_eval(obj->value == 42); // expected-warning{{TRUE}}
// Because malloc() was never free()d:
// expected-warning@-2{{Potential leak of memory pointed to by 'alias'}}
}
}

Expand Down
14 changes: 14 additions & 0 deletions clang/test/Analysis/new-ctor-conservative.cpp
@@ -0,0 +1,14 @@
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -analyzer-config c++-allocator-inlining=true -std=c++11 -verify %s

void clang_analyzer_eval(bool);

struct S {
int x;
S() : x(1) {}
~S() {}
};

void checkConstructorInlining() {
S *s = new S;
clang_analyzer_eval(s->x == 1); // expected-warning{{TRUE}}
}

0 comments on commit 5579630

Please sign in to comment.