Skip to content

Commit fcd020d

Browse files
authored
[Clang][Sema] Fix malformed AST for anonymous class access in template. (llvm#90842)
# Observed erroneous behavior Prior to this change, a `MemberExpr` that accesses an anonymous class might have a prvalue as its base (even though C++ mandates that the base of a `MemberExpr` must be a glvalue), if the code containing the `MemberExpr` was in a template. Here's an example on [godbolt](https://godbolt.org/z/Gz1Mer9oz) (that is essentially identical to the new test this patch adds). This example sets up a struct containing an anonymous struct: ```cxx struct S { struct { int i; }; }; ``` It then accesses the member `i` using the expression `S().i`. When we do this in a non-template function, we get the following AST: ``` `-ExprWithCleanups <col:10, col:14> 'int' `-ImplicitCastExpr <col:10, col:14> 'int' <LValueToRValue> `-MemberExpr <col:10, col:14> 'int' xvalue .i 0xbdcb3c0 `-MemberExpr <col:10, col:14> 'S::(anonymous struct at line:2:3)' xvalue .S::(anonymous struct at line:2:3) 0xbdcb488 `-MaterializeTemporaryExpr <col:10, col:12> 'S' xvalue `-CXXTemporaryObjectExpr <col:10, col:12> 'S' 'void () noexcept' zeroing ``` As expected, the AST contains a `MaterializeTemporarExpr` to materialize the prvalue `S()` before accessing its members. When we perform this access in a function template (that doesn't actually even use its template parameter), the AST for the template itself looks the same as above. However, the AST for an instantiation of the template looks different: ``` `-ExprWithCleanups <col:10, col:14> 'int' `-ImplicitCastExpr <col:10, col:14> 'int' <LValueToRValue> `-MemberExpr <col:10, col:14> 'int' xvalue .i 0xbdcb3c0 `-MaterializeTemporaryExpr <col:10, col:14> 'S::(anonymous struct at line:2:3)' xvalue `-MemberExpr <col:10, col:14> 'S::(anonymous struct at line:2:3)' .S::(anonymous struct at line:2:3) 0xbdcb488 `-CXXTemporaryObjectExpr <col:10, col:12> 'S' 'void () noexcept' zeroing ``` Note how the inner `MemberExpr` (the one accessing the anonymous struct) acts on a prvalue. Interestingly, this does not appear to cause any problems for CodeGen, probably because CodeGen is set up to deal with `MemberExpr`s on rvalues in C. However, it does cause issues in the dataflow framework, which only supports C++ today and expects the base of a `MemberExpr` to be a glvalue. Beyond the issues with the dataflow framework, I think this issue should be fixed because it goes contrary to what the C++ standard mandates, and the AST produced for the non-template case indicates that we want to follow the C++ rules here. # Reasons for erroneous behavior Here's why we're getting this malformed AST. First of all, `TreeTransform` [strips any `MaterializeTemporaryExpr`s](https://github.com/llvm/llvm-project/blob/cd132dcbeb0fc79fd657bd5e0a8e9244c3fb5da6/clang/lib/Sema/TreeTransform.h#L14853) from the AST. It is therefore up to [`TreeTransform::RebuildMemberExpr()`](https://github.com/llvm/llvm-project/blob/cd132dcbeb0fc79fd657bd5e0a8e9244c3fb5da6/clang/lib/Sema/TreeTransform.h#L2853) to recreate a `MaterializeTemporaryExpr` if needed. In the [general case](https://github.com/llvm/llvm-project/blob/cd132dcbeb0fc79fd657bd5e0a8e9244c3fb5da6/clang/lib/Sema/TreeTransform.h#L2915), it does this: It calls `Sema::BuildMemberReferenceExpr()`, which ensures that the base is a glvalue by [materializing a temporary](https://github.com/llvm/llvm-project/blob/cd132dcbeb0fc79fd657bd5e0a8e9244c3fb5da6/clang/lib/Sema/SemaExprMember.cpp#L1016) if needed. However, when `TreeTransform::RebuildMemberExpr()` encounters an anonymous class, it [calls `Sema::BuildFieldReferenceExpr()`](https://github.com/llvm/llvm-project/blob/cd132dcbeb0fc79fd657bd5e0a8e9244c3fb5da6/clang/lib/Sema/TreeTransform.h#L2880), which, unlike `Sema::BuildMemberReferenceExpr()`, does not make sure that the base is a glvalue. # Proposed fix I considered several possible ways to fix this issue: - Add logic to `Sema::BuildFieldReferenceExpr()` that materializes a temporary if needed. This appears to work, but it feels like the fix is in the wrong place: - AFAIU, other callers of `Sema::BuildFieldReferenceExpr()` don't need this logic. - The issue is caused by `TreeTransform` removing the `MaterializeTemporaryExpr`, so it seems the fix should also be in `TreeTransform` - Materialize the temporary directly in `TreeTransform::RebuildMemberExpr()` if needed (within the case that deals with anonymous classes). This would work, too, but it would duplicate logic that already exists in `Sema::BuildMemberReferenceExpr()` (which we leverage for the general case). - Use `Sema::BuildMemberReferenceExpr()` instead of `Sema::BuildFieldReferenceExpr()` for the anonymous class case, so that it also uses the existing logic for materializing the temporary. This is the option I've decided to go with here. There's a slight wrinkle in that we create a `LookupResult` that claims we looked up the unnamed field for the anonymous class -- even though we would obviously never be able to look up an unnamed field. I think this is defensible and still better than the other alternatives, but I would welcome feedback on this from others who know the code better.
1 parent 2e165a2 commit fcd020d

File tree

4 files changed

+97
-1
lines changed

4 files changed

+97
-1
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,7 @@ Bug Fixes to AST Handling
715715
^^^^^^^^^^^^^^^^^^^^^^^^^
716716
- Clang now properly preserves ``FoundDecls`` within a ``ConceptReference``. (#GH82628)
717717
- The presence of the ``typename`` keyword is now stored in ``TemplateTemplateParmDecl``.
718+
- Fixed malformed AST generated for anonymous union access in templates. (#GH90842)
718719

719720
Miscellaneous Bug Fixes
720721
^^^^^^^^^^^^^^^^^^^^^^^

clang/lib/Sema/TreeTransform.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2874,10 +2874,21 @@ class TreeTransform {
28742874
return ExprError();
28752875
Base = BaseResult.get();
28762876

2877+
// `TranformMaterializeTemporaryExpr()` removes materialized temporaries
2878+
// from the AST, so we need to re-insert them if needed (since
2879+
// `BuildFieldRefereneExpr()` doesn't do this).
2880+
if (!isArrow && Base->isPRValue()) {
2881+
BaseResult = getSema().TemporaryMaterializationConversion(Base);
2882+
if (BaseResult.isInvalid())
2883+
return ExprError();
2884+
Base = BaseResult.get();
2885+
}
2886+
28772887
CXXScopeSpec EmptySS;
28782888
return getSema().BuildFieldReferenceExpr(
28792889
Base, isArrow, OpLoc, EmptySS, cast<FieldDecl>(Member),
2880-
DeclAccessPair::make(FoundDecl, FoundDecl->getAccess()), MemberNameInfo);
2890+
DeclAccessPair::make(FoundDecl, FoundDecl->getAccess()),
2891+
MemberNameInfo);
28812892
}
28822893

28832894
CXXScopeSpec SS;
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-unknown -ast-dump %s \
2+
// RUN: | FileCheck -strict-whitespace %s
3+
4+
struct S {
5+
struct {
6+
int i;
7+
};
8+
};
9+
10+
int accessInRegularFunction() {
11+
return S().i;
12+
// CHECK: FunctionDecl {{.*}} accessInRegularFunction 'int ()'
13+
// CHECK: | `-ReturnStmt {{.*}}
14+
// CHECK-NEXT: | `-ExprWithCleanups {{.*}} 'int'
15+
// CHECK-NEXT: | `-ImplicitCastExpr {{.*}} 'int' <LValueToRValue>
16+
// CHECK-NEXT: | `-MemberExpr {{.*}} 'int' xvalue .i
17+
// CHECK-NEXT: | `-MemberExpr {{.*}} 'S::(anonymous struct at {{.*}})
18+
// CHECK-NEXT: | `-MaterializeTemporaryExpr {{.*}} 'S' xvalue
19+
// CHECK-NEXT: | `-CXXTemporaryObjectExpr {{.*}} 'S' 'void () noexcept' zeroing
20+
}
21+
22+
// AST should look the same in a function template with an unused template
23+
// parameter.
24+
template <class>
25+
int accessInFunctionTemplate() {
26+
return S().i;
27+
// CHECK: FunctionDecl {{.*}} accessInFunctionTemplate 'int ()'
28+
// CHECK: | `-ReturnStmt {{.*}}
29+
// CHECK-NEXT: | `-ExprWithCleanups {{.*}} 'int'
30+
// CHECK-NEXT: | `-ImplicitCastExpr {{.*}} 'int' <LValueToRValue>
31+
// CHECK-NEXT: | `-MemberExpr {{.*}} 'int' xvalue .i
32+
// CHECK-NEXT: | `-MemberExpr {{.*}} 'S::(anonymous struct at {{.*}})
33+
// CHECK-NEXT: | `-MaterializeTemporaryExpr {{.*}} 'S' xvalue
34+
// CHECK-NEXT: | `-CXXTemporaryObjectExpr {{.*}} 'S' 'void () noexcept' zeroing
35+
}
36+
37+
// AST should look the same in an instantiation of the function template.
38+
// This is a regression test: The AST used to contain the
39+
// `MaterializeTemporaryExpr` in the wrong place, causing a `MemberExpr` to have
40+
// a prvalue base (which is not allowed in C++).
41+
template int accessInFunctionTemplate<int>();
42+
// CHECK: FunctionDecl {{.*}} accessInFunctionTemplate 'int ()' explicit_instantiation_definition
43+
// CHECK: `-ReturnStmt {{.*}}
44+
// CHECK-NEXT: `-ExprWithCleanups {{.*}} 'int'
45+
// CHECK-NEXT: `-ImplicitCastExpr {{.*}} 'int' <LValueToRValue>
46+
// CHECK-NEXT: `-MemberExpr {{.*}} 'int' xvalue .i
47+
// CHECK-NEXT: `-MemberExpr {{.*}} 'S::(anonymous struct at {{.*}})
48+
// CHECK-NEXT: `-MaterializeTemporaryExpr {{.*}} 'S' xvalue
49+
// CHECK-NEXT: `-CXXTemporaryObjectExpr {{.*}} 'S' 'void () noexcept' zeroing

clang/unittests/Analysis/FlowSensitive/TransferTest.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@
2727
#include <string>
2828
#include <utility>
2929

30+
namespace clang {
31+
namespace dataflow {
32+
namespace {
33+
AST_MATCHER(FunctionDecl, isTemplated) { return Node.isTemplated(); }
34+
} // namespace
35+
} // namespace dataflow
36+
} // namespace clang
37+
3038
namespace {
3139

3240
using namespace clang;
@@ -7416,4 +7424,31 @@ TEST(TransferTest, ConditionalRelation) {
74167424
});
74177425
}
74187426

7427+
// This is a crash repro.
7428+
// We used to crash while transferring `S().i` because Clang contained a bug
7429+
// causing the AST to be malformed.
7430+
TEST(TransferTest, AnonymousUnionMemberExprInTemplate) {
7431+
using ast_matchers::functionDecl;
7432+
using ast_matchers::hasName;
7433+
using ast_matchers::unless;
7434+
7435+
std::string Code = R"cc(
7436+
struct S {
7437+
struct {
7438+
int i;
7439+
};
7440+
};
7441+
7442+
template <class>
7443+
void target() {
7444+
S().i;
7445+
}
7446+
7447+
template void target<int>();
7448+
)cc";
7449+
auto Matcher = functionDecl(hasName("target"), unless(isTemplated()));
7450+
ASSERT_THAT_ERROR(checkDataflowWithNoopAnalysis(Code, Matcher),
7451+
llvm::Succeeded());
7452+
}
7453+
74197454
} // namespace

0 commit comments

Comments
 (0)