[CFG] NFC: Refactor ConstructionContext into a finite set of cases.

ConstructionContext is moved into a separate translation unit and is separated
into multiple classes. The "old" "raw" ConstructionContext is renamed into
ConstructionContextLayer - which corresponds to the idea of building the context
gradually layer-by-layer, but it isn't easy to use in the clients. Once
CXXConstructExpr is reached, layers that we've gathered so far are transformed
into the actual, "new-style" "flat" ConstructionContext, which is put into the
CFGConstructor element and has no layers whatsoever (until it actually needs
them, eg. aggregate initialization). The new-style ConstructionContext is
instead presented as a variety of sub-classes that enumerate different ways of
constructing an object in C++. There are 5 of these supported for now,
which is around a half of what needs to be supported.

The layer-by-layer buildup process is still a little bit weird, but it hides
all the weirdness in one place, that sounds like a good thing.

Differential Revision: https://reviews.llvm.org/D43533

llvm-svn: 326238
This commit is contained in:
Artem Dergachev 2018-02-27 20:03:35 +00:00
parent 301991080e
commit 4068481bdb
7 changed files with 496 additions and 205 deletions

View File

@ -38,6 +38,7 @@ namespace clang {
class ASTContext;
class BinaryOperator;
class CFG;
class ConstructionContext;
class CXXBaseSpecifier;
class CXXBindTemporaryExpr;
class CXXCtorInitializer;
@ -140,94 +141,6 @@ protected:
CFGStmt() = default;
};
// This is bulky data for CFGConstructor which would not fit into the
// CFGElement's room (pair of pointers). Contains the information
// necessary to express what memory is being initialized by
// the construction.
class ConstructionContext {
public:
typedef llvm::PointerUnion<Stmt *, CXXCtorInitializer *> TriggerTy;
private:
// The construction site - the statement that triggered the construction
// for one of its parts. For instance, stack variable declaration statement
// triggers construction of itself or its elements if it's an array,
// new-expression triggers construction of the newly allocated object(s).
TriggerTy Trigger;
// Sometimes a single trigger is not enough to describe the construction site.
// In this case we'd have a chain of "partial" construction contexts.
// Some examples:
// - A constructor within in an aggregate initializer list within a variable
// would have a construction context of the initializer list with the parent
// construction context of a variable.
// - A constructor for a temporary that needs to be both destroyed
// and materialized into an elidable copy constructor would have a
// construction context of a CXXBindTemporaryExpr with the parent
// construction context of a MaterializeTemproraryExpr.
// Not all of these are currently supported.
const ConstructionContext *Parent = nullptr;
ConstructionContext() = default;
ConstructionContext(TriggerTy Trigger, const ConstructionContext *Parent)
: Trigger(Trigger), Parent(Parent) {}
public:
static const ConstructionContext *
create(BumpVectorContext &C, TriggerTy Trigger,
const ConstructionContext *Parent = nullptr) {
ConstructionContext *CC = C.getAllocator().Allocate<ConstructionContext>();
return new (CC) ConstructionContext(Trigger, Parent);
}
bool isNull() const { return Trigger.isNull(); }
TriggerTy getTrigger() const { return Trigger; }
const ConstructionContext *getParent() const { return Parent; }
const Stmt *getTriggerStmt() const {
return Trigger.dyn_cast<Stmt *>();
}
const CXXCtorInitializer *getTriggerInit() const {
return Trigger.dyn_cast<CXXCtorInitializer *>();
}
const MaterializeTemporaryExpr *getMaterializedTemporary() const {
// TODO: Be more careful to ensure that there's only one MTE around.
for (const ConstructionContext *CC = this; CC; CC = CC->getParent()) {
if (const auto *MTE = dyn_cast_or_null<MaterializeTemporaryExpr>(
CC->getTriggerStmt())) {
return MTE;
}
}
return nullptr;
}
bool isSameAsPartialContext(const ConstructionContext *Other) const {
assert(Other);
return (Trigger == Other->Trigger);
}
// See if Other is a proper initial segment of this construction context
// in terms of the parent chain - i.e. a few first parents coincide and
// then the other context terminates but our context goes further - i.e.,
// we are providing the same context that the other context provides,
// and a bit more above that.
bool isStrictlyMoreSpecificThan(const ConstructionContext *Other) const {
const ConstructionContext *Self = this;
while (true) {
if (!Other)
return Self;
if (!Self || !Self->isSameAsPartialContext(Other))
return false;
Self = Self->getParent();
Other = Other->getParent();
}
llvm_unreachable("The above loop can only be terminated via return!");
}
};
/// CFGConstructor - Represents C++ constructor call. Maintains information
/// necessary to figure out what memory is being initialized by the
/// constructor expression. For now this is only used by the analyzer's CFG.
@ -235,7 +148,7 @@ class CFGConstructor : public CFGStmt {
public:
explicit CFGConstructor(CXXConstructExpr *CE, const ConstructionContext *C)
: CFGStmt(CE, Constructor) {
assert(!C->isNull());
assert(C);
Data2.setPointer(const_cast<ConstructionContext *>(C));
}
@ -247,22 +160,6 @@ public:
return cast<CXXConstructExpr>(getStmt())->getType();
}
ConstructionContext::TriggerTy getTrigger() const {
return getConstructionContext()->getTrigger();
}
const Stmt *getTriggerStmt() const {
return getConstructionContext()->getTriggerStmt();
}
const CXXCtorInitializer *getTriggerInit() const {
return getConstructionContext()->getTriggerInit();
}
const MaterializeTemporaryExpr *getMaterializedTemporary() const {
return getConstructionContext()->getMaterializedTemporary();
}
private:
friend class CFGElement;

View File

@ -0,0 +1,245 @@
//===- ConstructionContext.h - CFG constructor information ------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file defines the ConstructionContext class and its sub-classes,
// which represent various different ways of constructing C++ objects
// with the additional information the users may want to know about
// the constructor.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_ANALYSIS_CONSTRUCTIONCONTEXT_H
#define LLVM_CLANG_ANALYSIS_CONSTRUCTIONCONTEXT_H
#include "clang/Analysis/Support/BumpVector.h"
#include "clang/AST/ExprCXX.h"
namespace clang {
/// Construction context is a linked list of multiple layers. Layers are
/// created gradually while traversing the AST, and layers that represent
/// the outmost AST nodes are built first, while the node that immediately
/// contains the constructor would be built last and capture the previous
/// layers as its parents. Construction context captures the last layer
/// (which has links to the previous layers) and classifies the seemingly
/// arbitrary chain of layers into one of the possible ways of constructing
/// an object in C++ for user-friendly experience.
class ConstructionContextLayer {
public:
typedef llvm::PointerUnion<Stmt *, CXXCtorInitializer *> TriggerTy;
private:
/// The construction site - the statement that triggered the construction
/// for one of its parts. For instance, stack variable declaration statement
/// triggers construction of itself or its elements if it's an array,
/// new-expression triggers construction of the newly allocated object(s).
TriggerTy Trigger;
/// Sometimes a single trigger is not enough to describe the construction
/// site. In this case we'd have a chain of "partial" construction context
/// layers.
/// Some examples:
/// - A constructor within in an aggregate initializer list within a variable
/// would have a construction context of the initializer list with
/// the parent construction context of a variable.
/// - A constructor for a temporary that needs to be both destroyed
/// and materialized into an elidable copy constructor would have a
/// construction context of a CXXBindTemporaryExpr with the parent
/// construction context of a MaterializeTemproraryExpr.
/// Not all of these are currently supported.
const ConstructionContextLayer *Parent = nullptr;
ConstructionContextLayer(TriggerTy Trigger,
const ConstructionContextLayer *Parent)
: Trigger(Trigger), Parent(Parent) {}
public:
static const ConstructionContextLayer *
create(BumpVectorContext &C, TriggerTy Trigger,
const ConstructionContextLayer *Parent = nullptr);
const ConstructionContextLayer *getParent() const { return Parent; }
bool isLast() const { return !Parent; }
const Stmt *getTriggerStmt() const {
return Trigger.dyn_cast<Stmt *>();
}
const CXXCtorInitializer *getTriggerInit() const {
return Trigger.dyn_cast<CXXCtorInitializer *>();
}
/// Returns true if these layers are equal as individual layers, even if
/// their parents are different.
bool isSameLayer(const ConstructionContextLayer *Other) const {
assert(Other);
return (Trigger == Other->Trigger);
}
/// See if Other is a proper initial segment of this construction context
/// in terms of the parent chain - i.e. a few first parents coincide and
/// then the other context terminates but our context goes further - i.e.,
/// we are providing the same context that the other context provides,
/// and a bit more above that.
bool isStrictlyMoreSpecificThan(const ConstructionContextLayer *Other) const;
};
/// ConstructionContext's subclasses describe different ways of constructing
/// an object in C++. The context re-captures the essential parent AST nodes
/// of the CXXConstructExpr it is assigned to and presents these nodes
/// through easy-to-understand accessor methods.
class ConstructionContext {
public:
enum Kind {
SimpleVariableKind,
ConstructorInitializerKind,
NewAllocatedObjectKind,
TemporaryObjectKind,
ReturnedValueKind
};
protected:
Kind K;
protected:
// Do not make public! These need to only be constructed
// via createFromLayers().
explicit ConstructionContext(Kind K) : K(K) {}
public:
/// Consume the construction context layer, together with its parent layers,
/// and wrap it up into a complete construction context.
static const ConstructionContext *
createFromLayers(BumpVectorContext &C,
const ConstructionContextLayer *TopLayer);
Kind getKind() const { return K; }
};
/// Represents construction into a simple local variable, eg. T var(123);.
class SimpleVariableConstructionContext : public ConstructionContext {
const DeclStmt *DS;
public:
explicit SimpleVariableConstructionContext(const DeclStmt *DS)
: ConstructionContext(ConstructionContext::SimpleVariableKind), DS(DS) {
assert(DS);
}
const DeclStmt *getDeclStmt() const { return DS; }
static bool classof(const ConstructionContext *CC) {
return CC->getKind() == SimpleVariableKind;
}
};
/// Represents construction into a field or a base class within a bigger object
/// via a constructor initializer, eg. T(): field(123) { ... }.
class ConstructorInitializerConstructionContext : public ConstructionContext {
const CXXCtorInitializer *I;
public:
explicit ConstructorInitializerConstructionContext(
const CXXCtorInitializer *I)
: ConstructionContext(ConstructionContext::ConstructorInitializerKind),
I(I) {
assert(I);
}
const CXXCtorInitializer *getCXXCtorInitializer() const { return I; }
static bool classof(const ConstructionContext *CC) {
return CC->getKind() == ConstructorInitializerKind;
}
};
/// Represents immediate initialization of memory allocated by operator new,
/// eg. new T(123);.
class NewAllocatedObjectConstructionContext : public ConstructionContext {
const CXXNewExpr *NE;
public:
explicit NewAllocatedObjectConstructionContext(const CXXNewExpr *NE)
: ConstructionContext(ConstructionContext::NewAllocatedObjectKind),
NE(NE) {
assert(NE);
}
const CXXNewExpr *getCXXNewExpr() const { return NE; }
static bool classof(const ConstructionContext *CC) {
return CC->getKind() == NewAllocatedObjectKind;
}
};
/// Represents a temporary object, eg. T(123), that does not immediately cross
/// function boundaries "by value"; constructors that construct function
/// value-type arguments or values that are immediately returned from the
/// function that returns a value receive separate construction context kinds.
class TemporaryObjectConstructionContext : public ConstructionContext {
const CXXBindTemporaryExpr *BTE;
const MaterializeTemporaryExpr *MTE;
public:
explicit TemporaryObjectConstructionContext(
const CXXBindTemporaryExpr *BTE, const MaterializeTemporaryExpr *MTE)
: ConstructionContext(ConstructionContext::TemporaryObjectKind),
BTE(BTE), MTE(MTE) {
// Both BTE and MTE can be null here, all combinations possible.
// Even though for now at least one should be non-null, we simply haven't
// implemented this case yet (this would be a temporary in the middle of
// nowhere that doesn't have a non-trivial destructor).
}
/// CXXBindTemporaryExpr here is non-null as long as the temporary has
/// a non-trivial destructor.
const CXXBindTemporaryExpr *getCXXBindTemporaryExpr() const {
return BTE;
}
/// MaterializeTemporaryExpr is non-null as long as the temporary is actually
/// used after construction, eg. by binding to a reference (lifetime
/// extension), accessing a field, calling a method, or passing it into
/// a function (an elidable copy or move constructor would be a common
/// example) by reference.
const MaterializeTemporaryExpr *getMaterializedTemporaryExpr() const {
return MTE;
}
static bool classof(const ConstructionContext *CC) {
return CC->getKind() == TemporaryObjectKind;
}
};
/// Represents a temporary object that is being immediately returned from a
/// function by value, eg. return t; or return T(123);. In this case there is
/// always going to be a constructor at the return site. However, the usual
/// temporary-related bureaucracy (CXXBindTemporaryExpr,
/// MaterializeTemporaryExpr) is normally located in the caller function's AST.
class ReturnedValueConstructionContext : public ConstructionContext {
const ReturnStmt *RS;
public:
explicit ReturnedValueConstructionContext(const ReturnStmt *RS)
: ConstructionContext(ConstructionContext::ReturnedValueKind), RS(RS) {
assert(RS);
}
const ReturnStmt *getReturnStmt() const { return RS; }
static bool classof(const ConstructionContext *CC) {
return CC->getKind() == ReturnedValueKind;
}
};
} // end namespace clang
#endif // LLVM_CLANG_ANALYSIS_CONSTRUCTIONCONTEXT_H

View File

@ -29,6 +29,7 @@
#include "clang/AST/StmtVisitor.h"
#include "clang/AST/Type.h"
#include "clang/Analysis/Support/BumpVector.h"
#include "clang/Analysis/ConstructionContext.h"
#include "clang/Basic/Builtins.h"
#include "clang/Basic/ExceptionSpecificationType.h"
#include "clang/Basic/LLVM.h"
@ -475,7 +476,7 @@ class CFGBuilder {
// Information about the currently visited C++ object construction site.
// This is set in the construction trigger and read when the constructor
// itself is being visited.
llvm::DenseMap<CXXConstructExpr *, const ConstructionContext *>
llvm::DenseMap<CXXConstructExpr *, const ConstructionContextLayer *>
ConstructionContextMap;
bool badCFG = false;
@ -652,18 +653,19 @@ private:
return Block;
}
// Remember to apply \p CC when constructing the CFG element for \p CE.
void consumeConstructionContext(const ConstructionContext *CC,
// Remember to apply the construction context based on the current \p Layer
// when constructing the CFG element for \p CE.
void consumeConstructionContext(const ConstructionContextLayer *Layer,
CXXConstructExpr *CE);
// Scan the child statement \p Child to find the constructor that might
// have been directly triggered by the current node, \p Trigger. If such
// constructor has been found, set current construction context to point
// to the trigger statement. The construction context will be unset once
// it is consumed when the CFG building procedure processes the
// construct-expression and adds the respective CFGConstructor element.
void findConstructionContexts(const ConstructionContext *ContextSoFar,
// Scan \p Child statement to find constructors in it, while keeping in mind
// that its parent statement is providing a partial construction context
// described by \p Layer. If a constructor is found, it would be assigned
// the context based on the layer. If an additional construction context layer
// is found, the function recurses into that.
void findConstructionContexts(const ConstructionContextLayer *Layer,
Stmt *Child);
// Unset the construction context after consuming it. This is done immediately
// after adding the CFGConstructor element, so there's no need to
// do this manually in every Visit... function.
@ -710,7 +712,11 @@ private:
void appendConstructor(CFGBlock *B, CXXConstructExpr *CE) {
if (BuildOpts.AddRichCXXConstructors) {
if (const ConstructionContext *CC = ConstructionContextMap.lookup(CE)) {
if (const ConstructionContextLayer *Layer =
ConstructionContextMap.lookup(CE)) {
const ConstructionContext *CC =
ConstructionContext::createFromLayers(cfg->getBumpVectorContext(),
Layer);
B->appendConstructor(CE, CC, cfg->getBumpVectorContext());
cleanupConstructionContext(CE);
return;
@ -1155,20 +1161,21 @@ static const VariableArrayType *FindVA(const Type *t) {
return nullptr;
}
void CFGBuilder::consumeConstructionContext(const ConstructionContext *CC, CXXConstructExpr *CE) {
if (const ConstructionContext *PreviousContext =
void CFGBuilder::consumeConstructionContext(
const ConstructionContextLayer *Layer, CXXConstructExpr *CE) {
if (const ConstructionContextLayer *PreviouslyStoredLayer =
ConstructionContextMap.lookup(CE)) {
// We might have visited this child when we were finding construction
// contexts within its parents.
assert(PreviousContext->isStrictlyMoreSpecificThan(CC) &&
assert(PreviouslyStoredLayer->isStrictlyMoreSpecificThan(Layer) &&
"Already within a different construction context!");
} else {
ConstructionContextMap[CE] = CC;
ConstructionContextMap[CE] = Layer;
}
}
void CFGBuilder::findConstructionContexts(
const ConstructionContext *ContextSoFar, Stmt *Child) {
const ConstructionContextLayer *Layer, Stmt *Child) {
if (!BuildOpts.AddRichCXXConstructors)
return;
@ -1178,36 +1185,36 @@ void CFGBuilder::findConstructionContexts(
switch(Child->getStmtClass()) {
case Stmt::CXXConstructExprClass:
case Stmt::CXXTemporaryObjectExprClass: {
consumeConstructionContext(ContextSoFar, cast<CXXConstructExpr>(Child));
consumeConstructionContext(Layer, cast<CXXConstructExpr>(Child));
break;
}
case Stmt::ExprWithCleanupsClass: {
auto *Cleanups = cast<ExprWithCleanups>(Child);
findConstructionContexts(ContextSoFar, Cleanups->getSubExpr());
findConstructionContexts(Layer, Cleanups->getSubExpr());
break;
}
case Stmt::CXXFunctionalCastExprClass: {
auto *Cast = cast<CXXFunctionalCastExpr>(Child);
findConstructionContexts(ContextSoFar, Cast->getSubExpr());
findConstructionContexts(Layer, Cast->getSubExpr());
break;
}
case Stmt::ImplicitCastExprClass: {
auto *Cast = cast<ImplicitCastExpr>(Child);
findConstructionContexts(ContextSoFar, Cast->getSubExpr());
findConstructionContexts(Layer, Cast->getSubExpr());
break;
}
case Stmt::CXXBindTemporaryExprClass: {
auto *BTE = cast<CXXBindTemporaryExpr>(Child);
findConstructionContexts(
ConstructionContext::create(cfg->getBumpVectorContext(), BTE,
ContextSoFar),
ConstructionContextLayer::create(cfg->getBumpVectorContext(),
BTE, Layer),
BTE->getSubExpr());
break;
}
case Stmt::ConditionalOperatorClass: {
auto *CO = cast<ConditionalOperator>(Child);
findConstructionContexts(ContextSoFar, CO->getLHS());
findConstructionContexts(ContextSoFar, CO->getRHS());
findConstructionContexts(Layer, CO->getLHS());
findConstructionContexts(Layer, CO->getRHS());
break;
}
default:
@ -1356,7 +1363,7 @@ CFGBlock *CFGBuilder::addInitializer(CXXCtorInitializer *I) {
if (Init) {
findConstructionContexts(
ConstructionContext::create(cfg->getBumpVectorContext(), I),
ConstructionContextLayer::create(cfg->getBumpVectorContext(), I),
Init);
if (HasTemporaries) {
@ -2448,7 +2455,7 @@ CFGBlock *CFGBuilder::VisitDeclSubExpr(DeclStmt *DS) {
appendStmt(Block, DS);
findConstructionContexts(
ConstructionContext::create(cfg->getBumpVectorContext(), DS),
ConstructionContextLayer::create(cfg->getBumpVectorContext(), DS),
Init);
// Keep track of the last non-null block, as 'Block' can be nulled out
@ -2642,7 +2649,7 @@ CFGBlock *CFGBuilder::VisitReturnStmt(ReturnStmt *R) {
addAutomaticObjHandling(ScopePos, LocalScope::const_iterator(), R);
findConstructionContexts(
ConstructionContext::create(cfg->getBumpVectorContext(), R),
ConstructionContextLayer::create(cfg->getBumpVectorContext(), R),
R->getRetValue());
// If the one of the destructors does not return, we already have the Exit
@ -3015,7 +3022,7 @@ CFGBlock *
CFGBuilder::VisitMaterializeTemporaryExpr(MaterializeTemporaryExpr *MTE,
AddStmtChoice asc) {
findConstructionContexts(
ConstructionContext::create(cfg->getBumpVectorContext(), MTE),
ConstructionContextLayer::create(cfg->getBumpVectorContext(), MTE),
MTE->getTemporary());
return VisitStmt(MTE, asc);
@ -4002,7 +4009,7 @@ CFGBlock *CFGBuilder::VisitCXXBindTemporaryExpr(CXXBindTemporaryExpr *E,
appendStmt(Block, E);
findConstructionContexts(
ConstructionContext::create(cfg->getBumpVectorContext(), E),
ConstructionContextLayer::create(cfg->getBumpVectorContext(), E),
E->getSubExpr());
// We do not want to propagate the AlwaysAdd property.
@ -4025,7 +4032,7 @@ CFGBlock *CFGBuilder::VisitCXXNewExpr(CXXNewExpr *NE,
appendStmt(Block, NE);
findConstructionContexts(
ConstructionContext::create(cfg->getBumpVectorContext(), NE),
ConstructionContextLayer::create(cfg->getBumpVectorContext(), NE),
const_cast<CXXConstructExpr *>(NE->getConstructExpr()));
if (NE->getInitializer())
@ -4752,19 +4759,44 @@ static void print_elem(raw_ostream &OS, StmtPrinterHelper &Helper,
} else if (const CXXConstructExpr *CCE = dyn_cast<CXXConstructExpr>(S)) {
OS << " (CXXConstructExpr, ";
if (Optional<CFGConstructor> CE = E.getAs<CFGConstructor>()) {
// TODO: Refactor into ConstructionContext::print().
if (const Stmt *S = CE->getTriggerStmt())
Helper.handledStmt(const_cast<Stmt *>(S), OS);
else if (const CXXCtorInitializer *I = CE->getTriggerInit())
print_initializer(OS, Helper, I);
else
llvm_unreachable("Unexpected trigger kind!");
OS << ", ";
if (const Stmt *S = CE->getMaterializedTemporary()) {
if (S != CE->getTriggerStmt()) {
Helper.handledStmt(const_cast<Stmt *>(S), OS);
OS << ", ";
}
const ConstructionContext *CC = CE->getConstructionContext();
const Stmt *S1 = nullptr, *S2 = nullptr;
switch (CC->getKind()) {
case ConstructionContext::ConstructorInitializerKind: {
const auto *ICC = cast<ConstructorInitializerConstructionContext>(CC);
print_initializer(OS, Helper, ICC->getCXXCtorInitializer());
OS << ", ";
break;
}
case ConstructionContext::SimpleVariableKind: {
const auto *DSCC = cast<SimpleVariableConstructionContext>(CC);
S1 = DSCC->getDeclStmt();
break;
}
case ConstructionContext::NewAllocatedObjectKind: {
const auto *NECC = cast<NewAllocatedObjectConstructionContext>(CC);
S1 = NECC->getCXXNewExpr();
break;
}
case ConstructionContext::ReturnedValueKind: {
const auto *RSCC = cast<ReturnedValueConstructionContext>(CC);
S1 = RSCC->getReturnStmt();
break;
}
case ConstructionContext::TemporaryObjectKind: {
const auto *TOCC = cast<TemporaryObjectConstructionContext>(CC);
S1 = TOCC->getCXXBindTemporaryExpr();
S2 = TOCC->getMaterializedTemporaryExpr();
break;
}
}
if (S1) {
Helper.handledStmt(const_cast<Stmt *>(S1), OS);
OS << ", ";
}
if (S2) {
Helper.handledStmt(const_cast<Stmt *>(S2), OS);
OS << ", ";
}
}
OS << CCE->getType().getAsString() << ")";

View File

@ -11,6 +11,7 @@ add_clang_library(clangAnalysis
CallGraph.cpp
CloneDetection.cpp
CocoaConventions.cpp
ConstructionContext.cpp
Consumed.cpp
CodeInjector.cpp
Dominators.cpp

View File

@ -0,0 +1,92 @@
//===- ConstructionContext.cpp - CFG constructor information --------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file defines the ConstructionContext class and its sub-classes,
// which represent various different ways of constructing C++ objects
// with the additional information the users may want to know about
// the constructor.
//
//===----------------------------------------------------------------------===//
#include "clang/Analysis/ConstructionContext.h"
using namespace clang;
const ConstructionContextLayer *
ConstructionContextLayer::create(BumpVectorContext &C, TriggerTy Trigger,
const ConstructionContextLayer *Parent) {
ConstructionContextLayer *CC =
C.getAllocator().Allocate<ConstructionContextLayer>();
return new (CC) ConstructionContextLayer(Trigger, Parent);
}
bool ConstructionContextLayer::isStrictlyMoreSpecificThan(
const ConstructionContextLayer *Other) const {
const ConstructionContextLayer *Self = this;
while (true) {
if (!Other)
return Self;
if (!Self || !Self->isSameLayer(Other))
return false;
Self = Self->getParent();
Other = Other->getParent();
}
llvm_unreachable("The above loop can only be terminated via return!");
}
const ConstructionContext *ConstructionContext::createFromLayers(
BumpVectorContext &C, const ConstructionContextLayer *TopLayer) {
// Before this point all we've had was a stockpile of arbitrary layers.
// Now validate that it is shaped as one of the finite amount of expected
// patterns.
if (const Stmt *S = TopLayer->getTriggerStmt()) {
if (const auto *DS = dyn_cast<DeclStmt>(S)) {
assert(TopLayer->isLast());
auto *CC =
C.getAllocator().Allocate<SimpleVariableConstructionContext>();
return new (CC) SimpleVariableConstructionContext(DS);
} else if (const auto *NE = dyn_cast<CXXNewExpr>(S)) {
assert(TopLayer->isLast());
auto *CC =
C.getAllocator().Allocate<NewAllocatedObjectConstructionContext>();
return new (CC) NewAllocatedObjectConstructionContext(NE);
} else if (const auto *BTE = dyn_cast<CXXBindTemporaryExpr>(S)) {
const MaterializeTemporaryExpr *MTE = nullptr;
assert(BTE->getType().getCanonicalType()
->getAsCXXRecordDecl()->hasNonTrivialDestructor());
// For temporaries with destructors, there may or may not be
// lifetime extension on the parent layer.
if (const ConstructionContextLayer *ParentLayer = TopLayer->getParent()) {
assert(ParentLayer->isLast());
MTE = cast<MaterializeTemporaryExpr>(ParentLayer->getTriggerStmt());
}
auto *CC =
C.getAllocator().Allocate<TemporaryObjectConstructionContext>();
return new (CC) TemporaryObjectConstructionContext(BTE, MTE);
} else if (const auto *MTE = dyn_cast<MaterializeTemporaryExpr>(S)) {
assert(MTE->getType().getCanonicalType()
->getAsCXXRecordDecl()->hasTrivialDestructor());
assert(TopLayer->isLast());
auto *CC =
C.getAllocator().Allocate<TemporaryObjectConstructionContext>();
return new (CC) TemporaryObjectConstructionContext(nullptr, MTE);
} else if (const auto *RS = dyn_cast<ReturnStmt>(S)) {
assert(TopLayer->isLast());
auto *CC =
C.getAllocator().Allocate<ReturnedValueConstructionContext>();
return new (CC) ReturnedValueConstructionContext(RS);
}
} else if (const CXXCtorInitializer *I = TopLayer->getTriggerInit()) {
assert(TopLayer->isLast());
auto *CC =
C.getAllocator().Allocate<ConstructorInitializerConstructionContext>();
return new (CC) ConstructorInitializerConstructionContext(I);
}
llvm_unreachable("Unexpected construction context!");
}

View File

@ -12,6 +12,7 @@
//===----------------------------------------------------------------------===//
#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"
#include "clang/Analysis/ConstructionContext.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/StmtCXX.h"
#include "clang/AST/ParentMap.h"
@ -111,47 +112,20 @@ ExprEngine::getRegionForConstructedObject(const CXXConstructExpr *CE,
// See if we're constructing an existing region by looking at the
// current construction context.
if (CC) {
if (const Stmt *TriggerStmt = CC->getTriggerStmt()) {
if (const CXXNewExpr *CNE = dyn_cast<CXXNewExpr>(TriggerStmt)) {
if (AMgr.getAnalyzerOptions().mayInlineCXXAllocator()) {
// TODO: Detect when the allocator returns a null pointer.
// Constructor shall not be called in this case.
if (const SubRegion *MR = dyn_cast_or_null<SubRegion>(
getCXXNewAllocatorValue(State, CNE, LCtx).getAsRegion())) {
if (CNE->isArray()) {
// TODO: In fact, we need to call the constructor for every
// allocated element, not just the first one!
CallOpts.IsArrayCtorOrDtor = true;
return getStoreManager().GetElementZeroRegion(
MR, CNE->getType()->getPointeeType());
}
return MR;
}
}
} else if (auto *DS = dyn_cast<DeclStmt>(TriggerStmt)) {
const auto *Var = cast<VarDecl>(DS->getSingleDecl());
SVal LValue = State->getLValue(Var, LCtx);
QualType Ty = Var->getType();
LValue = makeZeroElementRegion(State, LValue, Ty,
CallOpts.IsArrayCtorOrDtor);
return LValue.getAsRegion();
} else if (isa<ReturnStmt>(TriggerStmt)) {
// TODO: We should construct into a CXXBindTemporaryExpr or a
// MaterializeTemporaryExpr around the call-expression on the previous
// stack frame. Currently we re-bind the temporary to the correct region
// later, but that's not semantically correct. This of course does not
// apply when we're in the top frame. But if we are in an inlined
// function, we should be able to take the call-site CFG element,
// and it should contain (but right now it wouldn't) some sort of
// construction context that'd give us the right temporary expression.
CallOpts.IsTemporaryCtorOrDtor = true;
return MRMgr.getCXXTempObjectRegion(CE, LCtx);
} else if (isa<CXXBindTemporaryExpr>(TriggerStmt)) {
CallOpts.IsTemporaryCtorOrDtor = true;
return MRMgr.getCXXTempObjectRegion(CE, LCtx);
}
// TODO: Consider other directly initialized elements.
} else if (const CXXCtorInitializer *Init = CC->getTriggerInit()) {
switch (CC->getKind()) {
case ConstructionContext::SimpleVariableKind: {
const auto *DSCC = cast<SimpleVariableConstructionContext>(CC);
const auto *DS = DSCC->getDeclStmt();
const auto *Var = cast<VarDecl>(DS->getSingleDecl());
SVal LValue = State->getLValue(Var, LCtx);
QualType Ty = Var->getType();
LValue =
makeZeroElementRegion(State, LValue, Ty, CallOpts.IsArrayCtorOrDtor);
return LValue.getAsRegion();
}
case ConstructionContext::ConstructorInitializerKind: {
const auto *ICC = cast<ConstructorInitializerConstructionContext>(CC);
const auto *Init = ICC->getCXXCtorInitializer();
assert(Init->isAnyMemberInitializer());
const CXXMethodDecl *CurCtor = cast<CXXMethodDecl>(LCtx->getDecl());
Loc ThisPtr =
@ -173,10 +147,45 @@ ExprEngine::getRegionForConstructedObject(const CXXConstructExpr *CE,
CallOpts.IsArrayCtorOrDtor);
return FieldVal.getAsRegion();
}
// FIXME: This will eventually need to handle new-expressions as well.
// Don't forget to update the pre-constructor initialization code in
// ExprEngine::VisitCXXConstructExpr.
case ConstructionContext::NewAllocatedObjectKind: {
if (AMgr.getAnalyzerOptions().mayInlineCXXAllocator()) {
const auto *NECC = cast<NewAllocatedObjectConstructionContext>(CC);
const auto *NE = NECC->getCXXNewExpr();
// TODO: Detect when the allocator returns a null pointer.
// Constructor shall not be called in this case.
if (const SubRegion *MR = dyn_cast_or_null<SubRegion>(
getCXXNewAllocatorValue(State, NE, LCtx).getAsRegion())) {
if (NE->isArray()) {
// TODO: In fact, we need to call the constructor for every
// allocated element, not just the first one!
CallOpts.IsArrayCtorOrDtor = true;
return getStoreManager().GetElementZeroRegion(
MR, NE->getType()->getPointeeType());
}
return MR;
}
}
break;
}
case ConstructionContext::TemporaryObjectKind: {
// TODO: Support temporaries lifetime-extended via static references.
// They'd need a getCXXStaticTempObjectRegion().
CallOpts.IsTemporaryCtorOrDtor = true;
return MRMgr.getCXXTempObjectRegion(CE, LCtx);
}
case ConstructionContext::ReturnedValueKind: {
// TODO: We should construct into a CXXBindTemporaryExpr or a
// MaterializeTemporaryExpr around the call-expression on the previous
// stack frame. Currently we re-bind the temporary to the correct region
// later, but that's not semantically correct. This of course does not
// apply when we're in the top frame. But if we are in an inlined
// function, we should be able to take the call-site CFG element,
// and it should contain (but right now it wouldn't) some sort of
// construction context that'd give us the right temporary expression.
CallOpts.IsTemporaryCtorOrDtor = true;
return MRMgr.getCXXTempObjectRegion(CE, LCtx);
}
}
}
// If we couldn't find an existing region to construct into, assume we're
// constructing a temporary. Notify the caller of our failure.
@ -227,6 +236,7 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE,
EvalCallOptions CallOpts;
auto C = getCurrentCFGElement().getAs<CFGConstructor>();
assert(C || getCurrentCFGElement().getAs<CFGStmt>());
const ConstructionContext *CC = C ? C->getConstructionContext() : nullptr;
const CXXBindTemporaryExpr *BTE = nullptr;
@ -235,15 +245,27 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE,
switch (CE->getConstructionKind()) {
case CXXConstructExpr::CK_Complete: {
Target = getRegionForConstructedObject(CE, Pred, CC, CallOpts);
if (CC && AMgr.getAnalyzerOptions().includeTemporaryDtorsInCFG() &&
!CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion &&
CallOpts.IsTemporaryCtorOrDtor) {
MTE = CC->getMaterializedTemporary();
if (!MTE || MTE->getStorageDuration() == SD_FullExpression) {
// If the temporary is lifetime-extended, don't save the BTE,
// because we don't need a temporary destructor, but an automatic
// destructor. The cast may fail because it may as well be a ReturnStmt.
BTE = dyn_cast<CXXBindTemporaryExpr>(CC->getTriggerStmt());
// In case of temporary object construction, extract data necessary for
// destruction and lifetime extension.
if (const auto *TCC =
dyn_cast_or_null<TemporaryObjectConstructionContext>(CC)) {
assert(CallOpts.IsTemporaryCtorOrDtor);
assert(!CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion);
if (AMgr.getAnalyzerOptions().includeTemporaryDtorsInCFG()) {
BTE = TCC->getCXXBindTemporaryExpr();
MTE = TCC->getMaterializedTemporaryExpr();
if (!BTE) {
// FIXME: lifetime extension for temporaries without destructors
// is not implemented yet.
MTE = nullptr;
}
if (MTE && MTE->getStorageDuration() != SD_FullExpression) {
// If the temporary is lifetime-extended, don't save the BTE,
// because we don't need a temporary destructor, but an automatic
// destructor.
BTE = nullptr;
}
}
}
break;

View File

@ -16,6 +16,7 @@
#include "clang/AST/CXXInheritance.h"
#include "clang/AST/DeclCXX.h"
#include "clang/Analysis/Analyses/LiveVariables.h"
#include "clang/Analysis/ConstructionContext.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "llvm/ADT/SmallSet.h"
@ -635,10 +636,11 @@ ExprEngine::mayInlineCallKind(const CallEvent &Call, const ExplodedNode *Pred,
const CXXConstructExpr *CtorExpr = Ctor.getOriginExpr();
auto CC = getCurrentCFGElement().getAs<CFGConstructor>();
const Stmt *ParentExpr = CC ? CC->getTriggerStmt() : nullptr;
auto CCE = getCurrentCFGElement().getAs<CFGConstructor>();
const ConstructionContext *CC = CCE ? CCE->getConstructionContext()
: nullptr;
if (ParentExpr && isa<CXXNewExpr>(ParentExpr) &&
if (CC && isa<NewAllocatedObjectConstructionContext>(CC) &&
!Opts.mayInlineCXXAllocator())
return CIP_DisallowedOnce;