[Clang][Sema] Fix malformed AST for anonymous class access in template. by martinboehme · Pull Request #90842 · llvm/llvm-project (original) (raw)

@llvm/pr-subscribers-clang

Author: None (martinboehme)

Changes

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 (that is
essentially identical to the new test this patch adds).

This example sets up a struct containing an anonymous struct:

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 MemberExprs 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 MaterializeTemporaryExprs
from the AST.

It is therefore up to
TreeTransform::RebuildMemberExpr()
to recreate a MaterializeTemporaryExpr if needed. In the general case,
it does this: It calls Sema::BuildMemberReferenceExpr(), which ensures that
the base is a glvalue by
materializing a temporary
if needed. However, when TreeTransform::RebuildMemberExpr() encounters an
anonymous class, it calls Sema::BuildFieldReferenceExpr(),
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:


Full diff: https://github.com/llvm/llvm-project/pull/90842.diff

3 Files Affected:

diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index f47bc219e6fa32..9c800c5705c7c6 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -2876,10 +2876,21 @@ class TreeTransform { return ExprError(); Base = BaseResult.get();

diff --git a/clang/test/AST/ast-dump-anonymous-class.cpp b/clang/test/AST/ast-dump-anonymous-class.cpp new file mode 100644 index 00000000000000..393c084c913d15 --- /dev/null +++ b/clang/test/AST/ast-dump-anonymous-class.cpp @@ -0,0 +1,49 @@ +// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-unknown -ast-dump %s
+// RUN: | FileCheck -strict-whitespace %s + +struct S {

#include #include

+namespace clang { +namespace dataflow { +namespace { +AST_MATCHER(FunctionDecl, isTemplated) { return Node.isTemplated(); } +} // namespace +} // namespace dataflow +} // namespace clang + namespace {

using namespace clang; @@ -7205,4 +7213,31 @@ TEST(TransferTest, ConditionalRelation) { }); }

+// This is a crash repro. +// We used to crash while transferring S().i because Clang contained a bug +// causing the AST to be malformed. +TEST(TransferTest, AnonymousUnionMemberExprInTemplate) {

+} + } // namespace