Author: rtrieu Date: Wed Feb 22 16:22:42 2017 New Revision: 295890 URL: http://llvm.org/viewvc/llvm-project?rev=295890&view=rev Log: [ODRHash] static_cast and Stmt hashing.
Add support for static_cast in classes. Add pointer-independent profiling for Stmt's, sharing most of the logic with Stmt::Profile. This is the first of the deep sub-Decl diffing for error messages. Differential Revision: https://reviews.llvm.org/D21675 Modified: cfe/trunk/include/clang/AST/Stmt.h cfe/trunk/include/clang/Basic/DiagnosticSerializationKinds.td cfe/trunk/lib/AST/ODRHash.cpp cfe/trunk/lib/AST/StmtProfile.cpp cfe/trunk/lib/Serialization/ASTReader.cpp cfe/trunk/test/Modules/odr_hash.cpp Modified: cfe/trunk/include/clang/AST/Stmt.h URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/AST/Stmt.h?rev=295890&r1=295889&r2=295890&view=diff ============================================================================== --- cfe/trunk/include/clang/AST/Stmt.h (original) +++ cfe/trunk/include/clang/AST/Stmt.h Wed Feb 22 16:22:42 2017 @@ -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 Modified: cfe/trunk/include/clang/Basic/DiagnosticSerializationKinds.td URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Basic/DiagnosticSerializationKinds.td?rev=295890&r1=295889&r2=295890&view=diff ============================================================================== --- cfe/trunk/include/clang/Basic/DiagnosticSerializationKinds.td (original) +++ cfe/trunk/include/clang/Basic/DiagnosticSerializationKinds.td Wed Feb 22 16:22:42 2017 @@ -121,10 +121,24 @@ def err_module_odr_violation_mismatch_de "%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__">, Modified: cfe/trunk/lib/AST/ODRHash.cpp URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/AST/ODRHash.cpp?rev=295890&r1=295889&r2=295890&view=diff ============================================================================== --- cfe/trunk/lib/AST/ODRHash.cpp (original) +++ cfe/trunk/lib/AST/ODRHash.cpp Wed Feb 22 16:22:42 2017 @@ -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 De 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) { Modified: cfe/trunk/lib/AST/StmtProfile.cpp URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/AST/StmtProfile.cpp?rev=295890&r1=295889&r2=295890&view=diff ============================================================================== --- cfe/trunk/lib/AST/StmtProfile.cpp (original) +++ cfe/trunk/lib/AST/StmtProfile.cpp Wed Feb 22 16:22:42 2017 @@ -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(con break; case OffsetOfNode::Identifier: - ID.AddPointer(ON.getFieldName()); + VisitIdentifierInfo(ON.getFieldName()); break; case OffsetOfNode::Base: @@ -861,7 +987,7 @@ void StmtProfiler::VisitOffsetOfExpr(con break; } } - + VisitExpr(S); } @@ -1451,7 +1577,7 @@ StmtProfiler::VisitCXXPseudoDestructorEx 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::VisitObjCAvailability 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 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); } Modified: cfe/trunk/lib/Serialization/ASTReader.cpp URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Serialization/ASTReader.cpp?rev=295890&r1=295889&r2=295890&view=diff ============================================================================== --- cfe/trunk/lib/Serialization/ASTReader.cpp (original) +++ cfe/trunk/lib/Serialization/ASTReader.cpp Wed Feb 22 16:22:42 2017 @@ -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; Modified: cfe/trunk/test/Modules/odr_hash.cpp URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Modules/odr_hash.cpp?rev=295890&r1=295889&r2=295890&view=diff ============================================================================== --- cfe/trunk/test/Modules/odr_hash.cpp (original) +++ cfe/trunk/test/Modules/odr_hash.cpp Wed Feb 22 16:22:42 2017 @@ -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 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits