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);
|
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
|
/// By looking at a certain item that may be potentially part of an object's
|
||||||
/// ConstructionContext, retrieve such object's location. A particular
|
/// ConstructionContext, retrieve such object's location. A particular
|
||||||
/// statement can be transparently passed as \p Item in most cases.
|
/// 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
|
/// fully implemented it sometimes indicates that it failed via its
|
||||||
/// out-parameter CallOpts; in such cases a fake temporary region is
|
/// out-parameter CallOpts; in such cases a fake temporary region is
|
||||||
/// returned, which is better than nothing but does not represent
|
/// returned, which is better than nothing but does not represent
|
||||||
/// the actual behavior of the program.
|
/// the actual behavior of the program. The Idx parameter is used if we
|
||||||
SVal computeObjectUnderConstruction(
|
/// construct an array of objects. In that case it points to the index
|
||||||
const Expr *E, ProgramStateRef State, const LocationContext *LCtx,
|
/// of the continous memory region.
|
||||||
const ConstructionContext *CC, EvalCallOptions &CallOpts);
|
/// 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
|
/// Update the program state with all the path-sensitive information
|
||||||
/// that's necessary to perform construction of an object with a given
|
/// that's necessary to perform construction of an object with a given
|
||||||
|
@ -724,12 +738,16 @@ public:
|
||||||
|
|
||||||
/// A convenient wrapper around computeObjectUnderConstruction
|
/// A convenient wrapper around computeObjectUnderConstruction
|
||||||
/// and updateObjectsUnderConstruction.
|
/// and updateObjectsUnderConstruction.
|
||||||
std::pair<ProgramStateRef, SVal> handleConstructionContext(
|
std::pair<ProgramStateRef, SVal>
|
||||||
const Expr *E, ProgramStateRef State, const LocationContext *LCtx,
|
handleConstructionContext(const Expr *E, ProgramStateRef State,
|
||||||
const ConstructionContext *CC, EvalCallOptions &CallOpts) {
|
const LocationContext *LCtx,
|
||||||
SVal V = computeObjectUnderConstruction(E, State, LCtx, CC, CallOpts);
|
const ConstructionContext *CC,
|
||||||
return std::make_pair(
|
EvalCallOptions &CallOpts, unsigned Idx = 0) {
|
||||||
updateObjectsUnderConstruction(V, E, State, LCtx, CC, CallOpts), V);
|
|
||||||
|
SVal V = computeObjectUnderConstruction(E, State, LCtx, CC, CallOpts, Idx);
|
||||||
|
State = updateObjectsUnderConstruction(V, E, State, LCtx, CC, CallOpts);
|
||||||
|
|
||||||
|
return std::make_pair(State, V);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -796,6 +814,15 @@ private:
|
||||||
const ExplodedNode *Pred,
|
const ExplodedNode *Pred,
|
||||||
const EvalCallOptions &CallOpts = {});
|
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,
|
void inlineCall(WorkList *WList, const CallEvent &Call, const Decl *D,
|
||||||
NodeBuilder &Bldr, ExplodedNode *Pred, ProgramStateRef State);
|
NodeBuilder &Bldr, ExplodedNode *Pred, ProgramStateRef State);
|
||||||
|
|
||||||
|
@ -838,7 +865,7 @@ private:
|
||||||
const Expr *InitWithAdjustments, const Expr *Result = nullptr,
|
const Expr *InitWithAdjustments, const Expr *Result = nullptr,
|
||||||
const SubRegion **OutRegionWithAdjustments = 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
|
/// multi-dimensional) array, for the purposes of element construction or
|
||||||
/// destruction.
|
/// destruction.
|
||||||
///
|
///
|
||||||
|
@ -846,8 +873,8 @@ private:
|
||||||
///
|
///
|
||||||
/// If the type is not an array type at all, the original value is returned.
|
/// If the type is not an array type at all, the original value is returned.
|
||||||
/// Otherwise the "IsArray" flag is set.
|
/// Otherwise the "IsArray" flag is set.
|
||||||
static SVal makeZeroElementRegion(ProgramStateRef State, SVal LValue,
|
static SVal makeElementRegion(ProgramStateRef State, SVal LValue,
|
||||||
QualType &Ty, bool &IsArray);
|
QualType &Ty, bool &IsArray, unsigned Idx = 0);
|
||||||
|
|
||||||
/// For a DeclStmt or CXXInitCtorInitializer, walk backward in the current CFG
|
/// For a DeclStmt or CXXInitCtorInitializer, walk backward in the current CFG
|
||||||
/// block to find the constructor expression that directly constructed into
|
/// block to find the constructor expression that directly constructed into
|
||||||
|
@ -878,6 +905,17 @@ public:
|
||||||
const ObjCForCollectionStmt *O,
|
const ObjCForCollectionStmt *O,
|
||||||
const LocationContext *LC);
|
const LocationContext *LC);
|
||||||
private:
|
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
|
/// Store the location of a C++ object corresponding to a statement
|
||||||
/// until the statement is actually encountered. For example, if a DeclStmt
|
/// until the statement is actually encountered. For example, if a DeclStmt
|
||||||
/// has CXXConstructExpr as its initializer, the object would be considered
|
/// has CXXConstructExpr as its initializer, the object would be considered
|
||||||
|
|
|
@ -185,6 +185,17 @@ typedef llvm::ImmutableMap<ConstructedObjectKey, SVal>
|
||||||
REGISTER_TRAIT_WITH_PROGRAMSTATE(ObjectsUnderConstruction,
|
REGISTER_TRAIT_WITH_PROGRAMSTATE(ObjectsUnderConstruction,
|
||||||
ObjectsUnderConstructionMap)
|
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.
|
// Engine construction and deletion.
|
||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
@ -441,16 +452,65 @@ ProgramStateRef ExprEngine::createTemporaryRegionIfNeeded(
|
||||||
return State;
|
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
|
ProgramStateRef
|
||||||
ExprEngine::addObjectUnderConstruction(ProgramStateRef State,
|
ExprEngine::addObjectUnderConstruction(ProgramStateRef State,
|
||||||
const ConstructionContextItem &Item,
|
const ConstructionContextItem &Item,
|
||||||
const LocationContext *LC, SVal V) {
|
const LocationContext *LC, SVal V) {
|
||||||
ConstructedObjectKey Key(Item, LC->getStackFrame());
|
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
|
// FIXME: Currently the state might already contain the marker due to
|
||||||
// incorrect handling of temporaries bound to default parameters.
|
// incorrect handling of temporaries bound to default parameters.
|
||||||
assert(!State->get<ObjectsUnderConstruction>(Key) ||
|
// The state will already contain the marker if we construct elements
|
||||||
Key.getItem().getKind() ==
|
// in an array, as we visit the same statement multiple times before
|
||||||
ConstructionContextItem::TemporaryDestructorKind);
|
// 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);
|
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,
|
void ExprEngine::printJson(raw_ostream &Out, ProgramStateRef State,
|
||||||
const LocationContext *LCtx, const char *NL,
|
const LocationContext *LCtx, const char *NL,
|
||||||
unsigned int Space, bool IsDot) const {
|
unsigned int Space, bool IsDot) const {
|
||||||
|
@ -600,6 +723,23 @@ void ExprEngine::printJson(raw_ostream &Out, ProgramStateRef State,
|
||||||
Out << "null," << NL;
|
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,
|
getCheckerManager().runCheckersForPrintStateJson(Out, State, NL, Space,
|
||||||
IsDot);
|
IsDot);
|
||||||
}
|
}
|
||||||
|
@ -961,8 +1101,9 @@ void ExprEngine::ProcessAutomaticObjDtor(const CFGAutomaticObjDtor Dtor,
|
||||||
// This workaround will just run the first destructor (which will still
|
// This workaround will just run the first destructor (which will still
|
||||||
// invalidate the entire array).
|
// invalidate the entire array).
|
||||||
EvalCallOptions CallOpts;
|
EvalCallOptions CallOpts;
|
||||||
Region = makeZeroElementRegion(state, loc::MemRegionVal(Region), varType,
|
Region = makeElementRegion(state, loc::MemRegionVal(Region), varType,
|
||||||
CallOpts.IsArrayCtorOrDtor).getAsRegion();
|
CallOpts.IsArrayCtorOrDtor)
|
||||||
|
.getAsRegion();
|
||||||
|
|
||||||
VisitCXXDestructor(varType, Region, Dtor.getTriggerStmt(),
|
VisitCXXDestructor(varType, Region, Dtor.getTriggerStmt(),
|
||||||
/*IsBase=*/false, Pred, Dst, CallOpts);
|
/*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
|
// This workaround will just run the first destructor (which will still
|
||||||
// invalidate the entire array).
|
// invalidate the entire array).
|
||||||
EvalCallOptions CallOpts;
|
EvalCallOptions CallOpts;
|
||||||
FieldVal = makeZeroElementRegion(State, FieldVal, T,
|
FieldVal = makeElementRegion(State, FieldVal, T, CallOpts.IsArrayCtorOrDtor);
|
||||||
CallOpts.IsArrayCtorOrDtor);
|
|
||||||
|
|
||||||
VisitCXXDestructor(T, FieldVal.getAsRegion(), CurDtor->getBody(),
|
VisitCXXDestructor(T, FieldVal.getAsRegion(), CurDtor->getBody(),
|
||||||
/*IsBase=*/false, Pred, Dst, CallOpts);
|
/*IsBase=*/false, Pred, Dst, CallOpts);
|
||||||
|
@ -1105,7 +1245,7 @@ void ExprEngine::ProcessTemporaryDtor(const CFGTemporaryDtor D,
|
||||||
CallOpts.IsArrayCtorOrDtor = true;
|
CallOpts.IsArrayCtorOrDtor = true;
|
||||||
}
|
}
|
||||||
} else {
|
} 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,
|
// but for now we don't have the respective construction contexts,
|
||||||
// so MR would always be null in this case. Do nothing for now.
|
// 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::makeElementRegion(ProgramStateRef State, SVal LValue,
|
||||||
SVal ExprEngine::makeZeroElementRegion(ProgramStateRef State, SVal LValue,
|
QualType &Ty, bool &IsArray, unsigned Idx) {
|
||||||
QualType &Ty, bool &IsArray) {
|
|
||||||
SValBuilder &SVB = State->getStateManager().getSValBuilder();
|
SValBuilder &SVB = State->getStateManager().getSValBuilder();
|
||||||
ASTContext &Ctx = SVB.getContext();
|
ASTContext &Ctx = SVB.getContext();
|
||||||
|
|
||||||
while (const ArrayType *AT = Ctx.getAsArrayType(Ty)) {
|
if (const ArrayType *AT = Ctx.getAsArrayType(Ty)) {
|
||||||
Ty = AT->getElementType();
|
while (AT) {
|
||||||
LValue = State->getLValue(Ty, SVB.makeZeroArrayIndex(), LValue);
|
Ty = AT->getElementType();
|
||||||
|
AT = dyn_cast<ArrayType>(AT->getElementType());
|
||||||
|
}
|
||||||
|
LValue = State->getLValue(Ty, SVB.makeArrayIndex(Idx), LValue);
|
||||||
IsArray = true;
|
IsArray = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,7 +113,7 @@ SVal ExprEngine::makeZeroElementRegion(ProgramStateRef State, SVal LValue,
|
||||||
|
|
||||||
SVal ExprEngine::computeObjectUnderConstruction(
|
SVal ExprEngine::computeObjectUnderConstruction(
|
||||||
const Expr *E, ProgramStateRef State, const LocationContext *LCtx,
|
const Expr *E, ProgramStateRef State, const LocationContext *LCtx,
|
||||||
const ConstructionContext *CC, EvalCallOptions &CallOpts) {
|
const ConstructionContext *CC, EvalCallOptions &CallOpts, unsigned Idx) {
|
||||||
SValBuilder &SVB = getSValBuilder();
|
SValBuilder &SVB = getSValBuilder();
|
||||||
MemRegionManager &MRMgr = SVB.getRegionManager();
|
MemRegionManager &MRMgr = SVB.getRegionManager();
|
||||||
ASTContext &ACtx = SVB.getContext();
|
ASTContext &ACtx = SVB.getContext();
|
||||||
|
@ -125,8 +127,8 @@ SVal ExprEngine::computeObjectUnderConstruction(
|
||||||
const auto *DS = DSCC->getDeclStmt();
|
const auto *DS = DSCC->getDeclStmt();
|
||||||
const auto *Var = cast<VarDecl>(DS->getSingleDecl());
|
const auto *Var = cast<VarDecl>(DS->getSingleDecl());
|
||||||
QualType Ty = Var->getType();
|
QualType Ty = Var->getType();
|
||||||
return makeZeroElementRegion(State, State->getLValue(Var, LCtx), Ty,
|
return makeElementRegion(State, State->getLValue(Var, LCtx), Ty,
|
||||||
CallOpts.IsArrayCtorOrDtor);
|
CallOpts.IsArrayCtorOrDtor, Idx);
|
||||||
}
|
}
|
||||||
case ConstructionContext::CXX17ElidedCopyConstructorInitializerKind:
|
case ConstructionContext::CXX17ElidedCopyConstructorInitializerKind:
|
||||||
case ConstructionContext::SimpleConstructorInitializerKind: {
|
case ConstructionContext::SimpleConstructorInitializerKind: {
|
||||||
|
@ -158,8 +160,8 @@ SVal ExprEngine::computeObjectUnderConstruction(
|
||||||
}
|
}
|
||||||
|
|
||||||
QualType Ty = Field->getType();
|
QualType Ty = Field->getType();
|
||||||
return makeZeroElementRegion(State, FieldVal, Ty,
|
return makeElementRegion(State, FieldVal, Ty, CallOpts.IsArrayCtorOrDtor,
|
||||||
CallOpts.IsArrayCtorOrDtor);
|
Idx);
|
||||||
}
|
}
|
||||||
case ConstructionContext::NewAllocatedObjectKind: {
|
case ConstructionContext::NewAllocatedObjectKind: {
|
||||||
if (AMgr.getAnalyzerOptions().MayInlineCXXAllocator) {
|
if (AMgr.getAnalyzerOptions().MayInlineCXXAllocator) {
|
||||||
|
@ -172,8 +174,12 @@ SVal ExprEngine::computeObjectUnderConstruction(
|
||||||
// TODO: In fact, we need to call the constructor for every
|
// TODO: In fact, we need to call the constructor for every
|
||||||
// allocated element, not just the first one!
|
// allocated element, not just the first one!
|
||||||
CallOpts.IsArrayCtorOrDtor = true;
|
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;
|
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;
|
EvalCallOptions CallOpts;
|
||||||
auto C = getCurrentCFGElement().getAs<CFGConstructor>();
|
auto C = getCurrentCFGElement().getAs<CFGConstructor>();
|
||||||
assert(C || getCurrentCFGElement().getAs<CFGStmt>());
|
assert(C || getCurrentCFGElement().getAs<CFGStmt>());
|
||||||
|
@ -500,9 +502,15 @@ void ExprEngine::handleConstructor(const Expr *E,
|
||||||
// Inherited constructors are always base class constructors.
|
// Inherited constructors are always base class constructors.
|
||||||
assert(CE && !CIE && "A complete constructor is inherited?!");
|
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.
|
// The target region is found from construction context.
|
||||||
std::tie(State, Target) =
|
std::tie(State, Target) =
|
||||||
handleConstructionContext(CE, State, LCtx, CC, CallOpts);
|
handleConstructionContext(CE, State, LCtx, CC, CallOpts, Idx);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case CXXConstructExpr::CK_VirtualBase: {
|
case CXXConstructExpr::CK_VirtualBase: {
|
||||||
|
@ -894,14 +902,39 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred,
|
||||||
SVal Result = symVal;
|
SVal Result = symVal;
|
||||||
|
|
||||||
if (CNE->isArray()) {
|
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())) {
|
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 =
|
const ElementRegion *EleReg =
|
||||||
getStoreManager().GetElementZeroRegion(NewReg, ObjTy);
|
MRMgr.getElementRegion(ObjTy, svalBuilder.makeArrayIndex(0), NewReg,
|
||||||
|
svalBuilder.getContext());
|
||||||
Result = loc::MemRegionVal(EleReg);
|
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);
|
State = State->BindExpr(CNE, Pred->getLocationContext(), Result);
|
||||||
Bldr.generateNode(CNE, Pred, State);
|
Bldr.generateNode(CNE, Pred, State);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -227,6 +227,13 @@ void ExprEngine::processCallExit(ExplodedNode *CEBNode) {
|
||||||
|
|
||||||
// Step 2: generate node with bound return value: CEBNode -> BindedRetNode.
|
// 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 the callee returns an expression, bind its value to CallExpr.
|
||||||
if (CE) {
|
if (CE) {
|
||||||
if (const ReturnStmt *RS = dyn_cast_or_null<ReturnStmt>(LastSt)) {
|
if (const ReturnStmt *RS = dyn_cast_or_null<ReturnStmt>(LastSt)) {
|
||||||
|
@ -255,6 +262,12 @@ void ExprEngine::processCallExit(ExplodedNode *CEBNode) {
|
||||||
SVal ThisV = state->getSVal(This);
|
SVal ThisV = state->getSVal(This);
|
||||||
ThisV = state->getSVal(ThisV.castAs<Loc>());
|
ThisV = state->getSVal(ThisV.castAs<Loc>());
|
||||||
state = state->BindExpr(CCE, callerCtx, ThisV);
|
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)) {
|
if (const auto *CNE = dyn_cast<CXXNewExpr>(CE)) {
|
||||||
|
@ -358,9 +371,10 @@ void ExprEngine::processCallExit(ExplodedNode *CEBNode) {
|
||||||
|
|
||||||
// Enqueue the next element in the block.
|
// Enqueue the next element in the block.
|
||||||
for (ExplodedNodeSet::iterator PSI = Dst.begin(), PSE = Dst.end();
|
for (ExplodedNodeSet::iterator PSI = Dst.begin(), PSE = Dst.end();
|
||||||
PSI != PSE; ++PSI) {
|
PSI != PSE; ++PSI) {
|
||||||
Engine.getWorkList()->enqueue(*PSI, calleeCtx->getCallSiteBlock(),
|
unsigned Idx = calleeCtx->getIndex() + (ShouldRepeatCall ? 0 : 1);
|
||||||
calleeCtx->getIndex()+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.
|
// initializers for array fields in default move/copy constructors.
|
||||||
// We still allow construction into ElementRegion targets when they don't
|
// We still allow construction into ElementRegion targets when they don't
|
||||||
// represent array elements.
|
// represent array elements.
|
||||||
if (CallOpts.IsArrayCtorOrDtor)
|
if (CallOpts.IsArrayCtorOrDtor) {
|
||||||
return CIP_DisallowedOnce;
|
if (!shouldInlineArrayConstruction(
|
||||||
|
dyn_cast<ArrayType>(CtorExpr->getType())))
|
||||||
|
return CIP_DisallowedOnce;
|
||||||
|
}
|
||||||
|
|
||||||
// Inlining constructors requires including initializers in the CFG.
|
// Inlining constructors requires including initializers in the CFG.
|
||||||
const AnalysisDeclContext *ADC = CallerSFC->getAnalysisDeclContext();
|
const AnalysisDeclContext *ADC = CallerSFC->getAnalysisDeclContext();
|
||||||
|
@ -852,7 +869,7 @@ ExprEngine::mayInlineCallKind(const CallEvent &Call, const ExplodedNode *Pred,
|
||||||
assert(ADC->getCFGBuildOptions().AddImplicitDtors && "No CFG destructors");
|
assert(ADC->getCFGBuildOptions().AddImplicitDtors && "No CFG destructors");
|
||||||
(void)ADC;
|
(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)
|
if (CallOpts.IsArrayCtorOrDtor)
|
||||||
return CIP_DisallowedOnce;
|
return CIP_DisallowedOnce;
|
||||||
|
|
||||||
|
@ -1065,6 +1082,38 @@ bool ExprEngine::shouldInlineCall(const CallEvent &Call, const Decl *D,
|
||||||
return true;
|
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) {
|
static bool isTrivialObjectAssignment(const CallEvent &Call) {
|
||||||
const CXXInstanceCall *ICall = dyn_cast<CXXInstanceCall>(&Call);
|
const CXXInstanceCall *ICall = dyn_cast<CXXInstanceCall>(&Call);
|
||||||
if (!ICall)
|
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() {
|
void testArrayNew() {
|
||||||
// FIXME: Pending proper implementation of constructors for 'new[]'.
|
|
||||||
raw_pair *p = new raw_pair[2]();
|
raw_pair *p = new raw_pair[2]();
|
||||||
clang_analyzer_eval(p[0].p1 == 0); // expected-warning{{UNKNOWN}}
|
clang_analyzer_eval(p[0].p1 == 0); // expected-warning{{TRUE}}
|
||||||
clang_analyzer_eval(p[0].p2 == 0); // expected-warning{{UNKNOWN}}
|
clang_analyzer_eval(p[0].p2 == 0); // expected-warning{{TRUE}}
|
||||||
clang_analyzer_eval(p[1].p1 == 0); // expected-warning{{UNKNOWN}}
|
clang_analyzer_eval(p[1].p1 == 0); // expected-warning{{TRUE}}
|
||||||
clang_analyzer_eval(p[1].p2 == 0); // expected-warning{{UNKNOWN}}
|
clang_analyzer_eval(p[1].p2 == 0); // expected-warning{{TRUE}}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct initializing_pair {
|
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];
|
IntWrapper arr[2];
|
||||||
|
|
||||||
// There should be no undefined value warnings here.
|
// There should be no undefined value warnings here.
|
||||||
// Eventually these should be TRUE as well, but right now
|
clang_analyzer_eval(arr[0].x == 0); // expected-warning{{TRUE}}
|
||||||
// we can't handle array constructors.
|
clang_analyzer_eval(arr[1].x == 0); // expected-warning{{TRUE}}
|
||||||
clang_analyzer_eval(arr[0].x == 0); // expected-warning{{UNKNOWN}}
|
|
||||||
clang_analyzer_eval(arr[1].x == 0); // expected-warning{{UNKNOWN}}
|
|
||||||
|
|
||||||
arr[0].x = &i;
|
arr[0].x = &i;
|
||||||
arr[1].x = &j;
|
arr[1].x = &j;
|
||||||
|
@ -312,10 +310,8 @@ namespace MultidimensionalArrays {
|
||||||
IntWrapper arr[2][2];
|
IntWrapper arr[2][2];
|
||||||
|
|
||||||
// There should be no undefined value warnings here.
|
// There should be no undefined value warnings here.
|
||||||
// Eventually these should be TRUE as well, but right now
|
clang_analyzer_eval(arr[0][0].x == 0); // expected-warning{{TRUE}}
|
||||||
// we can't handle array constructors.
|
clang_analyzer_eval(arr[1][1].x == 0); // expected-warning{{TRUE}}
|
||||||
clang_analyzer_eval(arr[0][0].x == 0); // expected-warning{{UNKNOWN}}
|
|
||||||
clang_analyzer_eval(arr[1][1].x == 0); // expected-warning{{UNKNOWN}}
|
|
||||||
|
|
||||||
arr[0][0].x = &i;
|
arr[0][0].x = &i;
|
||||||
arr[1][1].x = &j;
|
arr[1][1].x = &j;
|
||||||
|
@ -597,5 +593,5 @@ void overrideLeak() {
|
||||||
void overrideDoubleDelete() {
|
void overrideDoubleDelete() {
|
||||||
auto *a = new CustomOperators();
|
auto *a = new CustomOperators();
|
||||||
delete a;
|
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,
|
"constraints": null,
|
||||||
"dynamic_types": null,
|
"dynamic_types": null,
|
||||||
"constructing_objects": null,
|
"constructing_objects": null,
|
||||||
|
"index_of_element": null,
|
||||||
"environment": null,
|
"environment": null,
|
||||||
"checker_messages": [
|
"checker_messages": [
|
||||||
{ "checker": "alpha.core.FooChecker", "messages": [
|
{ "checker": "alpha.core.FooChecker", "messages": [
|
||||||
|
|
|
@ -17,6 +17,7 @@ Node0x1 [shape=record,label=
|
||||||
"constraints": null,
|
"constraints": null,
|
||||||
"dynamic_types": null,
|
"dynamic_types": null,
|
||||||
"constructing_objects": null,
|
"constructing_objects": null,
|
||||||
|
"index_of_element": null,
|
||||||
"checker_messages": [
|
"checker_messages": [
|
||||||
{ "checker": "FooChecker", "messages": [
|
{ "checker": "FooChecker", "messages": [
|
||||||
"Foo: Bar"
|
"Foo: Bar"
|
||||||
|
@ -75,6 +76,7 @@ Node0x4 [shape=record,label=
|
||||||
"constraints": null,
|
"constraints": null,
|
||||||
"dynamic_types": null,
|
"dynamic_types": null,
|
||||||
"constructing_objects": null,
|
"constructing_objects": null,
|
||||||
|
"index_of_element": null,
|
||||||
"checker_messages": [
|
"checker_messages": [
|
||||||
{ "checker": "FooChecker", "messages": [
|
{ "checker": "FooChecker", "messages": [
|
||||||
"Foo: Bar",
|
"Foo: Bar",
|
||||||
|
|
|
@ -24,6 +24,7 @@ Node0x1 [shape=record,label=
|
||||||
"environment": null,
|
"environment": null,
|
||||||
"dynamic_types": null,
|
"dynamic_types": null,
|
||||||
"constructing_objects": null,
|
"constructing_objects": null,
|
||||||
|
"index_of_element": null,
|
||||||
"checker_messages": null,
|
"checker_messages": null,
|
||||||
"constraints": [
|
"constraints": [
|
||||||
{ "symbol": "reg_$0<x>", "range": "{ [0, 0] }" }
|
{ "symbol": "reg_$0<x>", "range": "{ [0, 0] }" }
|
||||||
|
|
|
@ -17,6 +17,7 @@ Node0x1 [shape=record,label=
|
||||||
"environment": null,
|
"environment": null,
|
||||||
"dynamic_types": null,
|
"dynamic_types": null,
|
||||||
"constructing_objects": null,
|
"constructing_objects": null,
|
||||||
|
"index_of_element": null,
|
||||||
"checker_messages": null,
|
"checker_messages": null,
|
||||||
"constraints": [
|
"constraints": [
|
||||||
{ "symbol": "reg_$0<x>", "range": "{ [0, 10] }" }
|
{ "symbol": "reg_$0<x>", "range": "{ [0, 10] }" }
|
||||||
|
@ -55,6 +56,7 @@ Node0x3 [shape=record,label=
|
||||||
"environment": null,
|
"environment": null,
|
||||||
"dynamic_types": null,
|
"dynamic_types": null,
|
||||||
"constructing_objects": null,
|
"constructing_objects": null,
|
||||||
|
"index_of_element": null,
|
||||||
"checker_messages": null,
|
"checker_messages": null,
|
||||||
"constraints": [
|
"constraints": [
|
||||||
{ "symbol": "reg_$0<x>", "range": "{ [0, 5] }" }
|
{ "symbol": "reg_$0<x>", "range": "{ [0, 5] }" }
|
||||||
|
@ -83,6 +85,7 @@ Node0x5 [shape=record,label=
|
||||||
"constraints": null,
|
"constraints": null,
|
||||||
"dynamic_types": null,
|
"dynamic_types": null,
|
||||||
"constructing_objects": null,
|
"constructing_objects": null,
|
||||||
|
"index_of_element": null,
|
||||||
"checker_messages": null
|
"checker_messages": null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,7 @@ Node0x1 [shape=record,label=
|
||||||
"constraints": null,
|
"constraints": null,
|
||||||
"dynamic_types": null,
|
"dynamic_types": null,
|
||||||
"constructing_objects": null,
|
"constructing_objects": null,
|
||||||
|
"index_of_element": null,
|
||||||
"checker_messages": null,
|
"checker_messages": null,
|
||||||
"environment": {
|
"environment": {
|
||||||
"pointer": "0x2",
|
"pointer": "0x2",
|
||||||
|
|
|
@ -18,6 +18,7 @@ Node0x1 [shape=record,label=
|
||||||
"constraints": null,
|
"constraints": null,
|
||||||
"dynamic_types": null,
|
"dynamic_types": null,
|
||||||
"constructing_objects": null,
|
"constructing_objects": null,
|
||||||
|
"index_of_element": null,
|
||||||
"checker_messages": null,
|
"checker_messages": null,
|
||||||
"environment": {
|
"environment": {
|
||||||
"pointer": "0x2",
|
"pointer": "0x2",
|
||||||
|
@ -73,6 +74,7 @@ Node0x6 [shape=record,label=
|
||||||
"constraints": null,
|
"constraints": null,
|
||||||
"dynamic_types": null,
|
"dynamic_types": null,
|
||||||
"constructing_objects": null,
|
"constructing_objects": null,
|
||||||
|
"index_of_element": null,
|
||||||
"checker_messages": null,
|
"checker_messages": null,
|
||||||
"environment": {
|
"environment": {
|
||||||
"pointer": "0x2",
|
"pointer": "0x2",
|
||||||
|
@ -122,6 +124,7 @@ Node0x9 [shape=record,label=
|
||||||
"constraints": null,
|
"constraints": null,
|
||||||
"dynamic_types": null,
|
"dynamic_types": null,
|
||||||
"constructing_objects": null,
|
"constructing_objects": null,
|
||||||
|
"index_of_element": null,
|
||||||
"checker_messages": null,
|
"checker_messages": null,
|
||||||
"environment": {
|
"environment": {
|
||||||
"pointer": "0x2",
|
"pointer": "0x2",
|
||||||
|
|
|
@ -34,6 +34,7 @@ Node0x1 [shape=record,label=
|
||||||
"constraints": null,
|
"constraints": null,
|
||||||
"dynamic_types": null,
|
"dynamic_types": null,
|
||||||
"constructing_objects": null,
|
"constructing_objects": null,
|
||||||
|
"index_of_element": null,
|
||||||
"checker_messages": null,
|
"checker_messages": null,
|
||||||
"store": {
|
"store": {
|
||||||
"pointer": "0x2",
|
"pointer": "0x2",
|
||||||
|
|
|
@ -20,6 +20,7 @@ Node0x1 [shape=record,label=
|
||||||
"constraints": null,
|
"constraints": null,
|
||||||
"dynamic_types": null,
|
"dynamic_types": null,
|
||||||
"constructing_objects": null,
|
"constructing_objects": null,
|
||||||
|
"index_of_element": null,
|
||||||
"checker_messages": null,
|
"checker_messages": null,
|
||||||
"store": {
|
"store": {
|
||||||
"pointer": "0x2",
|
"pointer": "0x2",
|
||||||
|
@ -74,6 +75,7 @@ Node0x4 [shape=record,label=
|
||||||
"constraints": null,
|
"constraints": null,
|
||||||
"dynamic_types": null,
|
"dynamic_types": null,
|
||||||
"constructing_objects": null,
|
"constructing_objects": null,
|
||||||
|
"index_of_element": null,
|
||||||
"checker_messages": null,
|
"checker_messages": null,
|
||||||
"store": {
|
"store": {
|
||||||
"pointer": "0x5",
|
"pointer": "0x5",
|
||||||
|
|
|
@ -24,6 +24,7 @@ Node0x1 [shape=record,label=
|
||||||
"constraints": null,
|
"constraints": null,
|
||||||
"dynamic_types": null,
|
"dynamic_types": null,
|
||||||
"constructing_objects": null,
|
"constructing_objects": null,
|
||||||
|
"index_of_element": null,
|
||||||
"checker_messages": [
|
"checker_messages": [
|
||||||
{ "checker": "foo", "messages": ["bar"] }
|
{ "checker": "foo", "messages": ["bar"] }
|
||||||
],
|
],
|
||||||
|
|
|
@ -47,6 +47,7 @@ void foo(int x) {
|
||||||
// CHECK-NEXT: "dynamic_types": null,
|
// CHECK-NEXT: "dynamic_types": null,
|
||||||
// CHECK-NEXT: "dynamic_casts": null,
|
// CHECK-NEXT: "dynamic_casts": null,
|
||||||
// CHECK-NEXT: "constructing_objects": null,
|
// CHECK-NEXT: "constructing_objects": null,
|
||||||
|
// CHECK-NEXT: "index_of_element": null,
|
||||||
// CHECK-NEXT: "checker_messages": null
|
// CHECK-NEXT: "checker_messages": null
|
||||||
// CHECK-NEXT: }
|
// CHECK-NEXT: }
|
||||||
|
|
||||||
|
|
|
@ -59,12 +59,9 @@ void test_automatic_aggregate() {
|
||||||
init_in_body a2[1];
|
init_in_body a2[1];
|
||||||
init_default_member a3[1];
|
init_default_member a3[1];
|
||||||
|
|
||||||
// FIXME: Should be TRUE, not FALSE.
|
clang_analyzer_eval(a1[0].a == 1); // expected-warning {{TRUE}}
|
||||||
clang_analyzer_eval(a1[0].a == 1); // expected-warning {{TRUE}} expected-warning {{FALSE}}
|
clang_analyzer_eval(a2[0].a == 1); // expected-warning {{TRUE}}
|
||||||
// FIXME: Should be TRUE, not FALSE.
|
clang_analyzer_eval(a3[0].a == 1); // expected-warning {{TRUE}}
|
||||||
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}}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_dynamic_aggregate() {
|
void test_dynamic_aggregate() {
|
||||||
|
@ -73,12 +70,9 @@ void test_dynamic_aggregate() {
|
||||||
auto *a2 = new init_in_body[1];
|
auto *a2 = new init_in_body[1];
|
||||||
auto *a3 = new init_default_member[1];
|
auto *a3 = new init_default_member[1];
|
||||||
|
|
||||||
// FIXME: Should be TRUE, not FALSE.
|
clang_analyzer_eval(a1[0].a == 1); // expected-warning {{TRUE}}
|
||||||
clang_analyzer_eval(a1[0].a == 1); // expected-warning {{TRUE}} expected-warning {{FALSE}}
|
clang_analyzer_eval(a2[0].a == 1); // expected-warning {{TRUE}}
|
||||||
// FIXME: Should be TRUE, not FALSE.
|
clang_analyzer_eval(a3[0].a == 1); // expected-warning {{TRUE}}
|
||||||
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}}
|
|
||||||
|
|
||||||
delete[] a1;
|
delete[] a1;
|
||||||
delete[] a2;
|
delete[] a2;
|
||||||
|
|
|
@ -25,9 +25,14 @@ void checkNewPOD() {
|
||||||
|
|
||||||
void checkNewArray() {
|
void checkNewArray() {
|
||||||
S *s = new S[10];
|
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[0].x == 1); // expected-warning{{UNKNOWN}}
|
||||||
clang_analyzer_eval(s[1].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 {
|
struct NullS {
|
||||||
|
|
|
@ -323,8 +323,8 @@ void testArrayNull() {
|
||||||
|
|
||||||
void testArrayDestr() {
|
void testArrayDestr() {
|
||||||
NoReturnDtor *p = new NoReturnDtor[2];
|
NoReturnDtor *p = new NoReturnDtor[2];
|
||||||
delete[] p; // Calls the base destructor which aborts, checked below
|
delete[] p; // Calls the base destructor which aborts, checked below
|
||||||
//TODO: clang_analyzer_eval should not be called
|
// TODO: clang_analyzer_eval should not be called
|
||||||
clang_analyzer_eval(true); // expected-warning{{TRUE}}
|
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);
|
void clang_analyzer_eval(bool);
|
||||||
|
|
||||||
struct X0 { };
|
struct X0 { };
|
||||||
|
@ -29,6 +29,7 @@ struct IntComparable {
|
||||||
|
|
||||||
void testMemberOperator(IntComparable B) {
|
void testMemberOperator(IntComparable B) {
|
||||||
clang_analyzer_eval(B == 0); // expected-warning{{TRUE}}
|
clang_analyzer_eval(B == 0); // expected-warning{{TRUE}}
|
||||||
|
// expected-note@-1{{TRUE}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,7 +47,9 @@ namespace UserDefinedConversions {
|
||||||
|
|
||||||
void test(const Convertible &obj) {
|
void test(const Convertible &obj) {
|
||||||
clang_analyzer_eval((int)obj == 42); // expected-warning{{TRUE}}
|
clang_analyzer_eval((int)obj == 42); // expected-warning{{TRUE}}
|
||||||
|
// expected-note@-1{{TRUE}}
|
||||||
clang_analyzer_eval(obj); // expected-warning{{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.
|
// Force a cache-out when we try to conjure a temporary region for the operator call.
|
||||||
// ...then, don't crash.
|
// ...then, don't crash.
|
||||||
clang_analyzer_eval(+(coin ? getSmallOpaque() : getSmallOpaque())); // expected-warning{{UNKNOWN}}
|
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}}
|
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
|
// This used to produce a warning about the iteration variable in the
|
||||||
// synthesized assignment operator being undefined.
|
// 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() {
|
void testNoWarning() {
|
||||||
|
|
||||||
B v, u;
|
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() {
|
void testNoWarningMove() {
|
||||||
B v, u;
|
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() {
|
void testConsistency() {
|
||||||
B v, u;
|
B v, u;
|
||||||
|
v.x = 0;
|
||||||
|
v.a[0].a = 24;
|
||||||
v.a[1].a = 47;
|
v.a[1].a = 47;
|
||||||
v.a[2].a = 42;
|
v.a[2].a = 42;
|
||||||
u = v;
|
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}}
|
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}}
|
clang_analyzer_eval(u.a[2].a == -42); // expected-warning{{TRUE}}
|
||||||
|
// expected-note@-1{{TRUE}}
|
||||||
}
|
}
|
||||||
|
|
||||||
void testConsistencyMove() {
|
void testConsistencyMove() {
|
||||||
B v, u;
|
B v, u;
|
||||||
|
v.x = 0;
|
||||||
|
v.a[0].a = 24;
|
||||||
v.a[1].a = 47;
|
v.a[1].a = 47;
|
||||||
v.a[2].a = 42;
|
v.a[2].a = 42;
|
||||||
u = static_cast<B &&>(v);
|
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}}
|
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}}
|
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
|
# 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:
|
class GenericEnvironment:
|
||||||
def __init__(self, json_e):
|
def __init__(self, json_e):
|
||||||
self.frames = [EnvironmentFrame(f) for f in json_e]
|
self.frames = [EnvironmentFrame(f) for f in json_e]
|
||||||
|
@ -269,6 +270,7 @@ class ProgramState:
|
||||||
'constraints': None,
|
'constraints': None,
|
||||||
'dynamic_types': None,
|
'dynamic_types': None,
|
||||||
'constructing_objects': None,
|
'constructing_objects': None,
|
||||||
|
'index_of_element': None,
|
||||||
'checker_messages': None
|
'checker_messages': None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,6 +298,10 @@ class ProgramState:
|
||||||
GenericEnvironment(json_ps['constructing_objects']) \
|
GenericEnvironment(json_ps['constructing_objects']) \
|
||||||
if json_ps['constructing_objects'] is not None else None
|
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']) \
|
self.checker_messages = CheckerMessages(json_ps['checker_messages']) \
|
||||||
if json_ps['checker_messages'] is not None else None
|
if json_ps['checker_messages'] is not None else None
|
||||||
|
|
||||||
|
@ -796,6 +802,9 @@ class DotDumpVisitor:
|
||||||
self.visit_environment_in_state('constructing_objects',
|
self.visit_environment_in_state('constructing_objects',
|
||||||
'Objects Under Construction',
|
'Objects Under Construction',
|
||||||
s, prev_s)
|
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)
|
self.visit_checker_messages_in_state(s, prev_s)
|
||||||
|
|
||||||
def visit_node(self, node):
|
def visit_node(self, node):
|
||||||
|
|
Loading…
Reference in New Issue