aaron.ballman created this revision.
aaron.ballman added reviewers: rsmith, nickdesaulniers, erichkeane, jyknight, 
clang-language-wg.
Herald added a project: All.
aaron.ballman requested review of this revision.
Herald added a project: clang.

We were accepting invalid code where the qualifiers of the anonymous structure 
were not taken into account when forming a member access to one of the indirect 
fields, as in:

  struct S {
    const struct {
      int i;
    };
  } s;
  
  void foo(void) {
    s.i = 12; // previously accepted, now rejected
  }

We now expose the anonymous structure's field as having a qualified type 
instead of an unqualified type so that access checking notes that an 
intermediate object in the expression is qualified. We also adjusted the 
diagnostic so that it's slightly more user friendly despite this being a rather 
rare case.

Note, this only impacts C; in C++ and with the Microsoft C anonymous structure 
extension, the qualifiers are stripped.

Fixes #48099


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D125167

Files:
  clang/docs/ReleaseNotes.rst
  clang/include/clang/Basic/DiagnosticSemaKinds.td
  clang/lib/Sema/SemaDecl.cpp
  clang/lib/Sema/SemaExpr.cpp
  clang/test/Sema/anonymous-struct-union.c
  clang/test/Sema/anonymous-struct-union.cpp

Index: clang/test/Sema/anonymous-struct-union.cpp
===================================================================
--- /dev/null
+++ clang/test/Sema/anonymous-struct-union.cpp
@@ -0,0 +1,34 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fsyntax-only -verify -fms-extensions -Wno-microsoft-anon-tag -x c %s
+// expected-no-diagnostics
+
+struct trivial {
+  int a;
+};
+
+struct GH48099 {
+  const struct {
+    int i;
+  };
+#ifndef __cplusplus
+  // This extension is only allowed in C.
+  const struct trivial;
+#endif
+};
+
+// In C++, qualifiers are ignored on the anonymous objects.
+void GH48099_test(void) {
+  struct GH48099 x;
+
+#ifdef __cplusplus  
+  // Test that the member access ignores the qualifiers. This would fail in C.
+  x.i = 12; // ok
+#else
+  // Test that the member access ignores the qualifiers for a Microsoft C
+  // anonymous structure declaration. This would fail in C++ because the
+  // declaration in GH48099 does not declare anything (so 'a' isn't lifted into
+  // the outer structure).
+  x.a = 12; // ok
+#endif
+}
+
Index: clang/test/Sema/anonymous-struct-union.c
===================================================================
--- clang/test/Sema/anonymous-struct-union.c
+++ clang/test/Sema/anonymous-struct-union.c
@@ -119,3 +119,26 @@
   struct s3 s;
   s.A = 1; // expected-warning {{'A' is deprecated}}
 }
+
+struct GH48099 {
+  const struct { // expected-note {{anonymous struct declared const here}}
+    int i;
+  };
+  volatile union {
+    int j;
+  };
+};
+
+// Ensure that qualifiers from the anonymous object are reflected on the
+// fields being hoisted into the outer context.
+void GH48099_test(void) {
+  struct GH48099 x;
+
+  // It's the access path that picks up the qualifiers, not the direct
+  // declaration of the field itself. So 'i' and 'j' are both 'int'.
+  _Static_assert(_Generic(x.i, int : 1, default : 0), "i is not int?");
+  _Static_assert(_Generic(x.j, int : 1, default : 0), "j is not int?");
+
+  // Test that the member access picks up the qualifiers.
+  x.i = 12; // expected-error {{cannot assign to non-static data member 'i' with const-qualified type 'const int'}}
+}
Index: clang/lib/Sema/SemaExpr.cpp
===================================================================
--- clang/lib/Sema/SemaExpr.cpp
+++ clang/lib/Sema/SemaExpr.cpp
@@ -13351,6 +13351,7 @@
   ConstMember,
   ConstMethod,
   NestedConstMember,
+  ConstAnonymousObject, // Only used as a note
   ConstUnknown,  // Keep as last element
 };
 
@@ -13370,6 +13371,10 @@
   // next checked expression is the result of a dereference.
   bool IsDereference = false;
   bool NextIsDereference = false;
+  // Used when diagnosing indirect fields that are const because the anonymous
+  // object is const rather than the field. This allows us to diagnose the
+  // field rather than the anonymous object.
+  const FieldDecl *LastNamedField = nullptr;
 
   // Loop to process MemberExpr chains.
   while (true) {
@@ -13388,16 +13393,44 @@
 
         if (!IsTypeModifiable(Field->getType(), IsDereference)) {
           if (!DiagnosticEmitted) {
-            S.Diag(Loc, diag::err_typecheck_assign_const)
-                << ExprRange << ConstMember << false /*static*/ << Field
-                << Field->getType();
+            // If the field is an anonymous union or structure, we want to
+            // diagnose the last named field rather than the unnamed object.
+            // This requires us to combine the qualifiers from the anonymous
+            // object with those of the field in order for the diagnostic to
+            // make sense.
+            if (Field->isAnonymousStructOrUnion()) {
+              assert(LastNamedField &&
+                     "we should have seen a named field by this point");
+              S.Diag(Loc, diag::err_typecheck_assign_const)
+                  << ExprRange << ConstMember
+                  << false /*static*/ << LastNamedField
+                  << LastNamedField->getType().withFastQualifiers(
+                         Field->getType().getLocalFastQualifiers());
+            } else {
+              S.Diag(Loc, diag::err_typecheck_assign_const)
+                  << ExprRange << ConstMember << false /*static*/ << Field
+                  << Field->getType();
+            }
             DiagnosticEmitted = true;
           }
-          S.Diag(VD->getLocation(), diag::note_typecheck_assign_const)
-              << ConstMember << false /*static*/ << Field << Field->getType()
-              << Field->getSourceRange();
+          // If the field is an anonymous union or structure, we want the note
+          // to be about the anonymous object being const rather than the
+          // indirect field.
+          if (Field->isAnonymousStructOrUnion()) {
+            S.Diag(VD->getLocation(), diag::note_typecheck_assign_const)
+                << ConstAnonymousObject
+                << Field->getType()
+                       ->castAs<RecordType>()
+                       ->getDecl()
+                       ->isStruct();
+          } else {
+            S.Diag(VD->getLocation(), diag::note_typecheck_assign_const)
+                << ConstMember << false /*static*/ << Field << Field->getType()
+                << Field->getSourceRange();
+          }
         }
         E = ME->getBase();
+        LastNamedField = Field;
         continue;
       } else if (const VarDecl *VDecl = dyn_cast<VarDecl>(VD)) {
         if (VDecl->getType().isConstQualified()) {
Index: clang/lib/Sema/SemaDecl.cpp
===================================================================
--- clang/lib/Sema/SemaDecl.cpp
+++ clang/lib/Sema/SemaDecl.cpp
@@ -5498,7 +5498,10 @@
   if (RecordDecl *OwningClass = dyn_cast<RecordDecl>(Owner)) {
     Anon = FieldDecl::Create(
         Context, OwningClass, DS.getBeginLoc(), Record->getLocation(),
-        /*IdentifierInfo=*/nullptr, Context.getTypeDeclType(Record), TInfo,
+                             /*IdentifierInfo=*/nullptr,
+                             Context.getTypeDeclType(Record).withFastQualifiers(
+                                 DS.getTypeQualifiers()),
+                             TInfo,
         /*BitWidth=*/nullptr, /*Mutable=*/false,
         /*InitStyle=*/ICIS_NoInit);
     Anon->setAccess(AS);
@@ -5520,7 +5523,9 @@
     assert(DS.getAttributes().empty() && "No attribute expected");
     Anon = VarDecl::Create(Context, Owner, DS.getBeginLoc(),
                            Record->getLocation(), /*IdentifierInfo=*/nullptr,
-                           Context.getTypeDeclType(Record), TInfo, SC);
+                           Context.getTypeDeclType(Record).withFastQualifiers(
+                               DS.getTypeQualifiers()),
+                           TInfo, SC);
 
     // Default-initialize the implicit variable. This initialization will be
     // trivial in almost all cases, except if a union member has an in-class
Index: clang/include/clang/Basic/DiagnosticSemaKinds.td
===================================================================
--- clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -7010,6 +7010,7 @@
   "cannot assign to non-static data member within const member function %1|"
   "cannot assign to %select{variable %2|non-static data member %2|lvalue}1 "
   "with %select{|nested }3const-qualified data member %4|"
+  "|" // Unused for anonymous objects
   "read-only variable is not assignable}0">;
 
 def note_typecheck_assign_const : Note<
@@ -7018,7 +7019,8 @@
   "variable %1 declared const here|"
   "%select{non-|}1static data member %2 declared const here|"
   "member function %q1 is declared const here|"
-  "%select{|nested }1data member %2 declared const here}0">;
+  "%select{|nested }1data member %2 declared const here|"
+  "anonymous %select{union|struct}1 declared const here}0">;
 
 def warn_unsigned_always_true_comparison : Warning<
   "result of comparison of %select{%3|unsigned expression}0 %2 "
Index: clang/docs/ReleaseNotes.rst
===================================================================
--- clang/docs/ReleaseNotes.rst
+++ clang/docs/ReleaseNotes.rst
@@ -149,6 +149,12 @@
   because there is no way to fully qualify the enumerator name, so this
   "extension" was unintentional and useless. This fixes
   `Issue 42372 <https://github.com/llvm/llvm-project/issues/42372>`_.
+- When forming a member expression, now consider any qualifiers written on an
+  anonymous structure or union as also applying to the field being referenced.
+  This fixes an issue where qualifiers were being ignored, allowing you to
+  assign to a ``const`` field. Note that qualifiers are ignored in C++ and for
+  Microsoft's extension of anonymous objects. This fixes
+  `Issues 48099 <https://github.com/llvm/llvm-project/issues/48099>`_.
 
 Improvements to Clang's diagnostics
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to