Skip to content

Commit 2cf2947

Browse files
committed
[clang][dataflow] Don't propagate result objects in unevaluated contexts.
Trying to do so can cause crashes -- see newly added test and the comments in the fix. We're starting to see a repeating pattern here: We're getting crashes because `ResultObjectVisitor` and `getReferencedDecls()` don't agree on which parts of the AST to visit and, hence, which fields should be modeled. I think we should ensure consistency between these two parts of the code by using a `RecursiveASTVisitor` in `getReferencedDecls()`[^1]; the `Traverse...()` functions that control which parts of the AST we visit would go in a common base class that would be used for both `ResultObjectVisitor` and `getReferencedDecls()`. I'd like to focus this PR, however, on a targeted fix for the current crash and postpone the refactoring to a later PR (which will be easier to revert if there are unintended side-effects). [^1]: As an added bonus, this would make the code better structured and more efficient than the current sequence of `if (dyn_cast<T>(...))` statements).
1 parent fefac5d commit 2cf2947

File tree

2 files changed

+63
-0
lines changed

2 files changed

+63
-0
lines changed

clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,17 @@ class ResultObjectVisitor : public RecursiveASTVisitor<ResultObjectVisitor> {
350350
return RecursiveASTVisitor<ResultObjectVisitor>::TraverseDecl(D);
351351
}
352352

353+
// Don't traverse expressions in unevaluated contexts, as we don't model
354+
// fields that are only used in these.
355+
// Note: The operand of the `noexcept` operator is an unevaluated operand, but
356+
// nevertheless it appears in the Clang CFG, so we don't exclude it here.
357+
bool TraverseDecltypeTypeLoc(DecltypeTypeLoc) { return true; }
358+
bool TraverseTypeOfExprTypeLoc(TypeOfExprTypeLoc) { return true; }
359+
bool TraverseCXXTypeidExpr(CXXTypeidExpr *) { return true; }
360+
bool TraverseUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *) {
361+
return true;
362+
}
363+
353364
bool TraverseBindingDecl(BindingDecl *BD) {
354365
// `RecursiveASTVisitor` doesn't traverse holding variables for
355366
// `BindingDecl`s by itself, so we need to tell it to.

clang/unittests/Analysis/FlowSensitive/TransferTest.cpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3331,6 +3331,58 @@ TEST(TransferTest, ResultObjectLocationDontVisitNestedRecordDecl) {
33313331
ASTContext &ASTCtx) {});
33323332
}
33333333

3334+
TEST(TransferTest, ResultObjectLocationDontVisitUnevaluatedContexts) {
3335+
// This is a crash repro.
3336+
// We used to crash because when propagating result objects, we would visit
3337+
// unevaluated contexts, but we don't model fields used only in these.
3338+
3339+
auto testFunction = [](llvm::StringRef Code, llvm::StringRef TargetFun) {
3340+
runDataflow(
3341+
Code,
3342+
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
3343+
ASTContext &ASTCtx) {},
3344+
LangStandard::lang_gnucxx17,
3345+
/* ApplyBuiltinTransfer= */ true, TargetFun);
3346+
};
3347+
3348+
std::string Code = R"cc(
3349+
// Definitions needed for `typeid`.
3350+
namespace std {
3351+
class type_info {};
3352+
class bad_typeid {};
3353+
} // namespace std
3354+
3355+
struct S1 {};
3356+
struct S2 { S1 s1; };
3357+
3358+
// We test each type of unevaluated context from a different target
3359+
// function. Some types of unevaluated contexts may actually cause the
3360+
// field `s1` to be modeled, and we don't want this to "pollute" the tests
3361+
// for the other unevaluated contexts.
3362+
void decltypeTarget() {
3363+
decltype(S2{}) Dummy;
3364+
}
3365+
void typeofTarget() {
3366+
typeof(S2{}) Dummy;
3367+
}
3368+
void typeidTarget() {
3369+
typeid(S2{});
3370+
}
3371+
void sizeofTarget() {
3372+
sizeof(S2{});
3373+
}
3374+
void noexceptTarget() {
3375+
noexcept(S2{});
3376+
}
3377+
)cc";
3378+
3379+
testFunction(Code, "decltypeTarget");
3380+
testFunction(Code, "typeofTarget");
3381+
testFunction(Code, "typeidTarget");
3382+
testFunction(Code, "sizeofTarget");
3383+
testFunction(Code, "noexceptTarget");
3384+
}
3385+
33343386
TEST(TransferTest, StaticCast) {
33353387
std::string Code = R"(
33363388
void target(int Foo) {

0 commit comments

Comments
 (0)