/// is used during dataflow analysis.
class DataflowAnalysisContext {
public:
+ // FIXME: merge with TransferOptions from Transfer.h.
+ struct Options {
+ bool EnableContextSensitiveAnalysis;
+ };
+
/// Constructs a dataflow analysis context.
///
/// Requirements:
///
/// `S` must not be null.
- DataflowAnalysisContext(std::unique_ptr<Solver> S)
+ DataflowAnalysisContext(std::unique_ptr<Solver> S,
+ Options Opts = {
+ /*EnableContextSensitiveAnalysis=*/false})
: S(std::move(S)), TrueVal(createAtomicBoolValue()),
- FalseVal(createAtomicBoolValue()) {
+ FalseVal(createAtomicBoolValue()), Options(Opts) {
assert(this->S != nullptr);
}
/// returns null.
const ControlFlowContext *getControlFlowContext(const FunctionDecl *F);
+ void addFieldsReferencedInScope(llvm::DenseSet<const FieldDecl *> Fields);
+
private:
+ friend class Environment;
+
struct NullableQualTypeDenseMapInfo : private llvm::DenseMapInfo<QualType> {
static QualType getEmptyKey() {
// Allow a NULL `QualType` by using a different value as the empty key.
using DenseMapInfo::isEqual;
};
+ /// Returns the subset of fields of `Type` that are referenced in the scope of
+ /// the analysis.
+ llvm::DenseSet<const FieldDecl *> getReferencedFields(QualType Type);
+
/// Adds all constraints of the flow condition identified by `Token` and all
/// of its transitive dependencies to `Constraints`. `VisitedTokens` is used
/// to track tokens of flow conditions that were already visited by recursive
AtomicBoolValue &TrueVal;
AtomicBoolValue &FalseVal;
+ Options Options;
+
// Indices that are used to avoid recreating the same composite boolean
// values.
llvm::DenseMap<std::pair<BoolValue *, BoolValue *>, ConjunctionValue *>
llvm::DenseMap<AtomicBoolValue *, BoolValue *> FlowConditionConstraints;
llvm::DenseMap<const FunctionDecl *, ControlFlowContext> FunctionContexts;
+
+ // All fields referenced (statically) in the scope of the analysis.
+ llvm::DenseSet<const FieldDecl *> FieldsReferencedInScope;
};
} // namespace dataflow
void pushCallInternal(const FunctionDecl *FuncDecl,
ArrayRef<const Expr *> Args);
+ /// Assigns storage locations and values to all variables in `Vars`.
+ void initVars(llvm::DenseSet<const VarDecl *> Vars);
+
// `DACtx` is not null and not owned by this object.
DataflowAnalysisContext *DACtx;
#include "clang/AST/ExprCXX.h"
#include "clang/Analysis/FlowSensitive/DebugSupport.h"
#include "clang/Analysis/FlowSensitive/Value.h"
+#include "llvm/ADT/SetOperations.h"
#include "llvm/Support/Debug.h"
#include <cassert>
#include <memory>
namespace clang {
namespace dataflow {
+void DataflowAnalysisContext::addFieldsReferencedInScope(
+ llvm::DenseSet<const FieldDecl *> Fields) {
+ llvm::set_union(FieldsReferencedInScope, Fields);
+}
+
+llvm::DenseSet<const FieldDecl *>
+DataflowAnalysisContext::getReferencedFields(QualType Type) {
+ llvm::DenseSet<const FieldDecl *> Fields = getObjectFields(Type);
+ llvm::set_intersect(Fields, FieldsReferencedInScope);
+ return Fields;
+}
+
StorageLocation &DataflowAnalysisContext::createStorageLocation(QualType Type) {
if (!Type.isNull() &&
(Type->isStructureOrClassType() || Type->isUnionType())) {
- // FIXME: Explore options to avoid eager initialization of fields as some of
- // them might not be needed for a particular analysis.
llvm::DenseMap<const ValueDecl *, StorageLocation *> FieldLocs;
- for (const FieldDecl *Field : getObjectFields(Type))
+ // During context-sensitive analysis, a struct may be allocated in one
+ // function, but its field accessed in a function lower in the stack than
+ // the allocation. Since we only collect fields used in the function where
+ // the allocation occurs, we can't apply that filter when performing
+ // context-sensitive analysis. But, this only applies to storage locations,
+ // since fields access it not allowed to fail. In contrast, field *values*
+ // don't need this allowance, since the API allows for uninitialized fields.
+ auto Fields = Options.EnableContextSensitiveAnalysis
+ ? getObjectFields(Type)
+ : getReferencedFields(Type);
+ for (const FieldDecl *Field : Fields)
FieldLocs.insert({Field, &createStorageLocation(Field->getType())});
return takeOwnership(
std::make_unique<AggregateStorageLocation>(Type, std::move(FieldLocs)));
}
/// Initializes a global storage value.
-static void initGlobalVar(const VarDecl &D, Environment &Env) {
- if (!D.hasGlobalStorage() ||
- Env.getStorageLocation(D, SkipPast::None) != nullptr)
- return;
-
- auto &Loc = Env.createStorageLocation(D);
- Env.setStorageLocation(D, Loc);
- if (auto *Val = Env.createValue(D.getType()))
- Env.setValue(Loc, *Val);
-}
-
-/// Initializes a global storage value.
-static void initGlobalVar(const Decl &D, Environment &Env) {
+static void insertIfGlobal(const Decl &D,
+ llvm::DenseSet<const FieldDecl *> &Fields,
+ llvm::DenseSet<const VarDecl *> &Vars) {
if (auto *V = dyn_cast<VarDecl>(&D))
- initGlobalVar(*V, Env);
-}
-
-/// Initializes global storage values that are declared or referenced from
-/// sub-statements of `S`.
-// FIXME: Add support for resetting globals after function calls to enable
-// the implementation of sound analyses.
-static void initGlobalVars(const Stmt &S, Environment &Env) {
- for (auto *Child : S.children()) {
+ if (V->hasGlobalStorage())
+ Vars.insert(V);
+}
+
+static void getFieldsAndGlobalVars(const Decl &D,
+ llvm::DenseSet<const FieldDecl *> &Fields,
+ llvm::DenseSet<const VarDecl *> &Vars) {
+ insertIfGlobal(D, Fields, Vars);
+ if (const auto *Decomp = dyn_cast<DecompositionDecl>(&D))
+ for (const auto *B : Decomp->bindings())
+ if (auto *ME = dyn_cast_or_null<MemberExpr>(B->getBinding()))
+ // FIXME: should we be using `E->getFoundDecl()`?
+ if (const auto *FD = dyn_cast<FieldDecl>(ME->getMemberDecl()))
+ Fields.insert(FD);
+}
+
+/// Traverses `S` and inserts into `Vars` any global storage values that are
+/// declared in or referenced from sub-statements.
+static void getFieldsAndGlobalVars(const Stmt &S,
+ llvm::DenseSet<const FieldDecl *> &Fields,
+ llvm::DenseSet<const VarDecl *> &Vars) {
+ for (auto *Child : S.children())
if (Child != nullptr)
- initGlobalVars(*Child, Env);
- }
+ getFieldsAndGlobalVars(*Child, Fields, Vars);
if (auto *DS = dyn_cast<DeclStmt>(&S)) {
- if (DS->isSingleDecl()) {
- initGlobalVar(*DS->getSingleDecl(), Env);
- } else {
+ if (DS->isSingleDecl())
+ getFieldsAndGlobalVars(*DS->getSingleDecl(), Fields, Vars);
+ else
for (auto *D : DS->getDeclGroup())
- initGlobalVar(*D, Env);
- }
+ getFieldsAndGlobalVars(*D, Fields, Vars);
} else if (auto *E = dyn_cast<DeclRefExpr>(&S)) {
- initGlobalVar(*E->getDecl(), Env);
+ insertIfGlobal(*E->getDecl(), Fields, Vars);
} else if (auto *E = dyn_cast<MemberExpr>(&S)) {
- initGlobalVar(*E->getMemberDecl(), Env);
+ // FIXME: should we be using `E->getFoundDecl()`?
+ const ValueDecl *VD = E->getMemberDecl();
+ insertIfGlobal(*VD, Fields, Vars);
+ if (const auto *FD = dyn_cast<FieldDecl>(VD))
+ Fields.insert(FD);
+ }
+}
+
+// FIXME: Add support for resetting globals after function calls to enable
+// the implementation of sound analyses.
+void Environment::initVars(llvm::DenseSet<const VarDecl *> Vars) {
+ for (const VarDecl *D : Vars) {
+ if (getStorageLocation(*D, SkipPast::None) != nullptr)
+ continue;
+ auto &Loc = createStorageLocation(*D);
+ setStorageLocation(*D, Loc);
+ if (auto *Val = createValue(D->getType()))
+ setValue(Loc, *Val);
}
}
if (const auto *FuncDecl = dyn_cast<FunctionDecl>(&DeclCtx)) {
assert(FuncDecl->getBody() != nullptr);
- initGlobalVars(*FuncDecl->getBody(), *this);
+
+ llvm::DenseSet<const FieldDecl *> Fields;
+ llvm::DenseSet<const VarDecl *> Vars;
+
+ // Look for global variable references in the constructor-initializers.
+ if (const auto *CtorDecl = dyn_cast<CXXConstructorDecl>(&DeclCtx)) {
+ for (const auto *Init : CtorDecl->inits()) {
+ if (const auto *M = Init->getAnyMember())
+ Fields.insert(M);
+ const Expr *E = Init->getInit();
+ assert(E != nullptr);
+ getFieldsAndGlobalVars(*E, Fields, Vars);
+ }
+ }
+ getFieldsAndGlobalVars(*FuncDecl->getBody(), Fields, Vars);
+
+ initVars(Vars);
+ // These have to be set before the lines that follow to ensure that create*
+ // work correctly for structs.
+ DACtx.addFieldsReferencedInScope(std::move(Fields));
+
for (const auto *ParamDecl : FuncDecl->parameters()) {
assert(ParamDecl != nullptr);
auto &ParamLoc = createStorageLocation(*ParamDecl);
setValue(*ThisPointeeLoc, *ThisPointeeVal);
}
}
-
- // Look for global variable references in the constructor-initializers.
- if (const auto *CtorDecl = dyn_cast<CXXConstructorDecl>(&DeclCtx)) {
- for (const auto *Init : CtorDecl->inits()) {
- const Expr *E = Init->getInit();
- assert(E != nullptr);
- initGlobalVars(*E, *this);
- }
- }
}
bool Environment::canDescend(unsigned MaxDepth,
ArrayRef<const Expr *> Args) {
CallStack.push_back(FuncDecl);
- // FIXME: In order to allow the callee to reference globals, we probably need
- // to call `initGlobalVars` here in some way.
+ // FIXME: Share this code with the constructor, rather than duplicating it.
+ llvm::DenseSet<const FieldDecl *> Fields;
+ llvm::DenseSet<const VarDecl *> Vars;
+ // Look for global variable references in the constructor-initializers.
+ if (const auto *CtorDecl = dyn_cast<CXXConstructorDecl>(FuncDecl)) {
+ for (const auto *Init : CtorDecl->inits()) {
+ if (const auto *M = Init->getAnyMember())
+ Fields.insert(M);
+ const Expr *E = Init->getInit();
+ assert(E != nullptr);
+ getFieldsAndGlobalVars(*E, Fields, Vars);
+ }
+ }
+ getFieldsAndGlobalVars(*FuncDecl->getBody(), Fields, Vars);
+ initVars(Vars);
+ DACtx->addFieldsReferencedInScope(std::move(Fields));
- auto ParamIt = FuncDecl->param_begin();
+ const auto *ParamIt = FuncDecl->param_begin();
// FIXME: Parameters don't always map to arguments 1:1; examples include
// overloaded operators implemented as member functions, and parameter packs.
const QualType Type = AggregateLoc.getType();
assert(Type->isStructureOrClassType() || Type->isUnionType());
- for (const FieldDecl *Field : getObjectFields(Type)) {
+ for (const FieldDecl *Field : DACtx->getReferencedFields(Type)) {
assert(Field != nullptr);
StorageLocation &FieldLoc = AggregateLoc.getChild(*Field);
MemberLocToStruct[&FieldLoc] = std::make_pair(StructVal, Field);
if (Type->isStructureOrClassType() || Type->isUnionType()) {
CreatedValuesCount++;
- // FIXME: Initialize only fields that are accessed in the context that is
- // being analyzed.
llvm::DenseMap<const ValueDecl *, Value *> FieldValues;
- for (const FieldDecl *Field : getObjectFields(Type)) {
+ for (const FieldDecl *Field : DACtx->getReferencedFields(Type)) {
assert(Field != nullptr);
QualType FieldType = Field->getType();
} else if (auto *VD = B->getHoldingVar()) {
// Holding vars are used to back the BindingDecls of tuple-like
// types. The holding var declarations appear *after* this statement,
- // so we have to create a location or them here to share with `B`. We
+ // so we have to create a location for them here to share with `B`. We
// don't visit the binding, because we know it will be a DeclRefExpr
// to `VD`.
auto &VDLoc = Env.createStorageLocation(*VD);
bool X;
Recursive *R;
};
+ // Use both fields to force them to be created with `createValue`.
+ void Usage(Recursive R) { (void)R.X; (void)R.R; }
)cc";
auto Unit =
const QualType *Ty = selectFirst<QualType>("target", Results);
const FieldDecl *R = selectFirst<FieldDecl>("field-r", Results);
ASSERT_TRUE(Ty != nullptr && !Ty->isNull());
- ASSERT_TRUE(R != nullptr);
+ ASSERT_THAT(R, NotNull());
+
+ Results = match(functionDecl(hasName("Usage")).bind("fun"), Context);
+ const auto *Fun = selectFirst<FunctionDecl>("fun", Results);
+ ASSERT_THAT(Fun, NotNull());
// Verify that the struct and the field (`R`) with first appearance of the
// type is created successfully.
- Environment Env(DAContext);
+ Environment Env(DAContext, *Fun);
Value *Val = Env.createValue(*Ty);
ASSERT_NE(Val, nullptr);
StructValue *SVal = clang::dyn_cast<StructValue>(Val);
ASTBuildVirtualMappedFiles = std::move(Arg);
return std::move(*this);
}
+ AnalysisInputs<AnalysisT> &&
+ withContextSensitivity() && {
+ EnableContextSensitivity = true;
+ return std::move(*this);
+ }
/// Required. Input code that is analyzed.
llvm::StringRef Code;
ArrayRef<std::string> ASTBuildArgs = {};
/// Optional. Options for building the AST context.
tooling::FileContentMappings ASTBuildVirtualMappedFiles = {};
+ /// Enables context-sensitive analysis when constructing the
+ /// `DataflowAnalysisContext`.
+ bool EnableContextSensitivity = false;
};
/// Returns assertions based on annotations that are present after statements in
auto &CFCtx = *MaybeCFCtx;
// Initialize states for running dataflow analysis.
- DataflowAnalysisContext DACtx(std::make_unique<WatchedLiteralsSolver>());
+ DataflowAnalysisContext DACtx(
+ std::make_unique<WatchedLiteralsSolver>(),
+ {/*EnableContextSensitiveAnalysis=*/AI.EnableContextSensitivity});
Environment InitEnv(DACtx, *Target);
auto Analysis = AI.MakeAnalysis(Context, InitEnv);
std::function<void(const CFGElement &,
#include "clang/Analysis/FlowSensitive/Value.h"
#include "clang/Basic/LangStandard.h"
#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Casting.h"
#include "llvm/Testing/Support/Error.h"
LangStandard::Kind Std = LangStandard::lang_cxx17,
llvm::StringRef TargetFun = "target") {
using ast_matchers::hasName;
+ llvm::SmallVector<std::string, 3> ASTBuildArgs = {
+ "-fsyntax-only", "-fno-delayed-template-parsing",
+ "-std=" +
+ std::string(LangStandard::getLangStandardForKind(Std).getName())};
+ auto AI =
+ AnalysisInputs<NoopAnalysis>(Code, hasName(TargetFun),
+ [&Options](ASTContext &C, Environment &) {
+ return NoopAnalysis(C, Options);
+ })
+ .withASTBuildArgs(ASTBuildArgs);
+ if (Options.BuiltinTransferOpts &&
+ Options.BuiltinTransferOpts->ContextSensitiveOpts)
+ AI = std::move(AI).withContextSensitivity();
ASSERT_THAT_ERROR(
checkDataflow<NoopAnalysis>(
- AnalysisInputs<NoopAnalysis>(Code, hasName(TargetFun),
- [Options](ASTContext &C, Environment &) {
- return NoopAnalysis(C, Options);
- })
- .withASTBuildArgs(
- {"-fsyntax-only", "-fno-delayed-template-parsing",
- "-std=" +
- std::string(LangStandard::getLangStandardForKind(Std)
- .getName())}),
+ std::move(AI),
/*VerifyResults=*/
[&Match](const llvm::StringMap<DataflowAnalysisState<NoopLattice>>
&Results,
void target() {
A Foo;
+ (void)Foo.Bar;
// [[p]]
}
)";
void target() {
A Foo = Gen();
+ (void)Foo.Bar;
// [[p]]
}
)";
TEST(TransferTest, ClassVarDecl) {
std::string Code = R"(
class A {
+ public:
int Bar;
};
void target() {
A Foo;
+ (void)Foo.Bar;
// [[p]]
}
)";
void target() {
A &Foo = getA();
+ (void)Foo.Bar.FooRef;
+ (void)Foo.Bar.FooPtr;
+ (void)Foo.Bar.BazRef;
+ (void)Foo.Bar.BazPtr;
// [[p]]
}
)";
void target() {
A *Foo = getA();
+ (void)Foo->Bar->FooRef;
+ (void)Foo->Bar->FooPtr;
+ (void)Foo->Bar->BazRef;
+ (void)Foo->Bar->BazPtr;
// [[p]]
}
)";
};
void target(A Foo) {
- (void)0;
+ (void)Foo.Bar;
// [[p]]
}
)";
int APrivate;
public:
int APublic;
+
+ private:
+ friend void target();
};
class B : public A {
int BProtected;
private:
int BPrivate;
+
+ private:
+ friend void target();
};
void target() {
B Foo;
+ (void)Foo.ADefault;
+ (void)Foo.AProtected;
+ (void)Foo.APrivate;
+ (void)Foo.APublic;
+ (void)Foo.BDefault;
+ (void)Foo.BProtected;
+ (void)Foo.BPrivate;
// [[p]]
}
)";
void target() {
B Foo;
+ (void)Foo.Bar;
// [[p]]
}
)";
int Bar;
void target() {
- (void)0; // [[p]]
+ A a;
+ // Mention the fields to ensure they're included in the analysis.
+ (void)a.Foo;
+ (void)a.Bar;
+ // [[p]]
}
};
)";
void target() {
A Foo = A();
+ (void)Foo.Bar;
// [[p]]
}
)";
void target() {
A Foo = A();
+ (void)Foo.Bar;
// [[p]]
}
)";
void target() {
A Foo;
A Bar;
+ (void)Foo.Baz;
// [[p1]]
Foo = Bar;
// [[p2]]
void target() {
A Foo;
+ (void)Foo.Baz;
A Bar = Foo;
// [[p]]
}
void target() {
A Foo;
+ (void)Foo.Baz;
A Bar((A(Foo)));
// [[p]]
}
void target() {
A Foo;
A Bar;
+ (void)Foo.Baz;
// [[p1]]
Foo = std::move(Bar);
// [[p2]]