diff options
| -rw-r--r-- | clang/include/clang/AST/Stmt.h | 10 | ||||
| -rw-r--r-- | clang/include/clang/Basic/DiagnosticSerializationKinds.td | 18 | ||||
| -rw-r--r-- | clang/lib/AST/ODRHash.cpp | 27 | ||||
| -rw-r--r-- | clang/lib/AST/StmtProfile.cpp | 229 | ||||
| -rw-r--r-- | clang/lib/Serialization/ASTReader.cpp | 101 | ||||
| -rw-r--r-- | clang/test/Modules/odr_hash.cpp | 72 | 
6 files changed, 366 insertions, 91 deletions
diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h index e28675d6a82..0224bb24782 100644 --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -39,6 +39,7 @@ namespace clang {    class Expr;    class IdentifierInfo;    class LabelDecl; +  class ODRHash;    class ParmVarDecl;    class PrinterHelper;    struct PrintingPolicy; @@ -436,6 +437,15 @@ public:    /// written in the source.    void Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Context,                 bool Canonical) const; + +  /// \brief Calculate a unique representation for a statement that is +  /// stable across compiler invocations. +  /// +  /// \param ID profile information will be stored in ID. +  /// +  /// \param Hash an ODRHash object which will be called where pointers would +  /// have been used in the Profile function. +  void ProcessODRHash(llvm::FoldingSetNodeID &ID, ODRHash& Hash) const;  };  /// DeclStmt - Adaptor class for mixing declarations with statements and diff --git a/clang/include/clang/Basic/DiagnosticSerializationKinds.td b/clang/include/clang/Basic/DiagnosticSerializationKinds.td index 30d48aef770..de52d994a8e 100644 --- a/clang/include/clang/Basic/DiagnosticSerializationKinds.td +++ b/clang/include/clang/Basic/DiagnosticSerializationKinds.td @@ -121,10 +121,24 @@ def err_module_odr_violation_mismatch_decl : Error<    "%q0 has different definitions in different modules; first difference is "    "%select{definition in module '%2'|defined here}1 found "    "%select{end of class|public access specifier|private access specifier|" -  "protected access specifier}3">; +  "protected access specifier|static assert}3">;  def note_module_odr_violation_mismatch_decl : Note<"but in '%0' found "    "%select{end of class|public access specifier|private access specifier|" -  "protected access specifier}1">; +  "protected access specifier|static assert}1">; + +def err_module_odr_violation_mismatch_decl_diff : Error< +  "%q0 has different definitions in different modules; first difference is " +  "%select{definition in module '%2'|defined here}1 found " +  "%select{" +  "static assert with condition|" +  "static assert with message|" +  "static assert with %select{|no }4message}3">; + +def note_module_odr_violation_mismatch_decl_diff : Note<"but in '%0' found " +  "%select{" +  "static assert with different condition|" +  "static assert with different message|" +  "static assert with %select{|no }2message}1">;  def warn_module_uses_date_time : Warning<    "%select{precompiled header|module}0 uses __DATE__ or __TIME__">, diff --git a/clang/lib/AST/ODRHash.cpp b/clang/lib/AST/ODRHash.cpp index a74c0380225..9c6f38c2d9d 100644 --- a/clang/lib/AST/ODRHash.cpp +++ b/clang/lib/AST/ODRHash.cpp @@ -22,7 +22,10 @@  using namespace clang; -void ODRHash::AddStmt(const Stmt *S) {} +void ODRHash::AddStmt(const Stmt *S) { +  assert(S && "Expecting non-null pointer."); +  S->ProcessODRHash(ID, *this); +}  void ODRHash::AddIdentifierInfo(const IdentifierInfo *II) {}  void ODRHash::AddNestedNameSpecifier(const NestedNameSpecifier *NNS) {}  void ODRHash::AddTemplateName(TemplateName Name) {} @@ -74,10 +77,18 @@ unsigned ODRHash::CalculateHash() {  class ODRDeclVisitor : public ConstDeclVisitor<ODRDeclVisitor> {    typedef ConstDeclVisitor<ODRDeclVisitor> Inherited;    llvm::FoldingSetNodeID &ID; +  ODRHash &Hash;  public: -  ODRDeclVisitor(llvm::FoldingSetNodeID &ID) -      : ID(ID) {} +  ODRDeclVisitor(llvm::FoldingSetNodeID &ID, ODRHash &Hash) +      : ID(ID), Hash(Hash) {} + +  void AddStmt(const Stmt *S) { +    Hash.AddBoolean(S); +    if (S) { +      Hash.AddStmt(S); +    } +  }    void Visit(const Decl *D) {      ID.AddInteger(D->getKind()); @@ -88,6 +99,13 @@ public:      ID.AddInteger(D->getAccess());      Inherited::VisitAccessSpecDecl(D);    } + +  void VisitStaticAssertDecl(const StaticAssertDecl *D) { +    AddStmt(D->getAssertExpr()); +    AddStmt(D->getMessage()); + +    Inherited::VisitStaticAssertDecl(D); +  }  };  // Only allow a small portion of Decl's to be processed.  Remove this once @@ -100,6 +118,7 @@ bool ODRHash::isWhitelistedDecl(const Decl *D, const CXXRecordDecl *Parent) {      default:        return false;      case Decl::AccessSpec: +    case Decl::StaticAssert:        return true;    }  } @@ -108,7 +127,7 @@ void ODRHash::AddSubDecl(const Decl *D) {    assert(D && "Expecting non-null pointer.");    AddDecl(D); -  ODRDeclVisitor(ID).Visit(D); +  ODRDeclVisitor(ID, *this).Visit(D);  }  void ODRHash::AddCXXRecordDecl(const CXXRecordDecl *Record) { diff --git a/clang/lib/AST/StmtProfile.cpp b/clang/lib/AST/StmtProfile.cpp index 49e43de638a..1ac2a5bf5e1 100644 --- a/clang/lib/AST/StmtProfile.cpp +++ b/clang/lib/AST/StmtProfile.cpp @@ -19,20 +19,22 @@  #include "clang/AST/ExprCXX.h"  #include "clang/AST/ExprObjC.h"  #include "clang/AST/ExprOpenMP.h" +#include "clang/AST/ODRHash.h"  #include "clang/AST/StmtVisitor.h"  #include "llvm/ADT/FoldingSet.h"  using namespace clang;  namespace {    class StmtProfiler : public ConstStmtVisitor<StmtProfiler> { +  protected:      llvm::FoldingSetNodeID &ID; -    const ASTContext &Context;      bool Canonical;    public: -    StmtProfiler(llvm::FoldingSetNodeID &ID, const ASTContext &Context, -                 bool Canonical) -      : ID(ID), Context(Context), Canonical(Canonical) { } +    StmtProfiler(llvm::FoldingSetNodeID &ID, bool Canonical) +        : ID(ID), Canonical(Canonical) {} + +    virtual ~StmtProfiler() {}      void VisitStmt(const Stmt *S); @@ -41,22 +43,25 @@ namespace {      /// \brief Visit a declaration that is referenced within an expression      /// or statement. -    void VisitDecl(const Decl *D); +    virtual void VisitDecl(const Decl *D) = 0;      /// \brief Visit a type that is referenced within an expression or      /// statement. -    void VisitType(QualType T); +    virtual void VisitType(QualType T) = 0;      /// \brief Visit a name that occurs within an expression or statement. -    void VisitName(DeclarationName Name); +    virtual void VisitName(DeclarationName Name) = 0; + +    /// \brief Visit identifiers that are not in Decl's or Type's. +    virtual void VisitIdentifierInfo(IdentifierInfo *II) = 0;      /// \brief Visit a nested-name-specifier that occurs within an expression      /// or statement. -    void VisitNestedNameSpecifier(NestedNameSpecifier *NNS); +    virtual void VisitNestedNameSpecifier(NestedNameSpecifier *NNS) = 0;      /// \brief Visit a template name that occurs within an expression or      /// statement. -    void VisitTemplateName(TemplateName Name); +    virtual void VisitTemplateName(TemplateName Name) = 0;      /// \brief Visit template arguments that occur within an expression or      /// statement. @@ -66,6 +71,127 @@ namespace {      /// \brief Visit a single template argument.      void VisitTemplateArgument(const TemplateArgument &Arg);    }; + +  class StmtProfilerWithPointers : public StmtProfiler { +    const ASTContext &Context; + +  public: +    StmtProfilerWithPointers(llvm::FoldingSetNodeID &ID, +                             const ASTContext &Context, bool Canonical) +        : StmtProfiler(ID, Canonical), Context(Context) {} +  private: +    void VisitDecl(const Decl *D) override { +      ID.AddInteger(D ? D->getKind() : 0); + +      if (Canonical && D) { +        if (const NonTypeTemplateParmDecl *NTTP = +                dyn_cast<NonTypeTemplateParmDecl>(D)) { +          ID.AddInteger(NTTP->getDepth()); +          ID.AddInteger(NTTP->getIndex()); +          ID.AddBoolean(NTTP->isParameterPack()); +          VisitType(NTTP->getType()); +          return; +        } + +        if (const ParmVarDecl *Parm = dyn_cast<ParmVarDecl>(D)) { +          // The Itanium C++ ABI uses the type, scope depth, and scope +          // index of a parameter when mangling expressions that involve +          // function parameters, so we will use the parameter's type for +          // establishing function parameter identity. That way, our +          // definition of "equivalent" (per C++ [temp.over.link]) is at +          // least as strong as the definition of "equivalent" used for +          // name mangling. +          VisitType(Parm->getType()); +          ID.AddInteger(Parm->getFunctionScopeDepth()); +          ID.AddInteger(Parm->getFunctionScopeIndex()); +          return; +        } + +        if (const TemplateTypeParmDecl *TTP = +                dyn_cast<TemplateTypeParmDecl>(D)) { +          ID.AddInteger(TTP->getDepth()); +          ID.AddInteger(TTP->getIndex()); +          ID.AddBoolean(TTP->isParameterPack()); +          return; +        } + +        if (const TemplateTemplateParmDecl *TTP = +                dyn_cast<TemplateTemplateParmDecl>(D)) { +          ID.AddInteger(TTP->getDepth()); +          ID.AddInteger(TTP->getIndex()); +          ID.AddBoolean(TTP->isParameterPack()); +          return; +        } +      } + +      ID.AddPointer(D ? D->getCanonicalDecl() : nullptr); +    } + +    void VisitType(QualType T) override { +      if (Canonical) +        T = Context.getCanonicalType(T); + +      ID.AddPointer(T.getAsOpaquePtr()); +    } + +    void VisitName(DeclarationName Name) override { +      ID.AddPointer(Name.getAsOpaquePtr()); +    } + +    void VisitIdentifierInfo(IdentifierInfo *II) override { +      ID.AddPointer(II); +    } + +    void VisitNestedNameSpecifier(NestedNameSpecifier *NNS) override { +      if (Canonical) +        NNS = Context.getCanonicalNestedNameSpecifier(NNS); +      ID.AddPointer(NNS); +    } + +    void VisitTemplateName(TemplateName Name) override { +      if (Canonical) +        Name = Context.getCanonicalTemplateName(Name); + +      Name.Profile(ID); +    } +  }; + +  class StmtProfilerWithoutPointers : public StmtProfiler { +    ODRHash &Hash; +  public: +    StmtProfilerWithoutPointers(llvm::FoldingSetNodeID &ID, ODRHash &Hash) +        : StmtProfiler(ID, false), Hash(Hash) {} + +  private: +    void VisitType(QualType T) override { +      Hash.AddQualType(T); +    } + +    void VisitName(DeclarationName Name) override { +      Hash.AddDeclarationName(Name); +    } +    void VisitIdentifierInfo(IdentifierInfo *II) override { +      ID.AddBoolean(II); +      if (II) { +        Hash.AddIdentifierInfo(II); +      } +    } +    void VisitDecl(const Decl *D) override { +      ID.AddBoolean(D); +      if (D) { +        Hash.AddDecl(D); +      } +    } +    void VisitTemplateName(TemplateName Name) override { +      Hash.AddTemplateName(Name); +    } +    void VisitNestedNameSpecifier(NestedNameSpecifier *NNS) override { +      ID.AddBoolean(NNS); +      if (NNS) { +        Hash.AddNestedNameSpecifier(NNS); +      } +    } +  };  }  void StmtProfiler::VisitStmt(const Stmt *S) { @@ -853,7 +979,7 @@ void StmtProfiler::VisitOffsetOfExpr(const OffsetOfExpr *S) {        break;      case OffsetOfNode::Identifier: -      ID.AddPointer(ON.getFieldName()); +      VisitIdentifierInfo(ON.getFieldName());        break;      case OffsetOfNode::Base: @@ -861,7 +987,7 @@ void StmtProfiler::VisitOffsetOfExpr(const OffsetOfExpr *S) {        break;      }    } -   +    VisitExpr(S);  } @@ -1451,7 +1577,7 @@ StmtProfiler::VisitCXXPseudoDestructorExpr(const CXXPseudoDestructorExpr *S) {    if (S->getDestroyedTypeInfo())      VisitType(S->getDestroyedType());    else -    ID.AddPointer(S->getDestroyedTypeIdentifier()); +    VisitIdentifierInfo(S->getDestroyedTypeIdentifier());  }  void StmtProfiler::VisitOverloadExpr(const OverloadExpr *S) { @@ -1701,77 +1827,6 @@ void StmtProfiler::VisitObjCAvailabilityCheckExpr(    VisitExpr(S);  } -void StmtProfiler::VisitDecl(const Decl *D) { -  ID.AddInteger(D? D->getKind() : 0); - -  if (Canonical && D) { -    if (const NonTypeTemplateParmDecl *NTTP = -          dyn_cast<NonTypeTemplateParmDecl>(D)) { -      ID.AddInteger(NTTP->getDepth()); -      ID.AddInteger(NTTP->getIndex()); -      ID.AddBoolean(NTTP->isParameterPack()); -      VisitType(NTTP->getType()); -      return; -    } - -    if (const ParmVarDecl *Parm = dyn_cast<ParmVarDecl>(D)) { -      // The Itanium C++ ABI uses the type, scope depth, and scope -      // index of a parameter when mangling expressions that involve -      // function parameters, so we will use the parameter's type for -      // establishing function parameter identity. That way, our -      // definition of "equivalent" (per C++ [temp.over.link]) is at -      // least as strong as the definition of "equivalent" used for -      // name mangling. -      VisitType(Parm->getType()); -      ID.AddInteger(Parm->getFunctionScopeDepth()); -      ID.AddInteger(Parm->getFunctionScopeIndex()); -      return; -    } - -    if (const TemplateTypeParmDecl *TTP = -          dyn_cast<TemplateTypeParmDecl>(D)) { -      ID.AddInteger(TTP->getDepth()); -      ID.AddInteger(TTP->getIndex()); -      ID.AddBoolean(TTP->isParameterPack()); -      return; -    } - -    if (const TemplateTemplateParmDecl *TTP = -          dyn_cast<TemplateTemplateParmDecl>(D)) { -      ID.AddInteger(TTP->getDepth()); -      ID.AddInteger(TTP->getIndex()); -      ID.AddBoolean(TTP->isParameterPack()); -      return; -    } -  } - -  ID.AddPointer(D? D->getCanonicalDecl() : nullptr); -} - -void StmtProfiler::VisitType(QualType T) { -  if (Canonical) -    T = Context.getCanonicalType(T); - -  ID.AddPointer(T.getAsOpaquePtr()); -} - -void StmtProfiler::VisitName(DeclarationName Name) { -  ID.AddPointer(Name.getAsOpaquePtr()); -} - -void StmtProfiler::VisitNestedNameSpecifier(NestedNameSpecifier *NNS) { -  if (Canonical) -    NNS = Context.getCanonicalNestedNameSpecifier(NNS); -  ID.AddPointer(NNS); -} - -void StmtProfiler::VisitTemplateName(TemplateName Name) { -  if (Canonical) -    Name = Context.getCanonicalTemplateName(Name); - -  Name.Profile(ID); -} -  void StmtProfiler::VisitTemplateArguments(const TemplateArgumentLoc *Args,                                            unsigned NumArgs) {    ID.AddInteger(NumArgs); @@ -1821,6 +1876,12 @@ void StmtProfiler::VisitTemplateArgument(const TemplateArgument &Arg) {  void Stmt::Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Context,                     bool Canonical) const { -  StmtProfiler Profiler(ID, Context, Canonical); +  StmtProfilerWithPointers Profiler(ID, Context, Canonical); +  Profiler.Visit(this); +} + +void Stmt::ProcessODRHash(llvm::FoldingSetNodeID &ID, +                          class ODRHash &Hash) const { +  StmtProfilerWithoutPointers Profiler(ID, Hash);    Profiler.Visit(this);  } diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp index 43cdda5c7d7..977cc115ca1 100644 --- a/clang/lib/Serialization/ASTReader.cpp +++ b/clang/lib/Serialization/ASTReader.cpp @@ -8955,6 +8955,7 @@ void ASTReader::diagnoseOdrViolations() {          PublicSpecifer,          PrivateSpecifer,          ProtectedSpecifer, +        StaticAssert,          Other        } FirstDiffType = Other,          SecondDiffType = Other; @@ -8976,6 +8977,8 @@ void ASTReader::diagnoseOdrViolations() {              break;            }            llvm_unreachable("Invalid access specifier"); +        case Decl::StaticAssert: +          return StaticAssert;          }        }; @@ -9047,6 +9050,104 @@ void ASTReader::diagnoseOdrViolations() {          break;        } +      assert(FirstDiffType == SecondDiffType); + +      // Used with err_module_odr_violation_mismatch_decl_diff and +      // note_module_odr_violation_mismatch_decl_diff +      enum ODRDeclDifference{ +        StaticAssertCondition, +        StaticAssertMessage, +        StaticAssertOnlyMessage, +      }; + +      // These lambdas have the common portions of the ODR diagnostics.  This +      // has the same return as Diag(), so addition parameters can be passed +      // in with operator<< +      auto ODRDiagError = [FirstRecord, &FirstModule, this]( +          SourceLocation Loc, SourceRange Range, ODRDeclDifference DiffType) { +        return Diag(Loc, diag::err_module_odr_violation_mismatch_decl_diff) +               << FirstRecord << FirstModule.empty() << FirstModule << Range +               << DiffType; +      }; +      auto ODRDiagNote = [&SecondModule, this]( +          SourceLocation Loc, SourceRange Range, ODRDeclDifference DiffType) { +        return Diag(Loc, diag::note_module_odr_violation_mismatch_decl_diff) +               << SecondModule << Range << DiffType; +      }; + +      auto ComputeODRHash = [&Hash](const Stmt* S) { +        assert(S); +        Hash.clear(); +        Hash.AddStmt(S); +        return Hash.CalculateHash(); +      }; + +      switch (FirstDiffType) { +      case Other: +      case EndOfClass: +      case PublicSpecifer: +      case PrivateSpecifer: +      case ProtectedSpecifer: +        llvm_unreachable("Invalid diff type"); + +      case StaticAssert: { +        StaticAssertDecl *FirstSA = cast<StaticAssertDecl>(FirstDecl); +        StaticAssertDecl *SecondSA = cast<StaticAssertDecl>(SecondDecl); + +        Expr *FirstExpr = FirstSA->getAssertExpr(); +        Expr *SecondExpr = SecondSA->getAssertExpr(); +        unsigned FirstODRHash = ComputeODRHash(FirstExpr); +        unsigned SecondODRHash = ComputeODRHash(SecondExpr); +        if (FirstODRHash != SecondODRHash) { +          ODRDiagError(FirstExpr->getLocStart(), FirstExpr->getSourceRange(), +                       StaticAssertCondition); +          ODRDiagNote(SecondExpr->getLocStart(), +                      SecondExpr->getSourceRange(), StaticAssertCondition); +          Diagnosed = true; +          break; +        } + +        StringLiteral *FirstStr = FirstSA->getMessage(); +        StringLiteral *SecondStr = SecondSA->getMessage(); +        assert((FirstStr || SecondStr) && "Both messages cannot be empty"); +        if ((FirstStr && !SecondStr) || (!FirstStr && SecondStr)) { +          SourceLocation FirstLoc, SecondLoc; +          SourceRange FirstRange, SecondRange; +          if (FirstStr) { +            FirstLoc = FirstStr->getLocStart(); +            FirstRange = FirstStr->getSourceRange(); +          } else { +            FirstLoc = FirstSA->getLocStart(); +            FirstRange = FirstSA->getSourceRange(); +          } +          if (SecondStr) { +            SecondLoc = SecondStr->getLocStart(); +            SecondRange = SecondStr->getSourceRange(); +          } else { +            SecondLoc = SecondSA->getLocStart(); +            SecondRange = SecondSA->getSourceRange(); +          } +          ODRDiagError(FirstLoc, FirstRange, StaticAssertOnlyMessage) +              << (FirstStr == nullptr); +          ODRDiagNote(SecondLoc, SecondRange, StaticAssertOnlyMessage) +              << (SecondStr == nullptr); +          Diagnosed = true; +          break; +        } + +        if (FirstStr && SecondStr && +            FirstStr->getString() != SecondStr->getString()) { +          ODRDiagError(FirstStr->getLocStart(), FirstStr->getSourceRange(), +                       StaticAssertMessage); +          ODRDiagNote(SecondStr->getLocStart(), SecondStr->getSourceRange(), +                      StaticAssertMessage); +          Diagnosed = true; +          break; +        } +        break; +      } +      } +        if (Diagnosed == true)          continue; diff --git a/clang/test/Modules/odr_hash.cpp b/clang/test/Modules/odr_hash.cpp index 51eb658fb39..652e74f9569 100644 --- a/clang/test/Modules/odr_hash.cpp +++ b/clang/test/Modules/odr_hash.cpp @@ -21,7 +21,7 @@  // RUN: echo "}"                        >> %t/Inputs/module.map  // Run test -// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/cache -x c++ -I%t/Inputs -verify %s -std=c++11 +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/cache -x c++ -I%t/Inputs -verify %s -std=c++1z  #if !defined(FIRST) && !defined(SECOND)  #include "first.h" @@ -57,6 +57,64 @@ S2 s2;  #endif  } // namespace AccessSpecifiers +namespace StaticAssert { +#if defined(FIRST) +struct S1 { +  static_assert(1 == 1, "First"); +}; +#elif defined(SECOND) +struct S1 { +  static_assert(1 == 1, "Second"); +}; +#else +S1 s1; +// expected-error@second.h:* {{'StaticAssert::S1' has different definitions in different modules; first difference is definition in module 'SecondModule' found static assert with message}} +// expected-note@first.h:* {{but in 'FirstModule' found static assert with different message}} +#endif + +#if defined(FIRST) +struct S2 { +  static_assert(2 == 2, "Message"); +}; +#elif defined(SECOND) +struct S2 { +  static_assert(2 == 2); +}; +#else +S2 s2; +// expected-error@second.h:* {{'StaticAssert::S2' has different definitions in different modules; first difference is definition in module 'SecondModule' found static assert with no message}} +// expected-note@first.h:* {{but in 'FirstModule' found static assert with message}} +#endif + +#if defined(FIRST) +struct S3 { +  static_assert(3 == 3, "Message"); +}; +#elif defined(SECOND) +struct S3 { +  static_assert(3 != 4, "Message"); +}; +#else +S3 s3; +// expected-error@second.h:* {{'StaticAssert::S3' has different definitions in different modules; first difference is definition in module 'SecondModule' found static assert with condition}} +// expected-note@first.h:* {{but in 'FirstModule' found static assert with different condition}} +#endif + +#if defined(FIRST) +struct S4 { +  static_assert(4 == 4, "Message"); +}; +#elif defined(SECOND) +struct S4 { +  public: +}; +#else +S4 s4; +// expected-error@second.h:* {{'StaticAssert::S4' has different definitions in different modules; first difference is definition in module 'SecondModule' found public access specifier}} +// expected-note@first.h:* {{but in 'FirstModule' found static assert}} +#endif +} +  // Naive parsing of AST can lead to cycles in processing.  Ensure  // self-references don't trigger an endless cycles of AST node processing.  namespace SelfReference { @@ -90,12 +148,18 @@ struct S {    public:    private:    protected: + +  static_assert(1 == 1, "Message"); +  static_assert(2 == 2);  };  #elif defined(SECOND)  struct S {    public:    private:    protected: + +  static_assert(1 == 1, "Message"); +  static_assert(2 == 2);  };  #else  S s; @@ -107,6 +171,9 @@ struct T {    private:    protected: +  static_assert(1 == 1, "Message"); +  static_assert(2 == 2); +    private:  };  #elif defined(SECOND) @@ -115,6 +182,9 @@ struct T {    private:    protected: +  static_assert(1 == 1, "Message"); +  static_assert(2 == 2); +    public:  };  #else  | 

