diff mbox series

[pushed] c++: layout of aggregate base with DMI [PR103681]

Message ID 20211217032655.711331-1-jason@redhat.com
State New
Headers show
Series [pushed] c++: layout of aggregate base with DMI [PR103681] | expand

Commit Message

Jason Merrill Dec. 17, 2021, 3:26 a.m. UTC
C++14 changed the definition of 'aggregate' to allow default member
initializers, but such classes still need to be considered "non-POD for the
purpose of layout" for ABI compatibility with C++11 code.  It seems rare to
derive from such a class, as evidenced by how long this bug has
survived (since r216750 in 2014), but it's certainly worth fixing.

We only warn when we were failing to allocate another field into the
tail padding of the newly aggregate class; this is the only ABI impact.

This also changes end_of_class to consider all data members, not just empty
data members; that used to be an additional flag, removed in r9-5710, but I
don't see any reason not to always include them.  This makes the result of
the function correspond to the ABI nvsize term and its nameless counterpart
that does include virtual bases.

When looking closely at other users of end_of_class, I realized that we were
assuming that the latter corresponded to the ABI dsize term, but it doesn't
if the class ends with an empty virtual base (in the rare case that the
empty base can't be assigned offset 0), and this matters for layout of
[[no_unique_address]].  So I added another mode that returns the desired
value for that case.  I'm not adding a warning for this ABI fix because it's
a C++20 feature.

Tested x86_64-pc-linux-gnu, applying to trunk.

	PR c++/103681

gcc/ChangeLog:

	* common.opt (fabi-version): Add v17.

gcc/cp/ChangeLog:

	* cp-tree.h (struct lang_type): Add non_pod_aggregate.
	(CLASSTYPE_NON_POD_AGGREGATE): New.
	* class.c (check_field_decls): Set it.
	(check_bases_and_members): Check it.
	(check_non_pod_aggregate): New.
	(enum eoc_mode): New.
	(end_of_class): Always include non-empty fields.
	Add eoc_nv_or_dsize mode.
	(include_empty_classes, layout_class_type): Adjust.

gcc/c-family/ChangeLog:

	* c-opts.c (c_common_post_options): Update defaults.

gcc/testsuite/ChangeLog:

	* g++.dg/abi/macro0.C: Update value.
	* g++.dg/abi/no_unique_address6.C: New test.
	* g++.dg/abi/nsdmi-aggr1.C: New test.
	* g++.dg/abi/nsdmi-aggr1a.C: New test.
---
 gcc/common.opt                                |   4 +
 gcc/cp/cp-tree.h                              |   9 +-
 gcc/c-family/c-opts.c                         |   6 +-
 gcc/cp/class.c                                | 145 +++++++++++++-----
 gcc/testsuite/g++.dg/abi/macro0.C             |   2 +-
 gcc/testsuite/g++.dg/abi/no_unique_address6.C |  23 +++
 gcc/testsuite/g++.dg/abi/nsdmi-aggr1.C        |  39 +++++
 gcc/testsuite/g++.dg/abi/nsdmi-aggr1a.C       |  35 +++++
 8 files changed, 217 insertions(+), 46 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/abi/no_unique_address6.C
 create mode 100644 gcc/testsuite/g++.dg/abi/nsdmi-aggr1.C
 create mode 100644 gcc/testsuite/g++.dg/abi/nsdmi-aggr1a.C


base-commit: 807ad4bc854caea407aaa09993e4a3220290b0f0

Comments

Hans-Peter Nilsson Jan. 20, 2022, 4:51 a.m. UTC | #1
> From: Jason Merrill via Gcc-patches <gcc-patches@gcc.gnu.org>
> Date: Fri, 17 Dec 2021 04:26:55 +0100

> C++14 changed the definition of 'aggregate' to allow default member
> initializers, but such classes still need to be considered "non-POD for the
> purpose of layout" for ABI compatibility with C++11 code.  It seems rare to
> derive from such a class, as evidenced by how long this bug has
> survived (since r216750 in 2014), but it's certainly worth fixing.
> 
> We only warn when we were failing to allocate another field into the
> tail padding of the newly aggregate class; this is the only ABI impact.
> 
> This also changes end_of_class to consider all data members, not just empty
> data members; that used to be an additional flag, removed in r9-5710, but I
> don't see any reason not to always include them.  This makes the result of
> the function correspond to the ABI nvsize term and its nameless counterpart
> that does include virtual bases.
> 
> When looking closely at other users of end_of_class, I realized that we were
> assuming that the latter corresponded to the ABI dsize term, but it doesn't
> if the class ends with an empty virtual base (in the rare case that the
> empty base can't be assigned offset 0), and this matters for layout of
> [[no_unique_address]].  So I added another mode that returns the desired
> value for that case.  I'm not adding a warning for this ABI fix because it's
> a C++20 feature.
> 
> Tested x86_64-pc-linux-gnu, applying to trunk.
> 
> 	PR c++/103681
> 
> gcc/ChangeLog:
> 
> 	* common.opt (fabi-version): Add v17.
> 
> gcc/cp/ChangeLog:
> 
> 	* cp-tree.h (struct lang_type): Add non_pod_aggregate.
> 	(CLASSTYPE_NON_POD_AGGREGATE): New.
> 	* class.c (check_field_decls): Set it.
> 	(check_bases_and_members): Check it.
> 	(check_non_pod_aggregate): New.
> 	(enum eoc_mode): New.
> 	(end_of_class): Always include non-empty fields.
> 	Add eoc_nv_or_dsize mode.
> 	(include_empty_classes, layout_class_type): Adjust.

Looks like this caused https://gcc.gnu.org/PR104139.

brgds, H-P
diff mbox series

Patch

diff --git a/gcc/common.opt b/gcc/common.opt
index 8f8fc2f9ee7..4af89ce8fd2 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -990,6 +990,10 @@  Driver Undocumented
 ;     Adds missing 'on' in mangling of operator names in some cases.
 ;     Default in G++ 11.
 ;
+; 17: Fixes layout of classes that inherit from aggregate classes with default
+;     member initializers in C++14 and up.
+;     Default in G++ 12.
+;
 ; Additional positive integers will be assigned as new versions of
 ; the ABI become the default version of the ABI.
 fabi-version=
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 7f32cf56383..191143025a3 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -2343,6 +2343,7 @@  struct GTY(()) lang_type {
   unsigned unique_obj_representations : 1;
   unsigned unique_obj_representations_set : 1;
   bool erroneous : 1;
+  bool non_pod_aggregate : 1;
 
   /* When adding a flag here, consider whether or not it ought to
      apply to a template instance if it applies to the template.  If
@@ -2351,7 +2352,7 @@  struct GTY(()) lang_type {
   /* There are some bits left to fill out a 32-bit word.  Keep track
      of this by updating the size of this bitfield whenever you add or
      remove a flag.  */
-  unsigned dummy : 4;
+  unsigned dummy : 3;
 
   tree primary_base;
   vec<tree_pair_s, va_gc> *vcall_indices;
@@ -2671,6 +2672,12 @@  struct GTY(()) lang_type {
 /* True if we saw errors while instantiating this class.  */
 #define CLASSTYPE_ERRONEOUS(NODE) \
   (LANG_TYPE_CLASS_CHECK (NODE)->erroneous)
+
+/* True if this class is non-layout-POD only because it was not an aggregate
+   before C++14.  If we run out of bits in lang_type, this could be replaced
+   with a hash_set only filled in when abi_version_crosses (17).  */
+#define CLASSTYPE_NON_POD_AGGREGATE(NODE) \
+  (LANG_TYPE_CLASS_CHECK (NODE)->non_pod_aggregate)
 
 /* Additional macros for inheritance information.  */
 
diff --git a/gcc/c-family/c-opts.c b/gcc/c-family/c-opts.c
index 93845d57dee..4c20e44f5b5 100644
--- a/gcc/c-family/c-opts.c
+++ b/gcc/c-family/c-opts.c
@@ -975,9 +975,9 @@  c_common_post_options (const char **pfilename)
 
   /* Change flag_abi_version to be the actual current ABI level, for the
      benefit of c_cpp_builtins, and to make comparison simpler.  */
-  const int latest_abi_version = 16;
-  /* Generate compatibility aliases for ABI v11 (7.1) by default.  */
-  const int abi_compat_default = 11;
+  const int latest_abi_version = 17;
+  /* Generate compatibility aliases for ABI v13 (8.2) by default.  */
+  const int abi_compat_default = 13;
 
 #define clamp(X) if (X == 0 || X > latest_abi_version) X = latest_abi_version
   clamp (flag_abi_version);
diff --git a/gcc/cp/class.c b/gcc/cp/class.c
index c30a44f039b..e70c535b3f2 100644
--- a/gcc/cp/class.c
+++ b/gcc/cp/class.c
@@ -3857,9 +3857,14 @@  check_field_decls (tree t, tree *access_decls,
 
       /* Now that we've removed bit-field widths from DECL_INITIAL,
 	 anything left in DECL_INITIAL is an NSDMI that makes the class
-	 non-aggregate in C++11.  */
-      if (DECL_INITIAL (field) && cxx_dialect < cxx14)
-	CLASSTYPE_NON_AGGREGATE (t) = true;
+	 non-aggregate in C++11, and non-layout-POD always.  */
+      if (DECL_INITIAL (field))
+	{
+	  if (cxx_dialect < cxx14)
+	    CLASSTYPE_NON_AGGREGATE (t) = true;
+	  else
+	    CLASSTYPE_NON_POD_AGGREGATE (t) = true;
+	}
 
       if (CP_TYPE_CONST_P (type))
 	{
@@ -6045,6 +6050,16 @@  check_bases_and_members (tree t)
   TYPE_HAS_COMPLEX_MOVE_ASSIGN (t) |= TYPE_CONTAINS_VPTR_P (t);
   TYPE_HAS_COMPLEX_DFLT (t) |= TYPE_CONTAINS_VPTR_P (t);
 
+  /* Is this class non-layout-POD because it wasn't an aggregate in C++98?  */
+  if (CLASSTYPE_NON_POD_AGGREGATE (t))
+    {
+      if (CLASSTYPE_NON_LAYOUT_POD_P (t))
+	/* It's non-POD for another reason.  */
+	CLASSTYPE_NON_POD_AGGREGATE (t) = false;
+      else if (abi_version_at_least (17))
+	CLASSTYPE_NON_LAYOUT_POD_P (t) = true;
+    }
+
   /* If the only explicitly declared default constructor is user-provided,
      set TYPE_HAS_COMPLEX_DFLT.  */
   if (!TYPE_HAS_COMPLEX_DFLT (t)
@@ -6341,12 +6356,17 @@  end_of_base (tree binfo)
   return size_binop (PLUS_EXPR, BINFO_OFFSET (binfo), size);
 }
 
-/* Returns the offset of the byte just past the end of the base class or empty
-   data member with the highest offset in T.  If INCLUDE_VIRTUALS_P is zero,
-   then only non-virtual bases are included.  */
+/* Returns one of three variations of the ending offset of T.  If MODE is
+   eoc_nvsize, the result is the ABI "nvsize" (i.e. sizeof before allocating
+   vbases).  If MODE is eoc_vsize, the result is the sizeof after allocating
+   vbases but before rounding, which is not named in the ABI.  If MODE is
+   eoc_nv_or_dsize, the result is the greater of "nvsize" and "dsize" (the size
+   of the actual data in the class, kinda), as used for allocation of
+   potentially-overlapping fields.  */
 
+enum eoc_mode { eoc_nvsize, eoc_vsize, eoc_nv_or_dsize };
 static tree
-end_of_class (tree t, bool include_virtuals_p)
+end_of_class (tree t, eoc_mode mode)
 {
   tree result = size_zero_node;
   vec<tree, va_gc> *vbases;
@@ -6358,8 +6378,7 @@  end_of_class (tree t, bool include_virtuals_p)
   for (binfo = TYPE_BINFO (t), i = 0;
        BINFO_BASE_ITERATE (binfo, i, base_binfo); ++i)
     {
-      if (!include_virtuals_p
-	  && BINFO_VIRTUAL_P (base_binfo)
+      if (BINFO_VIRTUAL_P (base_binfo)
 	  && (!BINFO_PRIMARY_P (base_binfo)
 	      || BINFO_INHERITANCE_CHAIN (base_binfo) != TYPE_BINFO (t)))
 	continue;
@@ -6369,25 +6388,35 @@  end_of_class (tree t, bool include_virtuals_p)
 	result = offset;
     }
 
-  /* Also consider empty data members.  */
   for (tree field = TYPE_FIELDS (t); field; field = DECL_CHAIN (field))
     if (TREE_CODE (field) == FIELD_DECL
-	&& !DECL_ARTIFICIAL (field)
-	&& field_poverlapping_p (field)
-	&& is_empty_class (TREE_TYPE (field)))
+	&& !DECL_FIELD_IS_BASE (field))
       {
-	/* Update sizeof(C) to max (sizeof(C), offset(D)+sizeof(D)) */
-	offset = size_binop (PLUS_EXPR, DECL_FIELD_OFFSET (field),
-			     TYPE_SIZE_UNIT (TREE_TYPE (field)));
+	tree size = DECL_SIZE_UNIT (field);
+	if (!size)
+	  /* DECL_SIZE_UNIT can be null for a flexible array.  */
+	  continue;
+
+	if (is_empty_field (field))
+	  /* For empty fields DECL_SIZE_UNIT is 0, but we want the
+	     size of the type (usually 1) for computing nvsize.  */
+	  size = TYPE_SIZE_UNIT (TREE_TYPE (field));
+
+	offset = size_binop (PLUS_EXPR, byte_position (field), size);
 	if (tree_int_cst_lt (result, offset))
 	  result = offset;
       }
 
-  if (include_virtuals_p)
+  if (mode != eoc_nvsize)
     for (vbases = CLASSTYPE_VBASECLASSES (t), i = 0;
 	 vec_safe_iterate (vbases, i, &base_binfo); i++)
       {
-	offset = end_of_base (base_binfo);
+	if (mode == eoc_nv_or_dsize)
+	  /* For dsize, don't count trailing empty bases.  */
+	  offset = size_binop (PLUS_EXPR, BINFO_OFFSET (binfo),
+			       CLASSTYPE_SIZE_UNIT (BINFO_TYPE (binfo)));
+	else
+	  offset = end_of_base (base_binfo);
 	if (tree_int_cst_lt (result, offset))
 	  result = offset;
       }
@@ -6395,6 +6424,45 @@  end_of_class (tree t, bool include_virtuals_p)
   return result;
 }
 
+/* Warn as appropriate about the change in whether we pack into the tail
+   padding of FIELD, a base field which has a C++14 aggregate type with default
+   member initializers.  */
+
+static void
+check_non_pod_aggregate (tree field)
+{
+  if (!abi_version_crosses (17) || cxx_dialect < cxx14)
+    return;
+  if (TREE_CODE (field) != FIELD_DECL
+      || (!DECL_FIELD_IS_BASE (field)
+	  && !field_poverlapping_p (field)))
+    return;
+  tree next = DECL_CHAIN (field);
+  while (next && TREE_CODE (next) != FIELD_DECL) next = DECL_CHAIN (next);
+  if (!next)
+    return;
+  tree type = TREE_TYPE (field);
+  if (TYPE_IDENTIFIER (type) == as_base_identifier)
+    type = TYPE_CONTEXT (type);
+  if (!CLASS_TYPE_P (type) || !CLASSTYPE_NON_POD_AGGREGATE (type))
+    return;
+  tree size = end_of_class (type, (DECL_FIELD_IS_BASE (field)
+				   ? eoc_nvsize : eoc_nv_or_dsize));
+  tree rounded = round_up_loc (input_location, size, DECL_ALIGN_UNIT (next));
+  if (tree_int_cst_lt (rounded, TYPE_SIZE_UNIT (type)))
+    {
+      location_t loc = DECL_SOURCE_LOCATION (next);
+      if (DECL_FIELD_IS_BASE (next))
+	warning_at (loc, OPT_Wabi,"offset of %qT base class for "
+		    "%<-std=c++14%> and up changes in "
+		    "%<-fabi-version=17%> (GCC 12)", TREE_TYPE (next));
+      else
+	warning_at (loc, OPT_Wabi, "offset of %qD for "
+		    "%<-std=c++14%> and up changes in "
+		    "%<-fabi-version=17%> (GCC 12)", next);
+    }
+}
+
 /* Warn about bases of T that are inaccessible because they are
    ambiguous.  For example:
 
@@ -6468,7 +6536,7 @@  include_empty_classes (record_layout_info rli)
      because we are willing to overlay multiple bases at the same
      offset.  However, now we need to make sure that RLI is big enough
      to reflect the entire class.  */
-  eoc = end_of_class (rli->t, CLASSTYPE_AS_BASE (rli->t) != NULL_TREE);
+  eoc = end_of_class (rli->t, eoc_vsize);
   rli_size = rli_size_unit_so_far (rli);
   if (TREE_CODE (rli_size) == INTEGER_CST
       && tree_int_cst_lt (rli_size, eoc))
@@ -6580,21 +6648,13 @@  layout_class_type (tree t, tree *virtuals_p)
 	{
 	  /* if D is a potentially-overlapping data member, update sizeof(C) to
 	     max (sizeof(C), offset(D)+max (nvsize(D), dsize(D))).  */
-	  tree nvsize = CLASSTYPE_SIZE_UNIT (type);
-	  /* end_of_class doesn't always give dsize, but it does in the case of
-	     a class with virtual bases, which is when dsize > nvsize.  */
-	  tree dsize = end_of_class (type, /*vbases*/true);
 	  if (CLASSTYPE_EMPTY_P (type))
 	    DECL_SIZE (field) = DECL_SIZE_UNIT (field) = size_zero_node;
-	  else if (tree_int_cst_le (dsize, nvsize))
-	    {
-	      DECL_SIZE_UNIT (field) = nvsize;
-	      DECL_SIZE (field) = CLASSTYPE_SIZE (type);
-	    }
 	  else
 	    {
-	      DECL_SIZE_UNIT (field) = dsize;
-	      DECL_SIZE (field) = bit_from_pos (dsize, bitsize_zero_node);
+	      tree size = end_of_class (type, eoc_nv_or_dsize);
+	      DECL_SIZE_UNIT (field) = size;
+	      DECL_SIZE (field) = bit_from_pos (size, bitsize_zero_node);
 	    }
 	}
 
@@ -6760,16 +6820,19 @@  layout_class_type (tree t, tree *virtuals_p)
      instead, so that the backends can emit -Wpsabi warnings in the cases
      where the ABI changed.  */
   for (field = TYPE_FIELDS (t); field; field = DECL_CHAIN (field))
-    if (TREE_CODE (field) == FIELD_DECL
-	&& DECL_C_BIT_FIELD (field)
-	/* We should not be confused by the fact that grokbitfield
-	   temporarily sets the width of the bit field into
-	   DECL_BIT_FIELD_REPRESENTATIVE (field).
-	   check_bitfield_decl eventually sets DECL_SIZE (field)
-	   to that width.  */
-	&& (DECL_SIZE (field) == NULL_TREE
-	    || integer_zerop (DECL_SIZE (field))))
-      SET_DECL_FIELD_CXX_ZERO_WIDTH_BIT_FIELD (field, 1);
+    {
+      if (TREE_CODE (field) == FIELD_DECL
+	  && DECL_C_BIT_FIELD (field)
+	  /* We should not be confused by the fact that grokbitfield
+	     temporarily sets the width of the bit field into
+	     DECL_BIT_FIELD_REPRESENTATIVE (field).
+	     check_bitfield_decl eventually sets DECL_SIZE (field)
+	     to that width.  */
+	  && (DECL_SIZE (field) == NULL_TREE
+	      || integer_zerop (DECL_SIZE (field))))
+	SET_DECL_FIELD_CXX_ZERO_WIDTH_BIT_FIELD (field, 1);
+      check_non_pod_aggregate (field);
+    }
 
   if (CLASSTYPE_NON_LAYOUT_POD_P (t) || CLASSTYPE_EMPTY_P (t))
     {
@@ -6792,7 +6855,7 @@  layout_class_type (tree t, tree *virtuals_p)
 	 used to compute TYPE_SIZE_UNIT.  */
 
       /* Set the size and alignment for the new type.  */
-      tree eoc = end_of_class (t, /*include_virtuals_p=*/0);
+      tree eoc = end_of_class (t, eoc_nvsize);
       TYPE_SIZE_UNIT (base_t)
 	= size_binop (MAX_EXPR,
 		      fold_convert (sizetype,
diff --git a/gcc/testsuite/g++.dg/abi/macro0.C b/gcc/testsuite/g++.dg/abi/macro0.C
index f25f291dba6..2d07fcdc631 100644
--- a/gcc/testsuite/g++.dg/abi/macro0.C
+++ b/gcc/testsuite/g++.dg/abi/macro0.C
@@ -1,6 +1,6 @@ 
 // This testcase will need to be kept in sync with c_common_post_options.
 // { dg-options "-fabi-version=0" }
 
-#if __GXX_ABI_VERSION != 1016
+#if __GXX_ABI_VERSION != 1017
 #error "Incorrect value of __GXX_ABI_VERSION"
 #endif
diff --git a/gcc/testsuite/g++.dg/abi/no_unique_address6.C b/gcc/testsuite/g++.dg/abi/no_unique_address6.C
new file mode 100644
index 00000000000..5df6995c619
--- /dev/null
+++ b/gcc/testsuite/g++.dg/abi/no_unique_address6.C
@@ -0,0 +1,23 @@ 
+// { dg-do compile { target c++20 } }
+
+#pragma GCC diagnostic ignored "-Winvalid-offsetof"
+
+struct E { };
+
+struct A: virtual E {
+  // Deny the vbase offset 0 so it goes at the end.
+  [[no_unique_address]] E e1;
+  char c;
+};
+
+struct B : public A {
+  char d;
+};
+
+struct C {
+  [[no_unique_address]] A a;
+  char d;
+};
+
+#define SA(X) static_assert ((X),#X)
+SA(__builtin_offsetof (B, d) == __builtin_offsetof (C, d));
diff --git a/gcc/testsuite/g++.dg/abi/nsdmi-aggr1.C b/gcc/testsuite/g++.dg/abi/nsdmi-aggr1.C
new file mode 100644
index 00000000000..c212c439a67
--- /dev/null
+++ b/gcc/testsuite/g++.dg/abi/nsdmi-aggr1.C
@@ -0,0 +1,39 @@ 
+// PR c++/103681
+// { dg-do compile { target c++11 } }
+// { dg-additional-options "-fabi-version=16 -Wabi" }
+
+struct A {
+  long l;
+  char c = -1;
+};
+struct B : public A {
+  char d;
+  // { dg-warning "offset" "" { target c++14 } .-1 }
+};
+
+#define SA(X) static_assert(X,#X)
+SA(sizeof (B) == sizeof (A));
+// { dg-error "static assertion" "" { target c++14 } .-1 }
+
+struct X { char d; };
+struct B2 : A, X { };
+// { dg-warning "offset" "" { target c++14 } .-1 }
+SA(sizeof (B2) == sizeof (A));
+// { dg-error "static assertion" "" { target c++14 } .-1 }
+
+#if __cplusplus > 201800L
+
+struct C {
+  [[no_unique_address]] A a;
+  char d;
+  // { dg-warning "offset" "" { target c++20 } .-1 }
+};
+SA(sizeof (C) == sizeof (A));
+// { dg-error "static assertion" "" { target c++20 } .-1 }
+
+struct C2 : A, X { };
+// { dg-warning "offset" "" { target c++20 } .-1 }
+SA(sizeof (B2) == sizeof (A));
+// { dg-error "static assertion" "" { target c++20 } .-1 }
+
+#endif /* C++20 */
diff --git a/gcc/testsuite/g++.dg/abi/nsdmi-aggr1a.C b/gcc/testsuite/g++.dg/abi/nsdmi-aggr1a.C
new file mode 100644
index 00000000000..e7a509dbc85
--- /dev/null
+++ b/gcc/testsuite/g++.dg/abi/nsdmi-aggr1a.C
@@ -0,0 +1,35 @@ 
+// PR c++/103681
+// { dg-do compile { target c++11 } }
+// { dg-additional-options "-fabi-version=0 -Wabi=16" }
+
+struct A {
+  long l;
+  char c = -1;
+};
+struct B : public A {
+  char d;
+  // { dg-warning "offset" "" { target c++14 } .-1 }
+};
+
+#define SA(X) static_assert(X,#X)
+SA(sizeof (B) == sizeof (A));
+
+struct X { char d; };
+struct B2 : A, X { };
+// { dg-warning "offset" "" { target c++14 } .-1 }
+SA(sizeof (B2) == sizeof (A));
+
+#if __cplusplus > 201800L
+
+struct C {
+  [[no_unique_address]] A a;
+  char d;
+  // { dg-warning "offset" "" { target c++20 } .-1 }
+};
+SA(sizeof (C) == sizeof (A));
+
+struct C2 : A, X { };
+// { dg-warning "offset" "" { target c++20 } .-1 }
+SA(sizeof (B2) == sizeof (A));
+
+#endif /* C++20 */