forked from OSchip/llvm-project
[analyzer] Evaluate construction of non-POD type arrays
Introducing the support for evaluating the constructor of every element in an array. The idea is to record the index of the current array member being constructed and create a loop during the analysis. We looping over the same CXXConstructExpr as many times as many elements the array has. Differential Revision: https://reviews.llvm.org/D127973
This commit is contained in:
parent
2da550140a
commit
b032e3ff61
|
@ -617,6 +617,11 @@ public:
|
|||
return svalBuilder.evalBinOp(ST, Op, LHS, RHS, T);
|
||||
}
|
||||
|
||||
/// Retreives which element is being constructed in a non POD type array.
|
||||
static Optional<unsigned>
|
||||
getIndexOfElementToConstruct(ProgramStateRef State, const CXXConstructExpr *E,
|
||||
const LocationContext *LCtx);
|
||||
|
||||
/// By looking at a certain item that may be potentially part of an object's
|
||||
/// ConstructionContext, retrieve such object's location. A particular
|
||||
/// statement can be transparently passed as \p Item in most cases.
|
||||
|
@ -708,10 +713,19 @@ public:
|
|||
/// fully implemented it sometimes indicates that it failed via its
|
||||
/// out-parameter CallOpts; in such cases a fake temporary region is
|
||||
/// returned, which is better than nothing but does not represent
|
||||
/// the actual behavior of the program.
|
||||
SVal computeObjectUnderConstruction(
|
||||
const Expr *E, ProgramStateRef State, const LocationContext *LCtx,
|
||||
const ConstructionContext *CC, EvalCallOptions &CallOpts);
|
||||
/// the actual behavior of the program. The Idx parameter is used if we
|
||||
/// construct an array of objects. In that case it points to the index
|
||||
/// of the continous memory region.
|
||||
/// E.g.:
|
||||
/// For `int arr[4]` this index can be 0,1,2,3.
|
||||
/// For `int arr2[3][3]` this index can be 0,1,...,7,8.
|
||||
/// A multi-dimensional array is also a continous memory location in a
|
||||
/// row major order, so for arr[0][0] Idx is 0 and for arr[2][2] Idx is 8.
|
||||
SVal computeObjectUnderConstruction(const Expr *E, ProgramStateRef State,
|
||||
const LocationContext *LCtx,
|
||||
const ConstructionContext *CC,
|
||||
EvalCallOptions &CallOpts,
|
||||
unsigned Idx = 0);
|
||||
|
||||
/// Update the program state with all the path-sensitive information
|
||||
/// that's necessary to perform construction of an object with a given
|
||||
|
@ -724,12 +738,16 @@ public:
|
|||
|
||||
/// A convenient wrapper around computeObjectUnderConstruction
|
||||
/// and updateObjectsUnderConstruction.
|
||||
std::pair<ProgramStateRef, SVal> handleConstructionContext(
|
||||
const Expr *E, ProgramStateRef State, const LocationContext *LCtx,
|
||||
const ConstructionContext *CC, EvalCallOptions &CallOpts) {
|
||||
SVal V = computeObjectUnderConstruction(E, State, LCtx, CC, CallOpts);
|
||||
return std::make_pair(
|
||||
updateObjectsUnderConstruction(V, E, State, LCtx, CC, CallOpts), V);
|
||||
std::pair<ProgramStateRef, SVal>
|
||||
handleConstructionContext(const Expr *E, ProgramStateRef State,
|
||||
const LocationContext *LCtx,
|
||||
const ConstructionContext *CC,
|
||||
EvalCallOptions &CallOpts, unsigned Idx = 0) {
|
||||
|
||||
SVal V = computeObjectUnderConstruction(E, State, LCtx, CC, CallOpts, Idx);
|
||||
State = updateObjectsUnderConstruction(V, E, State, LCtx, CC, CallOpts);
|
||||
|
||||
return std::make_pair(State, V);
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -796,6 +814,15 @@ private:
|
|||
const ExplodedNode *Pred,
|
||||
const EvalCallOptions &CallOpts = {});
|
||||
|
||||
/// Checks whether our policies allow us to inline a non-POD type array
|
||||
/// construction.
|
||||
bool shouldInlineArrayConstruction(const ArrayType *Type);
|
||||
|
||||
/// Checks whether we construct an array of non-POD type, and decides if the
|
||||
/// constructor should be inkoved once again.
|
||||
bool shouldRepeatCtorCall(ProgramStateRef State, const CXXConstructExpr *E,
|
||||
const LocationContext *LCtx);
|
||||
|
||||
void inlineCall(WorkList *WList, const CallEvent &Call, const Decl *D,
|
||||
NodeBuilder &Bldr, ExplodedNode *Pred, ProgramStateRef State);
|
||||
|
||||
|
@ -838,7 +865,7 @@ private:
|
|||
const Expr *InitWithAdjustments, const Expr *Result = nullptr,
|
||||
const SubRegion **OutRegionWithAdjustments = nullptr);
|
||||
|
||||
/// Returns a region representing the first element of a (possibly
|
||||
/// Returns a region representing the `Idx`th element of a (possibly
|
||||
/// multi-dimensional) array, for the purposes of element construction or
|
||||
/// destruction.
|
||||
///
|
||||
|
@ -846,8 +873,8 @@ private:
|
|||
///
|
||||
/// If the type is not an array type at all, the original value is returned.
|
||||
/// Otherwise the "IsArray" flag is set.
|
||||
static SVal makeZeroElementRegion(ProgramStateRef State, SVal LValue,
|
||||
QualType &Ty, bool &IsArray);
|
||||
static SVal makeElementRegion(ProgramStateRef State, SVal LValue,
|
||||
QualType &Ty, bool &IsArray, unsigned Idx = 0);
|
||||
|
||||
/// For a DeclStmt or CXXInitCtorInitializer, walk backward in the current CFG
|
||||
/// block to find the constructor expression that directly constructed into
|
||||
|
@ -878,6 +905,17 @@ public:
|
|||
const ObjCForCollectionStmt *O,
|
||||
const LocationContext *LC);
|
||||
private:
|
||||
/// Assuming we construct an array of non-POD types, this method allows us
|
||||
/// to store which element is to be constructed next.
|
||||
static ProgramStateRef
|
||||
setIndexOfElementToConstruct(ProgramStateRef State, const CXXConstructExpr *E,
|
||||
const LocationContext *LCtx, unsigned Idx);
|
||||
|
||||
static ProgramStateRef
|
||||
removeIndexOfElementToConstruct(ProgramStateRef State,
|
||||
const CXXConstructExpr *E,
|
||||
const LocationContext *LCtx);
|
||||
|
||||
/// Store the location of a C++ object corresponding to a statement
|
||||
/// until the statement is actually encountered. For example, if a DeclStmt
|
||||
/// has CXXConstructExpr as its initializer, the object would be considered
|
||||
|
|
|
@ -185,6 +185,17 @@ typedef llvm::ImmutableMap<ConstructedObjectKey, SVal>
|
|||
REGISTER_TRAIT_WITH_PROGRAMSTATE(ObjectsUnderConstruction,
|
||||
ObjectsUnderConstructionMap)
|
||||
|
||||
// This trait is responsible for storing the index of the element that is to be
|
||||
// constructed in the next iteration. As a result a CXXConstructExpr is only
|
||||
// stored if it is array type. Also the index is the index of the continous
|
||||
// memory region, which is important for multi-dimensional arrays. E.g:: int
|
||||
// arr[2][2]; assume arr[1][1] will be the next element under construction, so
|
||||
// the index is 3.
|
||||
typedef llvm::ImmutableMap<
|
||||
std::pair<const CXXConstructExpr *, const LocationContext *>, unsigned>
|
||||
IndexOfElementToConstructMap;
|
||||
REGISTER_TRAIT_WITH_PROGRAMSTATE(IndexOfElementToConstruct,
|
||||
IndexOfElementToConstructMap)
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Engine construction and deletion.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
@ -441,16 +452,65 @@ ProgramStateRef ExprEngine::createTemporaryRegionIfNeeded(
|
|||
return State;
|
||||
}
|
||||
|
||||
ProgramStateRef ExprEngine::setIndexOfElementToConstruct(
|
||||
ProgramStateRef State, const CXXConstructExpr *E,
|
||||
const LocationContext *LCtx, unsigned Idx) {
|
||||
auto Key = std::make_pair(E, LCtx->getStackFrame());
|
||||
|
||||
assert(!State->contains<IndexOfElementToConstruct>(Key) || Idx > 0);
|
||||
|
||||
return State->set<IndexOfElementToConstruct>(Key, Idx);
|
||||
}
|
||||
|
||||
Optional<unsigned>
|
||||
ExprEngine::getIndexOfElementToConstruct(ProgramStateRef State,
|
||||
const CXXConstructExpr *E,
|
||||
const LocationContext *LCtx) {
|
||||
|
||||
return Optional<unsigned>::create(
|
||||
State->get<IndexOfElementToConstruct>({E, LCtx->getStackFrame()}));
|
||||
}
|
||||
|
||||
ProgramStateRef
|
||||
ExprEngine::removeIndexOfElementToConstruct(ProgramStateRef State,
|
||||
const CXXConstructExpr *E,
|
||||
const LocationContext *LCtx) {
|
||||
auto Key = std::make_pair(E, LCtx->getStackFrame());
|
||||
|
||||
assert(E && State->contains<IndexOfElementToConstruct>(Key));
|
||||
return State->remove<IndexOfElementToConstruct>(Key);
|
||||
}
|
||||
|
||||
ProgramStateRef
|
||||
ExprEngine::addObjectUnderConstruction(ProgramStateRef State,
|
||||
const ConstructionContextItem &Item,
|
||||
const LocationContext *LC, SVal V) {
|
||||
ConstructedObjectKey Key(Item, LC->getStackFrame());
|
||||
|
||||
const CXXConstructExpr *E = nullptr;
|
||||
|
||||
if (auto DS = dyn_cast_or_null<DeclStmt>(Item.getStmtOrNull())) {
|
||||
if (auto VD = dyn_cast_or_null<VarDecl>(DS->getSingleDecl()))
|
||||
E = dyn_cast<CXXConstructExpr>(VD->getInit());
|
||||
}
|
||||
|
||||
if (!E && !Item.getStmtOrNull()) {
|
||||
auto CtorInit = Item.getCXXCtorInitializer();
|
||||
E = dyn_cast<CXXConstructExpr>(CtorInit->getInit());
|
||||
}
|
||||
|
||||
// FIXME: Currently the state might already contain the marker due to
|
||||
// incorrect handling of temporaries bound to default parameters.
|
||||
assert(!State->get<ObjectsUnderConstruction>(Key) ||
|
||||
Key.getItem().getKind() ==
|
||||
ConstructionContextItem::TemporaryDestructorKind);
|
||||
// The state will already contain the marker if we construct elements
|
||||
// in an array, as we visit the same statement multiple times before
|
||||
// the array declaration. The marker is removed when we exit the
|
||||
// constructor call.
|
||||
assert((!State->get<ObjectsUnderConstruction>(Key) ||
|
||||
Key.getItem().getKind() ==
|
||||
ConstructionContextItem::TemporaryDestructorKind ||
|
||||
State->contains<IndexOfElementToConstruct>({E, LC})) &&
|
||||
"The object is already marked as `UnderConstruction`, when it's not "
|
||||
"supposed to!");
|
||||
return State->set<ObjectsUnderConstruction>(Key, V);
|
||||
}
|
||||
|
||||
|
@ -582,6 +642,69 @@ printObjectsUnderConstructionJson(raw_ostream &Out, ProgramStateRef State,
|
|||
}
|
||||
}
|
||||
|
||||
static void printIndicesOfElementsToConstructJson(
|
||||
raw_ostream &Out, ProgramStateRef State, const char *NL,
|
||||
const LocationContext *LCtx, const ASTContext &Context,
|
||||
unsigned int Space = 0, bool IsDot = false) {
|
||||
using KeyT = std::pair<const Expr *, const LocationContext *>;
|
||||
|
||||
PrintingPolicy PP =
|
||||
LCtx->getAnalysisDeclContext()->getASTContext().getPrintingPolicy();
|
||||
|
||||
++Space;
|
||||
bool HasItem = false;
|
||||
|
||||
// Store the last key.
|
||||
KeyT LastKey;
|
||||
for (const auto &I : State->get<IndexOfElementToConstruct>()) {
|
||||
const KeyT &Key = I.first;
|
||||
if (Key.second != LCtx)
|
||||
continue;
|
||||
|
||||
if (!HasItem) {
|
||||
Out << "[" << NL;
|
||||
HasItem = true;
|
||||
}
|
||||
|
||||
LastKey = Key;
|
||||
}
|
||||
|
||||
for (const auto &I : State->get<IndexOfElementToConstruct>()) {
|
||||
const KeyT &Key = I.first;
|
||||
unsigned Value = I.second;
|
||||
if (Key.second != LCtx)
|
||||
continue;
|
||||
|
||||
Indent(Out, Space, IsDot) << "{ ";
|
||||
|
||||
// Expr
|
||||
const Expr *E = Key.first;
|
||||
Out << "\"stmt_id\": " << E->getID(Context);
|
||||
|
||||
// Kind - hack to display the current index
|
||||
Out << ", \"kind\": \"Cur: " << Value - 1 << "\"";
|
||||
|
||||
// Pretty-print
|
||||
Out << ", \"pretty\": ";
|
||||
Out << "\"" << E->getStmtClassName() << " "
|
||||
<< E->getSourceRange().printToString(Context.getSourceManager()) << " '"
|
||||
<< QualType::getAsString(E->getType().split(), PP);
|
||||
Out << "'\"";
|
||||
|
||||
Out << ", \"value\": \"Next: " << Value << "\" }";
|
||||
|
||||
if (Key != LastKey)
|
||||
Out << ',';
|
||||
Out << NL;
|
||||
}
|
||||
|
||||
if (HasItem)
|
||||
Indent(Out, --Space, IsDot) << ']'; // End of "location_context".
|
||||
else {
|
||||
Out << "null ";
|
||||
}
|
||||
}
|
||||
|
||||
void ExprEngine::printJson(raw_ostream &Out, ProgramStateRef State,
|
||||
const LocationContext *LCtx, const char *NL,
|
||||
unsigned int Space, bool IsDot) const {
|
||||
|
@ -600,6 +723,23 @@ void ExprEngine::printJson(raw_ostream &Out, ProgramStateRef State,
|
|||
Out << "null," << NL;
|
||||
}
|
||||
|
||||
Indent(Out, Space, IsDot) << "\"index_of_element\": ";
|
||||
if (LCtx && !State->get<IndexOfElementToConstruct>().isEmpty()) {
|
||||
++Space;
|
||||
|
||||
auto &Context = getContext();
|
||||
Out << '[' << NL;
|
||||
LCtx->printJson(Out, NL, Space, IsDot, [&](const LocationContext *LC) {
|
||||
printIndicesOfElementsToConstructJson(Out, State, NL, LC, Context, Space,
|
||||
IsDot);
|
||||
});
|
||||
|
||||
--Space;
|
||||
Indent(Out, Space, IsDot) << "]," << NL; // End of "index_of_element".
|
||||
} else {
|
||||
Out << "null," << NL;
|
||||
}
|
||||
|
||||
getCheckerManager().runCheckersForPrintStateJson(Out, State, NL, Space,
|
||||
IsDot);
|
||||
}
|
||||
|
@ -961,8 +1101,9 @@ void ExprEngine::ProcessAutomaticObjDtor(const CFGAutomaticObjDtor Dtor,
|
|||
// This workaround will just run the first destructor (which will still
|
||||
// invalidate the entire array).
|
||||
EvalCallOptions CallOpts;
|
||||
Region = makeZeroElementRegion(state, loc::MemRegionVal(Region), varType,
|
||||
CallOpts.IsArrayCtorOrDtor).getAsRegion();
|
||||
Region = makeElementRegion(state, loc::MemRegionVal(Region), varType,
|
||||
CallOpts.IsArrayCtorOrDtor)
|
||||
.getAsRegion();
|
||||
|
||||
VisitCXXDestructor(varType, Region, Dtor.getTriggerStmt(),
|
||||
/*IsBase=*/false, Pred, Dst, CallOpts);
|
||||
|
@ -1045,8 +1186,7 @@ void ExprEngine::ProcessMemberDtor(const CFGMemberDtor D,
|
|||
// This workaround will just run the first destructor (which will still
|
||||
// invalidate the entire array).
|
||||
EvalCallOptions CallOpts;
|
||||
FieldVal = makeZeroElementRegion(State, FieldVal, T,
|
||||
CallOpts.IsArrayCtorOrDtor);
|
||||
FieldVal = makeElementRegion(State, FieldVal, T, CallOpts.IsArrayCtorOrDtor);
|
||||
|
||||
VisitCXXDestructor(T, FieldVal.getAsRegion(), CurDtor->getBody(),
|
||||
/*IsBase=*/false, Pred, Dst, CallOpts);
|
||||
|
@ -1105,7 +1245,7 @@ void ExprEngine::ProcessTemporaryDtor(const CFGTemporaryDtor D,
|
|||
CallOpts.IsArrayCtorOrDtor = true;
|
||||
}
|
||||
} else {
|
||||
// We'd eventually need to makeZeroElementRegion() trick here,
|
||||
// We'd eventually need to makeElementRegion() trick here,
|
||||
// but for now we don't have the respective construction contexts,
|
||||
// so MR would always be null in this case. Do nothing for now.
|
||||
}
|
||||
|
|
|
@ -94,15 +94,17 @@ void ExprEngine::performTrivialCopy(NodeBuilder &Bldr, ExplodedNode *Pred,
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
SVal ExprEngine::makeZeroElementRegion(ProgramStateRef State, SVal LValue,
|
||||
QualType &Ty, bool &IsArray) {
|
||||
SVal ExprEngine::makeElementRegion(ProgramStateRef State, SVal LValue,
|
||||
QualType &Ty, bool &IsArray, unsigned Idx) {
|
||||
SValBuilder &SVB = State->getStateManager().getSValBuilder();
|
||||
ASTContext &Ctx = SVB.getContext();
|
||||
|
||||
while (const ArrayType *AT = Ctx.getAsArrayType(Ty)) {
|
||||
Ty = AT->getElementType();
|
||||
LValue = State->getLValue(Ty, SVB.makeZeroArrayIndex(), LValue);
|
||||
if (const ArrayType *AT = Ctx.getAsArrayType(Ty)) {
|
||||
while (AT) {
|
||||
Ty = AT->getElementType();
|
||||
AT = dyn_cast<ArrayType>(AT->getElementType());
|
||||
}
|
||||
LValue = State->getLValue(Ty, SVB.makeArrayIndex(Idx), LValue);
|
||||
IsArray = true;
|
||||
}
|
||||
|
||||
|
@ -111,7 +113,7 @@ SVal ExprEngine::makeZeroElementRegion(ProgramStateRef State, SVal LValue,
|
|||
|
||||
SVal ExprEngine::computeObjectUnderConstruction(
|
||||
const Expr *E, ProgramStateRef State, const LocationContext *LCtx,
|
||||
const ConstructionContext *CC, EvalCallOptions &CallOpts) {
|
||||
const ConstructionContext *CC, EvalCallOptions &CallOpts, unsigned Idx) {
|
||||
SValBuilder &SVB = getSValBuilder();
|
||||
MemRegionManager &MRMgr = SVB.getRegionManager();
|
||||
ASTContext &ACtx = SVB.getContext();
|
||||
|
@ -125,8 +127,8 @@ SVal ExprEngine::computeObjectUnderConstruction(
|
|||
const auto *DS = DSCC->getDeclStmt();
|
||||
const auto *Var = cast<VarDecl>(DS->getSingleDecl());
|
||||
QualType Ty = Var->getType();
|
||||
return makeZeroElementRegion(State, State->getLValue(Var, LCtx), Ty,
|
||||
CallOpts.IsArrayCtorOrDtor);
|
||||
return makeElementRegion(State, State->getLValue(Var, LCtx), Ty,
|
||||
CallOpts.IsArrayCtorOrDtor, Idx);
|
||||
}
|
||||
case ConstructionContext::CXX17ElidedCopyConstructorInitializerKind:
|
||||
case ConstructionContext::SimpleConstructorInitializerKind: {
|
||||
|
@ -158,8 +160,8 @@ SVal ExprEngine::computeObjectUnderConstruction(
|
|||
}
|
||||
|
||||
QualType Ty = Field->getType();
|
||||
return makeZeroElementRegion(State, FieldVal, Ty,
|
||||
CallOpts.IsArrayCtorOrDtor);
|
||||
return makeElementRegion(State, FieldVal, Ty, CallOpts.IsArrayCtorOrDtor,
|
||||
Idx);
|
||||
}
|
||||
case ConstructionContext::NewAllocatedObjectKind: {
|
||||
if (AMgr.getAnalyzerOptions().MayInlineCXXAllocator) {
|
||||
|
@ -172,8 +174,12 @@ SVal ExprEngine::computeObjectUnderConstruction(
|
|||
// TODO: In fact, we need to call the constructor for every
|
||||
// allocated element, not just the first one!
|
||||
CallOpts.IsArrayCtorOrDtor = true;
|
||||
return loc::MemRegionVal(getStoreManager().GetElementZeroRegion(
|
||||
MR, NE->getType()->getPointeeType()));
|
||||
|
||||
auto R = MRMgr.getElementRegion(NE->getType()->getPointeeType(),
|
||||
svalBuilder.makeArrayIndex(Idx), MR,
|
||||
SVB.getContext());
|
||||
|
||||
return loc::MemRegionVal(R);
|
||||
}
|
||||
return V;
|
||||
}
|
||||
|
@ -484,10 +490,6 @@ void ExprEngine::handleConstructor(const Expr *E,
|
|||
}
|
||||
}
|
||||
|
||||
// FIXME: Handle arrays, which run the same constructor for every element.
|
||||
// For now, we just run the first constructor (which should still invalidate
|
||||
// the entire array).
|
||||
|
||||
EvalCallOptions CallOpts;
|
||||
auto C = getCurrentCFGElement().getAs<CFGConstructor>();
|
||||
assert(C || getCurrentCFGElement().getAs<CFGStmt>());
|
||||
|
@ -500,9 +502,15 @@ void ExprEngine::handleConstructor(const Expr *E,
|
|||
// Inherited constructors are always base class constructors.
|
||||
assert(CE && !CIE && "A complete constructor is inherited?!");
|
||||
|
||||
unsigned Idx = 0;
|
||||
if (CE->getType()->isArrayType()) {
|
||||
Idx = getIndexOfElementToConstruct(State, CE, LCtx).getValueOr(0u);
|
||||
State = setIndexOfElementToConstruct(State, CE, LCtx, Idx + 1);
|
||||
}
|
||||
|
||||
// The target region is found from construction context.
|
||||
std::tie(State, Target) =
|
||||
handleConstructionContext(CE, State, LCtx, CC, CallOpts);
|
||||
handleConstructionContext(CE, State, LCtx, CC, CallOpts, Idx);
|
||||
break;
|
||||
}
|
||||
case CXXConstructExpr::CK_VirtualBase: {
|
||||
|
@ -894,14 +902,39 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred,
|
|||
SVal Result = symVal;
|
||||
|
||||
if (CNE->isArray()) {
|
||||
// FIXME: allocating an array requires simulating the constructors.
|
||||
// For now, just return a symbolicated region.
|
||||
|
||||
if (const auto *NewReg = cast_or_null<SubRegion>(symVal.getAsRegion())) {
|
||||
QualType ObjTy = CNE->getType()->getPointeeType();
|
||||
// If each element is initialized by their default constructor, the field
|
||||
// values are properly placed inside the required region, however if an
|
||||
// initializer list is used, this doesn't happen automatically.
|
||||
auto *Init = CNE->getInitializer();
|
||||
bool isInitList = dyn_cast_or_null<InitListExpr>(Init);
|
||||
|
||||
QualType ObjTy =
|
||||
isInitList ? Init->getType() : CNE->getType()->getPointeeType();
|
||||
const ElementRegion *EleReg =
|
||||
getStoreManager().GetElementZeroRegion(NewReg, ObjTy);
|
||||
MRMgr.getElementRegion(ObjTy, svalBuilder.makeArrayIndex(0), NewReg,
|
||||
svalBuilder.getContext());
|
||||
Result = loc::MemRegionVal(EleReg);
|
||||
|
||||
// If the array is list initialized, we bind the initializer list to the
|
||||
// memory region here, otherwise we would lose it.
|
||||
if (isInitList) {
|
||||
Bldr.takeNodes(Pred);
|
||||
Pred = Bldr.generateNode(CNE, Pred, State);
|
||||
|
||||
SVal V = State->getSVal(Init, LCtx);
|
||||
ExplodedNodeSet evaluated;
|
||||
evalBind(evaluated, CNE, Pred, Result, V, true);
|
||||
|
||||
Bldr.takeNodes(Pred);
|
||||
Bldr.addNodes(evaluated);
|
||||
|
||||
Pred = *evaluated.begin();
|
||||
State = Pred->getState();
|
||||
}
|
||||
}
|
||||
|
||||
State = State->BindExpr(CNE, Pred->getLocationContext(), Result);
|
||||
Bldr.generateNode(CNE, Pred, State);
|
||||
return;
|
||||
|
|
|
@ -227,6 +227,13 @@ void ExprEngine::processCallExit(ExplodedNode *CEBNode) {
|
|||
|
||||
// Step 2: generate node with bound return value: CEBNode -> BindedRetNode.
|
||||
|
||||
// If this variable is set to 'true' the analyzer will evaluate the call
|
||||
// statement we are about to exit again, instead of continuing the execution
|
||||
// from the statement after the call. This is useful for non-POD type array
|
||||
// construction where the CXXConstructExpr is referenced only once in the CFG,
|
||||
// but we want to evaluate it as many times as many elements the array has.
|
||||
bool ShouldRepeatCall = false;
|
||||
|
||||
// If the callee returns an expression, bind its value to CallExpr.
|
||||
if (CE) {
|
||||
if (const ReturnStmt *RS = dyn_cast_or_null<ReturnStmt>(LastSt)) {
|
||||
|
@ -255,6 +262,12 @@ void ExprEngine::processCallExit(ExplodedNode *CEBNode) {
|
|||
SVal ThisV = state->getSVal(This);
|
||||
ThisV = state->getSVal(ThisV.castAs<Loc>());
|
||||
state = state->BindExpr(CCE, callerCtx, ThisV);
|
||||
|
||||
ShouldRepeatCall = shouldRepeatCtorCall(state, CCE, callerCtx);
|
||||
|
||||
if (!ShouldRepeatCall &&
|
||||
getIndexOfElementToConstruct(state, CCE, callerCtx))
|
||||
state = removeIndexOfElementToConstruct(state, CCE, callerCtx);
|
||||
}
|
||||
|
||||
if (const auto *CNE = dyn_cast<CXXNewExpr>(CE)) {
|
||||
|
@ -358,9 +371,10 @@ void ExprEngine::processCallExit(ExplodedNode *CEBNode) {
|
|||
|
||||
// Enqueue the next element in the block.
|
||||
for (ExplodedNodeSet::iterator PSI = Dst.begin(), PSE = Dst.end();
|
||||
PSI != PSE; ++PSI) {
|
||||
Engine.getWorkList()->enqueue(*PSI, calleeCtx->getCallSiteBlock(),
|
||||
calleeCtx->getIndex()+1);
|
||||
PSI != PSE; ++PSI) {
|
||||
unsigned Idx = calleeCtx->getIndex() + (ShouldRepeatCall ? 0 : 1);
|
||||
|
||||
Engine.getWorkList()->enqueue(*PSI, calleeCtx->getCallSiteBlock(), Idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -800,8 +814,11 @@ ExprEngine::mayInlineCallKind(const CallEvent &Call, const ExplodedNode *Pred,
|
|||
// initializers for array fields in default move/copy constructors.
|
||||
// We still allow construction into ElementRegion targets when they don't
|
||||
// represent array elements.
|
||||
if (CallOpts.IsArrayCtorOrDtor)
|
||||
return CIP_DisallowedOnce;
|
||||
if (CallOpts.IsArrayCtorOrDtor) {
|
||||
if (!shouldInlineArrayConstruction(
|
||||
dyn_cast<ArrayType>(CtorExpr->getType())))
|
||||
return CIP_DisallowedOnce;
|
||||
}
|
||||
|
||||
// Inlining constructors requires including initializers in the CFG.
|
||||
const AnalysisDeclContext *ADC = CallerSFC->getAnalysisDeclContext();
|
||||
|
@ -852,7 +869,7 @@ ExprEngine::mayInlineCallKind(const CallEvent &Call, const ExplodedNode *Pred,
|
|||
assert(ADC->getCFGBuildOptions().AddImplicitDtors && "No CFG destructors");
|
||||
(void)ADC;
|
||||
|
||||
// FIXME: We don't handle constructors or destructors for arrays properly.
|
||||
// FIXME: We don't handle destructors for arrays properly.
|
||||
if (CallOpts.IsArrayCtorOrDtor)
|
||||
return CIP_DisallowedOnce;
|
||||
|
||||
|
@ -1065,6 +1082,38 @@ bool ExprEngine::shouldInlineCall(const CallEvent &Call, const Decl *D,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool ExprEngine::shouldInlineArrayConstruction(const ArrayType *Type) {
|
||||
if (!Type)
|
||||
return false;
|
||||
|
||||
// FIXME: Handle other arrays types.
|
||||
if (const auto *CAT = dyn_cast<ConstantArrayType>(Type)) {
|
||||
unsigned Size = getContext().getConstantArrayElementCount(CAT);
|
||||
|
||||
return Size <= AMgr.options.maxBlockVisitOnPath;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ExprEngine::shouldRepeatCtorCall(ProgramStateRef State,
|
||||
const CXXConstructExpr *E,
|
||||
const LocationContext *LCtx) {
|
||||
|
||||
if (!E)
|
||||
return false;
|
||||
|
||||
auto Ty = E->getType();
|
||||
|
||||
// FIXME: Handle non constant array types
|
||||
if (const auto *CAT = dyn_cast<ConstantArrayType>(Ty)) {
|
||||
unsigned Size = getContext().getConstantArrayElementCount(CAT);
|
||||
return Size > getIndexOfElementToConstruct(State, E, LCtx);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool isTrivialObjectAssignment(const CallEvent &Call) {
|
||||
const CXXInstanceCall *ICall = dyn_cast<CXXInstanceCall>(&Call);
|
||||
if (!ICall)
|
||||
|
|
|
@ -0,0 +1,259 @@
|
|||
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -analyzer-disable-checker=cplusplus -analyzer-config c++-inlining=constructors -verify %s
|
||||
|
||||
#include "Inputs/system-header-simulator-cxx.h"
|
||||
|
||||
void clang_analyzer_eval(bool);
|
||||
|
||||
struct s {
|
||||
int x;
|
||||
int y;
|
||||
};
|
||||
|
||||
void a1(void) {
|
||||
s arr[3];
|
||||
int x = arr[0].x;
|
||||
// expected-warning@-1{{Assigned value is garbage or undefined}}
|
||||
}
|
||||
|
||||
void a2(void) {
|
||||
s arr[3];
|
||||
int x = arr[1].x;
|
||||
// expected-warning@-1{{Assigned value is garbage or undefined}}
|
||||
}
|
||||
|
||||
void a3(void) {
|
||||
s arr[3];
|
||||
int x = arr[2].x;
|
||||
// expected-warning@-1{{Assigned value is garbage or undefined}}
|
||||
}
|
||||
|
||||
struct s2 {
|
||||
int x;
|
||||
int y = 2;
|
||||
};
|
||||
|
||||
void b1(void) {
|
||||
s2 arr[3];
|
||||
|
||||
clang_analyzer_eval(arr[0].y == 2); // expected-warning{{TRUE}}
|
||||
int x = arr[0].x;
|
||||
// expected-warning@-1{{Assigned value is garbage or undefined}}
|
||||
}
|
||||
|
||||
void b2(void) {
|
||||
s2 arr[3];
|
||||
|
||||
clang_analyzer_eval(arr[1].y == 2); // expected-warning{{TRUE}}
|
||||
int x = arr[1].x;
|
||||
// expected-warning@-1{{Assigned value is garbage or undefined}}
|
||||
}
|
||||
|
||||
void b3(void) {
|
||||
s2 arr[3];
|
||||
|
||||
clang_analyzer_eval(arr[2].y == 2); // expected-warning{{TRUE}}
|
||||
int x = arr[2].x;
|
||||
// expected-warning@-1{{Assigned value is garbage or undefined}}
|
||||
}
|
||||
|
||||
void c1(void) {
|
||||
{
|
||||
s2 arr[2];
|
||||
arr[1].x = 3;
|
||||
|
||||
clang_analyzer_eval(arr[1].y == 2); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(arr[1].x == 3); // expected-warning{{TRUE}}
|
||||
}
|
||||
|
||||
{
|
||||
s2 arr[2];
|
||||
|
||||
clang_analyzer_eval(arr[1].y == 2); // expected-warning{{TRUE}}
|
||||
int x = arr[1].x;
|
||||
// expected-warning@-1{{Assigned value is garbage or undefined}}
|
||||
}
|
||||
}
|
||||
|
||||
struct s3 {
|
||||
int x = 1;
|
||||
int y = 2;
|
||||
};
|
||||
|
||||
struct s4 {
|
||||
s3 arr[2];
|
||||
s sarr[2];
|
||||
};
|
||||
|
||||
void e1(void) {
|
||||
s4 arr[2];
|
||||
|
||||
clang_analyzer_eval(arr[0].arr[0].x == 1); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(arr[0].arr[0].y == 2); // expected-warning{{TRUE}}
|
||||
|
||||
clang_analyzer_eval(arr[0].arr[1].x == 1); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(arr[0].arr[1].y == 2); // expected-warning{{TRUE}}
|
||||
|
||||
clang_analyzer_eval(arr[1].arr[0].x == 1); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(arr[1].arr[0].y == 2); // expected-warning{{TRUE}}
|
||||
|
||||
clang_analyzer_eval(arr[1].arr[1].x == 1); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(arr[1].arr[1].y == 2); // expected-warning{{TRUE}}
|
||||
|
||||
int x = arr[1].sarr[1].x;
|
||||
// expected-warning@-1{{Assigned value is garbage or undefined}}
|
||||
}
|
||||
|
||||
void f1(void) {
|
||||
s2 arr[2][2];
|
||||
|
||||
clang_analyzer_eval(arr[1][1].y == 2); // expected-warning{{TRUE}}
|
||||
int x = arr[1][1].x;
|
||||
// expected-warning@-1{{Assigned value is garbage or undefined}}
|
||||
}
|
||||
|
||||
struct s5 {
|
||||
static int c;
|
||||
int x;
|
||||
|
||||
s5() : x(c++) {}
|
||||
};
|
||||
|
||||
void g1(void) {
|
||||
// FIXME: This test requires -analyzer-disable-checker=cplusplus,
|
||||
// because of the checker's weird behaviour in case of arrays.
|
||||
// E.g.:
|
||||
// s3 *arr = new s3[4];
|
||||
// s3 *arr2 = new (arr + 1) s3[1];
|
||||
// ^~~~~~~~~~~~~~~~~~~
|
||||
// warning: 12 bytes is possibly not enough
|
||||
// for array allocation which requires
|
||||
// 4 bytes.
|
||||
|
||||
s5::c = 0;
|
||||
s5 *arr = new s5[4];
|
||||
new (arr + 1) s5[3];
|
||||
|
||||
clang_analyzer_eval(arr[0].x == 0); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(arr[1].x == 4); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(arr[2].x == 5); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(arr[3].x == 6); // expected-warning{{TRUE}}
|
||||
}
|
||||
|
||||
void g2(void) {
|
||||
s5::c = 0;
|
||||
s5 arr[4];
|
||||
|
||||
clang_analyzer_eval(arr[0].x == 0); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(arr[1].x == 1); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(arr[2].x == 2); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(arr[3].x == 3); // expected-warning{{TRUE}}
|
||||
}
|
||||
|
||||
void g3(void) {
|
||||
s5::c = 0;
|
||||
s5 arr[2][2];
|
||||
|
||||
clang_analyzer_eval(arr[0][0].x == 0); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(arr[0][1].x == 1); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(arr[1][0].x == 2); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(arr[1][1].x == 3); // expected-warning{{TRUE}}
|
||||
}
|
||||
|
||||
void h1(void) {
|
||||
s5::c = 0;
|
||||
s5 a[2][2], b[2][2];
|
||||
|
||||
clang_analyzer_eval(a[0][0].x == 0); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(a[0][1].x == 1); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(a[1][0].x == 2); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(a[1][1].x == 3); // expected-warning{{TRUE}}
|
||||
|
||||
clang_analyzer_eval(b[0][0].x == 4); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(b[0][1].x == 5); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(b[1][0].x == 6); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(b[1][1].x == 7); // expected-warning{{TRUE}}
|
||||
}
|
||||
|
||||
void h2(void) {
|
||||
s a[2][2], b[2][2];
|
||||
|
||||
int x = a[1][1].x;
|
||||
// expected-warning@-1{{Assigned value is garbage or undefined}}
|
||||
}
|
||||
|
||||
void h3(void) {
|
||||
s a[2][2], b[2][2];
|
||||
|
||||
int x = b[1][1].y;
|
||||
// expected-warning@-1{{Assigned value is garbage or undefined}}
|
||||
}
|
||||
|
||||
struct Base {
|
||||
int x;
|
||||
int y;
|
||||
|
||||
Base(int x, int y) : x(x), y(y) {}
|
||||
};
|
||||
|
||||
struct Derived : public Base {
|
||||
int i;
|
||||
int j;
|
||||
|
||||
Derived(int x, int y, int i, int j) : Base(x, y), i(i), j(j) {}
|
||||
};
|
||||
|
||||
void delegate(void) {
|
||||
Derived arr[2] = {{1, 2, 3, 4}, {5, 6, 7, 8}};
|
||||
|
||||
clang_analyzer_eval(arr[0].x == 1); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(arr[0].y == 2); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(arr[0].i == 3); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(arr[0].j == 4); // expected-warning{{TRUE}}
|
||||
|
||||
clang_analyzer_eval(arr[1].x == 5); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(arr[1].y == 6); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(arr[1].i == 7); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(arr[1].j == 8); // expected-warning{{TRUE}}
|
||||
}
|
||||
|
||||
void delegate_heap(void) {
|
||||
Derived *arr = new Derived[2]{{1, 2, 3, 4}, {5, 6, 7, 8}};
|
||||
|
||||
clang_analyzer_eval(arr[0].x == 1); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(arr[0].y == 2); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(arr[0].i == 3); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(arr[0].j == 4); // expected-warning{{TRUE}}
|
||||
|
||||
clang_analyzer_eval(arr[1].x == 5); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(arr[1].y == 6); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(arr[1].i == 7); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(arr[1].j == 8); // expected-warning{{TRUE}}
|
||||
}
|
||||
|
||||
struct Member {
|
||||
int x;
|
||||
int y;
|
||||
};
|
||||
|
||||
struct Parent {
|
||||
Member arr[2];
|
||||
|
||||
Parent() : arr{{1, 2}, {3, 4}} {}
|
||||
};
|
||||
|
||||
void member() {
|
||||
Parent arr[2];
|
||||
|
||||
// FIXME: Ideally these are TRUE, but at the moment InitListExpr has no
|
||||
// knowledge about where the initializer list is used, so we can't bind
|
||||
// the initializer list to the required region.
|
||||
clang_analyzer_eval(arr[0].arr[0].x == 1); // expected-warning{{UNKNOWN}}
|
||||
clang_analyzer_eval(arr[0].arr[0].y == 2); // expected-warning{{UNKNOWN}}
|
||||
clang_analyzer_eval(arr[0].arr[1].x == 3); // expected-warning{{UNKNOWN}}
|
||||
clang_analyzer_eval(arr[0].arr[1].y == 4); // expected-warning{{UNKNOWN}}
|
||||
|
||||
clang_analyzer_eval(arr[1].arr[0].x == 1); // expected-warning{{UNKNOWN}}
|
||||
clang_analyzer_eval(arr[1].arr[0].y == 2); // expected-warning{{UNKNOWN}}
|
||||
clang_analyzer_eval(arr[1].arr[1].x == 3); // expected-warning{{UNKNOWN}}
|
||||
clang_analyzer_eval(arr[1].arr[1].y == 4); // expected-warning{{UNKNOWN}}
|
||||
}
|
|
@ -581,12 +581,11 @@ namespace ZeroInitialization {
|
|||
}
|
||||
|
||||
void testArrayNew() {
|
||||
// FIXME: Pending proper implementation of constructors for 'new[]'.
|
||||
raw_pair *p = new raw_pair[2]();
|
||||
clang_analyzer_eval(p[0].p1 == 0); // expected-warning{{UNKNOWN}}
|
||||
clang_analyzer_eval(p[0].p2 == 0); // expected-warning{{UNKNOWN}}
|
||||
clang_analyzer_eval(p[1].p1 == 0); // expected-warning{{UNKNOWN}}
|
||||
clang_analyzer_eval(p[1].p2 == 0); // expected-warning{{UNKNOWN}}
|
||||
clang_analyzer_eval(p[0].p1 == 0); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(p[0].p2 == 0); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(p[1].p1 == 0); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(p[1].p2 == 0); // expected-warning{{TRUE}}
|
||||
}
|
||||
|
||||
struct initializing_pair {
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
// RUN: %clang_analyze_cc1 %s \
|
||||
// RUN: -analyzer-checker=debug.AnalysisOrder \
|
||||
// RUN: -analyzer-config debug.AnalysisOrder:PreCall=true \
|
||||
// RUN: -analyzer-config debug.AnalysisOrder:PostCall=true \
|
||||
// RUN: 2>&1 | FileCheck %s
|
||||
|
||||
// This test ensures that eval::Call event will be triggered for constructors.
|
||||
|
||||
class C {
|
||||
public:
|
||||
C(){};
|
||||
};
|
||||
|
||||
void stack() {
|
||||
C arr[4];
|
||||
C *arr2 = new C[4];
|
||||
C arr3[2][2];
|
||||
}
|
||||
|
||||
// C arr[4];
|
||||
// CHECK: PreCall (C::C) [CXXConstructorCall]
|
||||
// CHECK-NEXT: PostCall (C::C) [CXXConstructorCall]
|
||||
// CHECK-NEXT: PreCall (C::C) [CXXConstructorCall]
|
||||
// CHECK-NEXT: PostCall (C::C) [CXXConstructorCall]
|
||||
// CHECK-NEXT: PreCall (C::C) [CXXConstructorCall]
|
||||
// CHECK-NEXT: PostCall (C::C) [CXXConstructorCall]
|
||||
// CHECK-NEXT: PreCall (C::C) [CXXConstructorCall]
|
||||
// CHECK-NEXT: PostCall (C::C) [CXXConstructorCall]
|
||||
//
|
||||
// C *arr2 = new C[4];
|
||||
// CHECK-NEXT: PreCall (operator new[]) [CXXAllocatorCall]
|
||||
// CHECK-NEXT: PostCall (operator new[]) [CXXAllocatorCall]
|
||||
// CHECK-NEXT: PreCall (C::C) [CXXConstructorCall]
|
||||
// CHECK-NEXT: PostCall (C::C) [CXXConstructorCall]
|
||||
// CHECK-NEXT: PreCall (C::C) [CXXConstructorCall]
|
||||
// CHECK-NEXT: PostCall (C::C) [CXXConstructorCall]
|
||||
// CHECK-NEXT: PreCall (C::C) [CXXConstructorCall]
|
||||
// CHECK-NEXT: PostCall (C::C) [CXXConstructorCall]
|
||||
// CHECK-NEXT: PreCall (C::C) [CXXConstructorCall]
|
||||
// CHECK-NEXT: PostCall (C::C) [CXXConstructorCall]
|
||||
//
|
||||
// C arr3[2][2];
|
||||
// CHECK-NEXT: PreCall (C::C) [CXXConstructorCall]
|
||||
// CHECK-NEXT: PostCall (C::C) [CXXConstructorCall]
|
||||
// CHECK-NEXT: PreCall (C::C) [CXXConstructorCall]
|
||||
// CHECK-NEXT: PostCall (C::C) [CXXConstructorCall]
|
||||
// CHECK-NEXT: PreCall (C::C) [CXXConstructorCall]
|
||||
// CHECK-NEXT: PostCall (C::C) [CXXConstructorCall]
|
||||
// CHECK-NEXT: PreCall (C::C) [CXXConstructorCall]
|
||||
// CHECK-NEXT: PostCall (C::C) [CXXConstructorCall]
|
|
@ -140,10 +140,8 @@ void testArrayInvalidation() {
|
|||
IntWrapper arr[2];
|
||||
|
||||
// There should be no undefined value warnings here.
|
||||
// Eventually these should be TRUE as well, but right now
|
||||
// we can't handle array constructors.
|
||||
clang_analyzer_eval(arr[0].x == 0); // expected-warning{{UNKNOWN}}
|
||||
clang_analyzer_eval(arr[1].x == 0); // expected-warning{{UNKNOWN}}
|
||||
clang_analyzer_eval(arr[0].x == 0); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(arr[1].x == 0); // expected-warning{{TRUE}}
|
||||
|
||||
arr[0].x = &i;
|
||||
arr[1].x = &j;
|
||||
|
@ -312,10 +310,8 @@ namespace MultidimensionalArrays {
|
|||
IntWrapper arr[2][2];
|
||||
|
||||
// There should be no undefined value warnings here.
|
||||
// Eventually these should be TRUE as well, but right now
|
||||
// we can't handle array constructors.
|
||||
clang_analyzer_eval(arr[0][0].x == 0); // expected-warning{{UNKNOWN}}
|
||||
clang_analyzer_eval(arr[1][1].x == 0); // expected-warning{{UNKNOWN}}
|
||||
clang_analyzer_eval(arr[0][0].x == 0); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(arr[1][1].x == 0); // expected-warning{{TRUE}}
|
||||
|
||||
arr[0][0].x = &i;
|
||||
arr[1][1].x = &j;
|
||||
|
@ -597,5 +593,5 @@ void overrideLeak() {
|
|||
void overrideDoubleDelete() {
|
||||
auto *a = new CustomOperators();
|
||||
delete a;
|
||||
delete a; // expected-warning@581 {{Attempt to free released memory}}
|
||||
delete a; // expected-warning@577 {{Attempt to free released memory}}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ Node0x1 [shape=record,label=
|
|||
"constraints": null,
|
||||
"dynamic_types": null,
|
||||
"constructing_objects": null,
|
||||
"index_of_element": null,
|
||||
"environment": null,
|
||||
"checker_messages": [
|
||||
{ "checker": "alpha.core.FooChecker", "messages": [
|
||||
|
|
|
@ -17,6 +17,7 @@ Node0x1 [shape=record,label=
|
|||
"constraints": null,
|
||||
"dynamic_types": null,
|
||||
"constructing_objects": null,
|
||||
"index_of_element": null,
|
||||
"checker_messages": [
|
||||
{ "checker": "FooChecker", "messages": [
|
||||
"Foo: Bar"
|
||||
|
@ -75,6 +76,7 @@ Node0x4 [shape=record,label=
|
|||
"constraints": null,
|
||||
"dynamic_types": null,
|
||||
"constructing_objects": null,
|
||||
"index_of_element": null,
|
||||
"checker_messages": [
|
||||
{ "checker": "FooChecker", "messages": [
|
||||
"Foo: Bar",
|
||||
|
|
|
@ -24,6 +24,7 @@ Node0x1 [shape=record,label=
|
|||
"environment": null,
|
||||
"dynamic_types": null,
|
||||
"constructing_objects": null,
|
||||
"index_of_element": null,
|
||||
"checker_messages": null,
|
||||
"constraints": [
|
||||
{ "symbol": "reg_$0<x>", "range": "{ [0, 0] }" }
|
||||
|
|
|
@ -17,6 +17,7 @@ Node0x1 [shape=record,label=
|
|||
"environment": null,
|
||||
"dynamic_types": null,
|
||||
"constructing_objects": null,
|
||||
"index_of_element": null,
|
||||
"checker_messages": null,
|
||||
"constraints": [
|
||||
{ "symbol": "reg_$0<x>", "range": "{ [0, 10] }" }
|
||||
|
@ -55,6 +56,7 @@ Node0x3 [shape=record,label=
|
|||
"environment": null,
|
||||
"dynamic_types": null,
|
||||
"constructing_objects": null,
|
||||
"index_of_element": null,
|
||||
"checker_messages": null,
|
||||
"constraints": [
|
||||
{ "symbol": "reg_$0<x>", "range": "{ [0, 5] }" }
|
||||
|
@ -83,6 +85,7 @@ Node0x5 [shape=record,label=
|
|||
"constraints": null,
|
||||
"dynamic_types": null,
|
||||
"constructing_objects": null,
|
||||
"index_of_element": null,
|
||||
"checker_messages": null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ Node0x1 [shape=record,label=
|
|||
"constraints": null,
|
||||
"dynamic_types": null,
|
||||
"constructing_objects": null,
|
||||
"index_of_element": null,
|
||||
"checker_messages": null,
|
||||
"environment": {
|
||||
"pointer": "0x2",
|
||||
|
|
|
@ -18,6 +18,7 @@ Node0x1 [shape=record,label=
|
|||
"constraints": null,
|
||||
"dynamic_types": null,
|
||||
"constructing_objects": null,
|
||||
"index_of_element": null,
|
||||
"checker_messages": null,
|
||||
"environment": {
|
||||
"pointer": "0x2",
|
||||
|
@ -73,6 +74,7 @@ Node0x6 [shape=record,label=
|
|||
"constraints": null,
|
||||
"dynamic_types": null,
|
||||
"constructing_objects": null,
|
||||
"index_of_element": null,
|
||||
"checker_messages": null,
|
||||
"environment": {
|
||||
"pointer": "0x2",
|
||||
|
@ -122,6 +124,7 @@ Node0x9 [shape=record,label=
|
|||
"constraints": null,
|
||||
"dynamic_types": null,
|
||||
"constructing_objects": null,
|
||||
"index_of_element": null,
|
||||
"checker_messages": null,
|
||||
"environment": {
|
||||
"pointer": "0x2",
|
||||
|
|
|
@ -34,6 +34,7 @@ Node0x1 [shape=record,label=
|
|||
"constraints": null,
|
||||
"dynamic_types": null,
|
||||
"constructing_objects": null,
|
||||
"index_of_element": null,
|
||||
"checker_messages": null,
|
||||
"store": {
|
||||
"pointer": "0x2",
|
||||
|
|
|
@ -20,6 +20,7 @@ Node0x1 [shape=record,label=
|
|||
"constraints": null,
|
||||
"dynamic_types": null,
|
||||
"constructing_objects": null,
|
||||
"index_of_element": null,
|
||||
"checker_messages": null,
|
||||
"store": {
|
||||
"pointer": "0x2",
|
||||
|
@ -74,6 +75,7 @@ Node0x4 [shape=record,label=
|
|||
"constraints": null,
|
||||
"dynamic_types": null,
|
||||
"constructing_objects": null,
|
||||
"index_of_element": null,
|
||||
"checker_messages": null,
|
||||
"store": {
|
||||
"pointer": "0x5",
|
||||
|
|
|
@ -24,6 +24,7 @@ Node0x1 [shape=record,label=
|
|||
"constraints": null,
|
||||
"dynamic_types": null,
|
||||
"constructing_objects": null,
|
||||
"index_of_element": null,
|
||||
"checker_messages": [
|
||||
{ "checker": "foo", "messages": ["bar"] }
|
||||
],
|
||||
|
|
|
@ -47,6 +47,7 @@ void foo(int x) {
|
|||
// CHECK-NEXT: "dynamic_types": null,
|
||||
// CHECK-NEXT: "dynamic_casts": null,
|
||||
// CHECK-NEXT: "constructing_objects": null,
|
||||
// CHECK-NEXT: "index_of_element": null,
|
||||
// CHECK-NEXT: "checker_messages": null
|
||||
// CHECK-NEXT: }
|
||||
|
||||
|
|
|
@ -59,12 +59,9 @@ void test_automatic_aggregate() {
|
|||
init_in_body a2[1];
|
||||
init_default_member a3[1];
|
||||
|
||||
// FIXME: Should be TRUE, not FALSE.
|
||||
clang_analyzer_eval(a1[0].a == 1); // expected-warning {{TRUE}} expected-warning {{FALSE}}
|
||||
// FIXME: Should be TRUE, not FALSE.
|
||||
clang_analyzer_eval(a2[0].a == 1); // expected-warning {{TRUE}} expected-warning {{FALSE}}
|
||||
// FIXME: Should be TRUE, not FALSE.
|
||||
clang_analyzer_eval(a3[0].a == 1); // expected-warning {{TRUE}} expected-warning {{FALSE}}
|
||||
clang_analyzer_eval(a1[0].a == 1); // expected-warning {{TRUE}}
|
||||
clang_analyzer_eval(a2[0].a == 1); // expected-warning {{TRUE}}
|
||||
clang_analyzer_eval(a3[0].a == 1); // expected-warning {{TRUE}}
|
||||
}
|
||||
|
||||
void test_dynamic_aggregate() {
|
||||
|
@ -73,12 +70,9 @@ void test_dynamic_aggregate() {
|
|||
auto *a2 = new init_in_body[1];
|
||||
auto *a3 = new init_default_member[1];
|
||||
|
||||
// FIXME: Should be TRUE, not FALSE.
|
||||
clang_analyzer_eval(a1[0].a == 1); // expected-warning {{TRUE}} expected-warning {{FALSE}}
|
||||
// FIXME: Should be TRUE, not FALSE.
|
||||
clang_analyzer_eval(a2[0].a == 1); // expected-warning {{TRUE}} expected-warning {{FALSE}}
|
||||
// FIXME: Should be TRUE, not FALSE.
|
||||
clang_analyzer_eval(a3[0].a == 1); // expected-warning {{TRUE}} expected-warning {{FALSE}}
|
||||
clang_analyzer_eval(a1[0].a == 1); // expected-warning {{TRUE}}
|
||||
clang_analyzer_eval(a2[0].a == 1); // expected-warning {{TRUE}}
|
||||
clang_analyzer_eval(a3[0].a == 1); // expected-warning {{TRUE}}
|
||||
|
||||
delete[] a1;
|
||||
delete[] a2;
|
||||
|
|
|
@ -25,9 +25,14 @@ void checkNewPOD() {
|
|||
|
||||
void checkNewArray() {
|
||||
S *s = new S[10];
|
||||
// FIXME: Should be true once we inline array constructors.
|
||||
|
||||
// FIXME: Handle big array construction
|
||||
clang_analyzer_eval(s[0].x == 1); // expected-warning{{UNKNOWN}}
|
||||
clang_analyzer_eval(s[1].x == 1); // expected-warning{{UNKNOWN}}
|
||||
|
||||
s = new S[4];
|
||||
clang_analyzer_eval(s[0].x == 1); // expected-warning{{TRUE}}
|
||||
clang_analyzer_eval(s[1].x == 1); // expected-warning{{TRUE}}
|
||||
}
|
||||
|
||||
struct NullS {
|
||||
|
|
|
@ -323,8 +323,8 @@ void testArrayNull() {
|
|||
|
||||
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
|
||||
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}}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// RUN: %clang_analyze_cc1 -std=c++11 -analyzer-checker=core,alpha.core,debug.ExprInspection -verify %s
|
||||
// RUN: %clang_analyze_cc1 -std=c++11 -analyzer-checker=core,alpha.core,debug.ExprInspection -analyzer-output=text -verify %s
|
||||
void clang_analyzer_eval(bool);
|
||||
|
||||
struct X0 { };
|
||||
|
@ -29,6 +29,7 @@ struct IntComparable {
|
|||
|
||||
void testMemberOperator(IntComparable B) {
|
||||
clang_analyzer_eval(B == 0); // expected-warning{{TRUE}}
|
||||
// expected-note@-1{{TRUE}}
|
||||
}
|
||||
|
||||
|
||||
|
@ -46,7 +47,9 @@ namespace UserDefinedConversions {
|
|||
|
||||
void test(const Convertible &obj) {
|
||||
clang_analyzer_eval((int)obj == 42); // expected-warning{{TRUE}}
|
||||
// expected-note@-1{{TRUE}}
|
||||
clang_analyzer_eval(obj); // expected-warning{{TRUE}}
|
||||
// expected-note@-1{{TRUE}}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,7 +85,15 @@ namespace RValues {
|
|||
// Force a cache-out when we try to conjure a temporary region for the operator call.
|
||||
// ...then, don't crash.
|
||||
clang_analyzer_eval(+(coin ? getSmallOpaque() : getSmallOpaque())); // expected-warning{{UNKNOWN}}
|
||||
// expected-note@-1{{Assuming 'coin' is 0}}
|
||||
// expected-note@-2{{'?' condition is false}}
|
||||
// expected-note@-3{{UNKNOWN}}
|
||||
// expected-note@-4{{Assuming 'coin' is 0}}
|
||||
// expected-note@-5{{'?' condition is false}}
|
||||
clang_analyzer_eval(+(coin ? getLargeOpaque() : getLargeOpaque())); // expected-warning{{UNKNOWN}}
|
||||
// expected-note@-1{{'coin' is 0}}
|
||||
// expected-note@-2{{'?' condition is false}}
|
||||
// expected-note@-3{{UNKNOWN}}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,31 +113,53 @@ namespace SynthesizedAssignment {
|
|||
|
||||
// This used to produce a warning about the iteration variable in the
|
||||
// synthesized assignment operator being undefined.
|
||||
//
|
||||
// Note: The warning we want to avoid can be found in https://bugs.llvm.org/show_bug.cgi?id=16745.
|
||||
// Back in the day, this function was created we couldn't evaluate non-POD type array construction,
|
||||
// so we couldn't evaluate the copy assignment either, hence we didn't detect that a field is
|
||||
// uninitialized.
|
||||
void testNoWarning() {
|
||||
|
||||
B v, u;
|
||||
u = v;
|
||||
u = v; // expected-warning@110{{Assigned value is garbage or undefined}}
|
||||
// expected-note@-1{{Calling defaulted copy assignment operator for 'B'}}
|
||||
// expected-note@110{{Assigned value is garbage or undefined}}
|
||||
}
|
||||
|
||||
void testNoWarningMove() {
|
||||
B v, u;
|
||||
u = static_cast<B &&>(v);
|
||||
u = static_cast<B &&>(v); // expected-warning@111{{Assigned value is garbage or undefined}}
|
||||
// expected-note@-1{{Calling defaulted move assignment operator for 'B'}}
|
||||
// expected-note@111{{Assigned value is garbage or undefined}}
|
||||
}
|
||||
|
||||
void testConsistency() {
|
||||
B v, u;
|
||||
v.x = 0;
|
||||
v.a[0].a = 24;
|
||||
v.a[1].a = 47;
|
||||
v.a[2].a = 42;
|
||||
u = v;
|
||||
clang_analyzer_eval(u.a[0].a == -24); // expected-warning{{TRUE}}
|
||||
// expected-note@-1{{TRUE}}
|
||||
clang_analyzer_eval(u.a[1].a == -47); // expected-warning{{TRUE}}
|
||||
// expected-note@-1{{TRUE}}
|
||||
clang_analyzer_eval(u.a[2].a == -42); // expected-warning{{TRUE}}
|
||||
// expected-note@-1{{TRUE}}
|
||||
}
|
||||
|
||||
void testConsistencyMove() {
|
||||
B v, u;
|
||||
v.x = 0;
|
||||
v.a[0].a = 24;
|
||||
v.a[1].a = 47;
|
||||
v.a[2].a = 42;
|
||||
u = static_cast<B &&>(v);
|
||||
clang_analyzer_eval(u.a[0].a == 25); // expected-warning{{TRUE}}
|
||||
// expected-note@-1{{TRUE}}
|
||||
clang_analyzer_eval(u.a[1].a == 48); // expected-warning{{TRUE}}
|
||||
// expected-note@-1{{TRUE}}
|
||||
clang_analyzer_eval(u.a[2].a == 43); // expected-warning{{TRUE}}
|
||||
// expected-note@-1{{TRUE}}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -144,7 +144,8 @@ class EnvironmentFrame:
|
|||
|
||||
|
||||
# A deserialized Environment. This class can also hold other entities that
|
||||
# are similar to Environment, such as Objects Under Construction.
|
||||
# are similar to Environment, such as Objects Under Construction or
|
||||
# Indices Of Elements Under Construction.
|
||||
class GenericEnvironment:
|
||||
def __init__(self, json_e):
|
||||
self.frames = [EnvironmentFrame(f) for f in json_e]
|
||||
|
@ -269,6 +270,7 @@ class ProgramState:
|
|||
'constraints': None,
|
||||
'dynamic_types': None,
|
||||
'constructing_objects': None,
|
||||
'index_of_element': None,
|
||||
'checker_messages': None
|
||||
}
|
||||
|
||||
|
@ -296,6 +298,10 @@ class ProgramState:
|
|||
GenericEnvironment(json_ps['constructing_objects']) \
|
||||
if json_ps['constructing_objects'] is not None else None
|
||||
|
||||
self.index_of_element = \
|
||||
GenericEnvironment(json_ps['index_of_element']) \
|
||||
if json_ps['index_of_element'] is not None else None
|
||||
|
||||
self.checker_messages = CheckerMessages(json_ps['checker_messages']) \
|
||||
if json_ps['checker_messages'] is not None else None
|
||||
|
||||
|
@ -796,6 +802,9 @@ class DotDumpVisitor:
|
|||
self.visit_environment_in_state('constructing_objects',
|
||||
'Objects Under Construction',
|
||||
s, prev_s)
|
||||
self.visit_environment_in_state('index_of_element',
|
||||
'Indices Of Elements Under Construction',
|
||||
s, prev_s)
|
||||
self.visit_checker_messages_in_state(s, prev_s)
|
||||
|
||||
def visit_node(self, node):
|
||||
|
|
Loading…
Reference in New Issue