Index: docs/ControlFlowIntegrity.rst =================================================================== --- docs/ControlFlowIntegrity.rst +++ docs/ControlFlowIntegrity.rst @@ -25,13 +25,25 @@ so it is required to specify ``-flto``, and the linker used must support LTO, for example via the `gold plugin`_. -To allow the checks to be implemented efficiently, the program must be -structured such that certain object files are compiled with CFI +To allow the checks to be implemented efficiently, the program must +be structured such that certain object files are compiled with CFI enabled, and are statically linked into the program. This may preclude -the use of shared libraries in some cases. Experimental support for -:ref:`cross-DSO control flow integrity ` exists that -does not have these requirements. This cross-DSO support has unstable -ABI at this time. +the use of shared libraries in some cases. + +The compiler will only produce CFI checks for a class if it can infer hidden +LTO visibility for that class. LTO visibility is a property of a class that +is inferred from flags and attributes. For more details, see the documentation +for :doc:`LTO visibility `. + +The ``-fsanitize=cfi-{vcall,nvcall,derived-cast,unrelated-cast}`` flags +require that a ``-fvisibility=`` flag also be specified. This is because the +default visibility setting is ``-fvisibility=default``, which would disable +CFI checks for classes without visibility attributes. Most users will want +to specify ``-fvisibility=hidden``, which enables CFI checks for such classes. + +Experimental support for :ref:`cross-DSO control flow integrity +` exists that does not require classes to have hidden LTO +visibility. This cross-DSO support has unstable ABI at this time. .. _gold plugin: http://llvm.org/docs/GoldPlugin.html @@ -233,11 +245,6 @@ source files, functions and types using the ``src``, ``fun`` and ``type`` entity types. -In addition, if a type has a ``uuid`` attribute and the blacklist contains -the type entry ``attr:uuid``, CFI checks are suppressed for that type. This -allows all COM types to be easily blacklisted, which is useful as COM types -are typically defined outside of the linked program. - .. code-block:: bash # Suppress checking for code in a file. @@ -247,8 +254,6 @@ fun:*MyFooBar* # Ignore all types in the standard library. type:std::* - # Ignore all types with a uuid attribute. - type:attr:uuid .. _cfi-cross-dso: @@ -260,6 +265,11 @@ apply across DSO boundaries. As in the regular CFI, each DSO must be built with ``-flto``. +Normally, the compiler will disable CFI checks for classes that do not +have hidden LTO visibility. With this flag enabled, the compiler will emit +cross-DSO CFI checks for all classes, except for those which appear in the +CFI blacklist or which use a ``no_sanitize`` attribute. + Design ====== Index: docs/LTOVisibility.rst =================================================================== --- /dev/null +++ docs/LTOVisibility.rst @@ -0,0 +1,43 @@ +============== +LTO Visibility +============== + +LTO visibility is a concept used by the compiler to determine which classes +the virtual function call optimization and control flow integrity features +apply to. These features use whole-program information, so they require +visibility of entire class hierarchies to work correctly. + +It is effectively an ODR violation to declare a class with hidden LTO +visibility in multiple linkage units, or to declare such a class in an +translation unit not built with LTO. A class with default LTO visibility +has no such restrictions, but the tradeoff is that the virtual function +call optimization and control flow integrity features can only be applied to +classes with internal linkage (e.g. classes declared in unnamed namespaces) +or classes with LTO visibility. + +LTO visibility is based on symbol visibility or, on the Windows platform, +the dllimport and dllexport attributes. When targeting non-Windows platforms, +classes with a visibility other than hidden visibility receive default LTO +visibility. When targeting Windows, classes with dllimport or dllexport +attributes receive default LTO visibility. All other classes receive hidden +LTO visibility. + +This mechanism will produce the correct result in most cases, but there are +two cases where it may wrongly infer hidden LTO visibility. + +1. If a linkage unit is produced from a combination of LTO object files and + non-LTO object files, any classes defined in translation units from which + the non-LTO object files were built will require default LTO visibility. + +2. Some ABIs provide the ability to define an abstract base class without + visibility attributes in multiple linkage units and have virtual calls + to derived classes in other linkage units work correctly. One example of + this is COM on Windows platforms. + +Classes that fall into either of these categories can be marked up with +the ``[[clang::lto_visibility_default]]`` attribute. To specifically +handle the COM case, the ``__declspec(uuid())`` attribute implies +``[[clang::lto_visibility_default]]``. On Windows platforms, the ``/MT`` and +``/MTd`` flags link the program against a prebuilt static standard library; +these flags imply default LTO visibility for every class declared in the +``std`` and `stdext`` namespaces. Index: docs/UsersManual.rst =================================================================== --- docs/UsersManual.rst +++ docs/UsersManual.rst @@ -1056,17 +1056,16 @@ .. option:: -fwhole-program-vtables Enable whole-program vtable optimizations, such as single-implementation - devirtualization and virtual constant propagation. Requires ``-flto``. + devirtualization and virtual constant propagation, for classes with internal + linkage or :doc:`linkage-unit scope `. Requires ``-flto``. - By default, the compiler will assume that all type hierarchies are - closed except those in the ``std`` namespace, the ``stdext`` namespace - and classes with the ``__declspec(uuid())`` attribute. +.. _opt_fdefault-class-scope: -.. option:: -fwhole-program-vtables-blacklist=path +.. option:: -fdefault-class-scope=VAL - Allows the user to specify the path to a list of additional classes to - blacklist from whole-program vtable optimizations. This list is in the - :ref:`CFI blacklist ` format. + This flag controls how the compiler infers class scope for class + declarations when using the flags ``-fwhole-program-vtables`` or + ``-fsanitize=cfi*``. See :doc:`ClassScope` for more details. .. option:: -fno-assume-sane-operator-new Index: docs/index.rst =================================================================== --- docs/index.rst +++ docs/index.rst @@ -31,6 +31,7 @@ SanitizerStats SanitizerSpecialCaseList ControlFlowIntegrity + ClassScope SafeStack Modules MSVCCompatibility Index: include/clang/Basic/Attr.td =================================================================== --- include/clang/Basic/Attr.td +++ include/clang/Basic/Attr.td @@ -1604,6 +1604,18 @@ let Documentation = [Undocumented]; } +def LinkageUnitScope : InheritableAttr { + let Spellings = [CXX11<"clang", "linkage_unit_scope">]; + let Subjects = SubjectList<[Record, Namespace]>; + let Documentation = [ClassScopeDocs]; +} + +def GlobalScope : InheritableAttr { + let Spellings = [CXX11<"clang", "global_scope">]; + let Subjects = SubjectList<[Record, Namespace]>; + let Documentation = [ClassScopeDocs]; +} + def AnyX86Interrupt : InheritableAttr, TargetSpecificAttr { // NOTE: If you add any additional spellings, ARMInterrupt's, // MSP430Interrupt's and MipsInterrupt's spellings must match. Index: include/clang/Basic/AttrDocs.td =================================================================== --- include/clang/Basic/AttrDocs.td +++ include/clang/Basic/AttrDocs.td @@ -2341,3 +2341,10 @@ string argument which is the message to display when emitting the warning. }]; } + +def ClassScopeDocs : Documentation { + let Category = DocCatType; + let Content = [{ +See :ref:`Overriding Class Scope Inferences with Attributes `. + }]; +} Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -2487,7 +2487,8 @@ "interface or protocol declarations|kernel functions|non-K&R-style functions|" "variables, enums, fields and typedefs|functions, methods, enums, and classes|" "structs, classes, variables, functions, and inline namespaces|" - "variables, functions, methods, types, enumerations, enumerators, labels, and non-static data members}1">, + "variables, functions, methods, types, enumerations, enumerators, labels, and non-static data members|" + "struct, union, class or namespace}1">, InGroup; def err_attribute_wrong_decl_type : Error; def warn_type_attribute_wrong_type : Warning< Index: include/clang/Driver/Options.td =================================================================== --- include/clang/Driver/Options.td +++ include/clang/Driver/Options.td @@ -1134,9 +1134,9 @@ Flags<[CC1Option]>, HelpText<"Enables whole-program vtable optimization. Requires -flto">; def fno_whole_program_vtables : Flag<["-"], "fno-whole-program-vtables">, Group; -def fwhole_program_vtables_blacklist_EQ : Joined<["-"], "fwhole-program-vtables-blacklist=">, - Group, Flags<[CC1Option]>, - HelpText<"Path to a blacklist file for whole-program vtable optimization">; +def fdefault_class_scope_EQ : Joined<["-"], "fdefault-class-scope=">, Group, + Flags<[CC1Option]>, + HelpText<"Default class scope for vtable opt and CFI">; def fwrapv : Flag<["-"], "fwrapv">, Group, Flags<[CC1Option]>, HelpText<"Treat signed integer overflow as two's complement">; def fwritable_strings : Flag<["-"], "fwritable-strings">, Group, Flags<[CC1Option]>, Index: include/clang/Frontend/CodeGenOptions.h =================================================================== --- include/clang/Frontend/CodeGenOptions.h +++ include/clang/Frontend/CodeGenOptions.h @@ -86,6 +86,13 @@ ProfileIRInstr, // IR level PGO instrumentation in LLVM. }; + enum ClassScopeKind { + ClassScopeGlobal, // Infer global scope for all classes. + ClassScopeAttrs, // Infer scope from visibility/dllimport/dllexport + // attributes. + ClassScopeLinkageUnit, // Infer linkage unit scope for all classes. + }; + /// The code model to use (-mcmodel). std::string CodeModel; @@ -202,9 +209,6 @@ /// \brief A list of all -fno-builtin-* function names (e.g., memset). std::vector NoBuiltinFuncs; - /// List of blacklist files for the whole-program vtable optimization feature. - std::vector WholeProgramVTablesBlacklistFiles; - public: // Define accessors/mutators for code generation options of enumeration type. #define CODEGENOPT(Name, Bits, Default) Index: include/clang/Frontend/CodeGenOptions.def =================================================================== --- include/clang/Frontend/CodeGenOptions.def +++ include/clang/Frontend/CodeGenOptions.def @@ -184,6 +184,9 @@ CODEGENOPT(WholeProgramVTables, 1, 0) ///< Whether to apply whole-program /// vtable optimization. +ENUM_CODEGENOPT(DefaultClassScope, ClassScopeKind, 2, + ClassScopeAttrs) ///< How to derive a class's scope for + /// vtable opt and CFI. /// The user specified number of registers to be used for integral arguments, /// or 0 if unspecified. Index: include/clang/Sema/AttributeList.h =================================================================== --- include/clang/Sema/AttributeList.h +++ include/clang/Sema/AttributeList.h @@ -904,7 +904,8 @@ ExpectedVariableEnumFieldOrTypedef, ExpectedFunctionMethodEnumOrClass, ExpectedStructClassVariableFunctionOrInlineNamespace, - ExpectedForMaybeUnused + ExpectedForMaybeUnused, + ExpectedRecordOrNamespace, }; } // end namespace clang Index: include/clang/Sema/Sema.h =================================================================== --- include/clang/Sema/Sema.h +++ include/clang/Sema/Sema.h @@ -2145,6 +2145,9 @@ unsigned AttrSpellingListIndex); CommonAttr *mergeCommonAttr(Decl *D, SourceRange Range, IdentifierInfo *Ident, unsigned AttrSpellingListIndex); + InheritableAttr *mergeClassScopeAttr(Decl *D, SourceRange Range, + int SpellingIndex, + bool IsLinkageUnitScope); void mergeDeclAttributes(NamedDecl *New, Decl *Old, AvailabilityMergeKind AMK = AMK_Redeclaration); Index: lib/CodeGen/CGClass.cpp =================================================================== --- lib/CodeGen/CGClass.cpp +++ lib/CodeGen/CGClass.cpp @@ -2489,7 +2489,7 @@ llvm::Value *VTable, SourceLocation Loc) { if (CGM.getCodeGenOpts().WholeProgramVTables && - !CGM.IsBitSetBlacklistedRecord(RD)) { + CGM.HasLinkageUnitScope(RD)) { llvm::Metadata *MD = CGM.CreateMetadataIdentifierForType(QualType(RD->getTypeForDecl(), 0)); llvm::Value *BitSetName = @@ -2565,7 +2565,12 @@ llvm::Value *VTable, CFITypeCheckKind TCK, SourceLocation Loc) { - if (CGM.IsBitSetBlacklistedRecord(RD)) + if (!CGM.getCodeGenOpts().SanitizeCfiCrossDso && + !CGM.HasLinkageUnitScope(RD)) + return; + + std::string TypeName = RD->getQualifiedNameAsString(); + if (getContext().getSanitizerBlacklist().isBlacklistedType(TypeName)) return; SanitizerScope SanScope(this); Index: lib/CodeGen/CGVTables.cpp =================================================================== --- lib/CodeGen/CGVTables.cpp +++ lib/CodeGen/CGVTables.cpp @@ -910,21 +910,36 @@ getLangOpts().Sanitize.has(SanitizerKind::CFIUnrelatedCast); } -bool CodeGenModule::IsBitSetBlacklistedRecord(const CXXRecordDecl *RD) { - std::string TypeName = RD->getQualifiedNameAsString(); - auto isInBlacklist = [&](const SanitizerBlacklist &BL) { - if (RD->hasAttr() && BL.isBlacklistedType("attr:uuid")) - return true; +bool CodeGenModule::HasLinkageUnitScope(const CXXRecordDecl *RD) { + LinkageInfo LV = RD->getLinkageAndVisibility(); + if (!isExternallyVisible(LV.getLinkage())) + return true; - return BL.isBlacklistedType(TypeName); - }; + // These attributes override our checks below. + const DeclContext *DC = RD; + while (DC) { + const Decl *D = cast(DC); + if (D->hasAttr()) { + return true; + } else if (D->hasAttr() || D->hasAttr()) { + return false; + } + DC = DC->getParent(); + } - return isInBlacklist(WholeProgramVTablesBlacklist) || - ((LangOpts.Sanitize.has(SanitizerKind::CFIVCall) || - LangOpts.Sanitize.has(SanitizerKind::CFINVCall) || - LangOpts.Sanitize.has(SanitizerKind::CFIDerivedCast) || - LangOpts.Sanitize.has(SanitizerKind::CFIUnrelatedCast)) && - isInBlacklist(getContext().getSanitizerBlacklist())); + switch (getCodeGenOpts().getDefaultClassScope()) { + case CodeGenOptions::ClassScopeGlobal: + return false; + case CodeGenOptions::ClassScopeLinkageUnit: + return true; + case CodeGenOptions::ClassScopeAttrs: + // Use object format-specific attributes to see if the record is visible + // outside the linkage unit. + if (getTriple().isOSBinFormatCOFF()) + return !RD->hasAttr() && !RD->hasAttr(); + else + return LV.getVisibility() == HiddenVisibility; + } } void CodeGenModule::EmitVTableBitSetEntries(llvm::GlobalVariable *VTable, @@ -938,12 +953,8 @@ typedef std::pair BSEntry; std::vector BitsetEntries; // Create a bit set entry for each address point. - for (auto &&AP : VTLayout.getAddressPoints()) { - if (IsBitSetBlacklistedRecord(AP.first.getBase())) - continue; - + for (auto &&AP : VTLayout.getAddressPoints()) BitsetEntries.push_back(std::make_pair(AP.first.getBase(), AP.second)); - } // Sort the bit set entries for determinism. std::sort(BitsetEntries.begin(), BitsetEntries.end(), Index: lib/CodeGen/CodeGenModule.h =================================================================== --- lib/CodeGen/CodeGenModule.h +++ lib/CodeGen/CodeGenModule.h @@ -490,8 +490,6 @@ /// MDNodes. llvm::DenseMap MetadataIdMap; - SanitizerBlacklist WholeProgramVTablesBlacklist; - public: CodeGenModule(ASTContext &C, const HeaderSearchOptions &headersearchopts, const PreprocessorOptions &ppopts, @@ -1118,9 +1116,9 @@ /// Returns whether we need bit sets attached to vtables. bool NeedVTableBitSets(); - /// Returns whether the given record is blacklisted from whole-program - /// transformations (i.e. CFI or whole-program vtable optimization). - bool IsBitSetBlacklistedRecord(const CXXRecordDecl *RD); + /// Returns whether the given record has linkage-unit scope and therefore may + /// participate in (single-module) CFI and whole-program vtable optimization. + bool HasLinkageUnitScope(const CXXRecordDecl *RD); /// Emit bit set entries for the given vtable using the given layout if /// vptr CFI is enabled. Index: lib/CodeGen/CodeGenModule.cpp =================================================================== --- lib/CodeGen/CodeGenModule.cpp +++ lib/CodeGen/CodeGenModule.cpp @@ -97,9 +97,7 @@ NSConcreteStackBlock(nullptr), BlockObjectAssign(nullptr), BlockObjectDispose(nullptr), BlockDescriptorType(nullptr), GenericBlockLiteralType(nullptr), LifetimeStartFn(nullptr), - LifetimeEndFn(nullptr), SanitizerMD(new SanitizerMetadata(*this)), - WholeProgramVTablesBlacklist(CGO.WholeProgramVTablesBlacklistFiles, - C.getSourceManager()) { + LifetimeEndFn(nullptr), SanitizerMD(new SanitizerMetadata(*this)) { // Initialize the type cache. llvm::LLVMContext &LLVMContext = M.getContext(); Index: lib/CodeGen/MicrosoftCXXABI.cpp =================================================================== --- lib/CodeGen/MicrosoftCXXABI.cpp +++ lib/CodeGen/MicrosoftCXXABI.cpp @@ -1519,15 +1519,13 @@ : CharUnits::Zero(); if (Info->PathToBaseWithVPtr.empty()) { - if (!CGM.IsBitSetBlacklistedRecord(RD)) - CGM.CreateVTableBitSetEntry(BitsetsMD, VTable, AddressPoint, RD); + CGM.CreateVTableBitSetEntry(BitsetsMD, VTable, AddressPoint, RD); return; } // Add a bitset entry for the least derived base belonging to this vftable. - if (!CGM.IsBitSetBlacklistedRecord(Info->PathToBaseWithVPtr.back())) - CGM.CreateVTableBitSetEntry(BitsetsMD, VTable, AddressPoint, - Info->PathToBaseWithVPtr.back()); + CGM.CreateVTableBitSetEntry(BitsetsMD, VTable, AddressPoint, + Info->PathToBaseWithVPtr.back()); // Add a bitset entry for each derived class that is laid out at the same // offset as the least derived base. @@ -1545,12 +1543,11 @@ Offset = VBI->second.VBaseOffset; if (!Offset.isZero()) return; - if (!CGM.IsBitSetBlacklistedRecord(DerivedRD)) - CGM.CreateVTableBitSetEntry(BitsetsMD, VTable, AddressPoint, DerivedRD); + CGM.CreateVTableBitSetEntry(BitsetsMD, VTable, AddressPoint, DerivedRD); } // Finally do the same for the most derived class. - if (Info->FullOffsetInMDC.isZero() && !CGM.IsBitSetBlacklistedRecord(RD)) + if (Info->FullOffsetInMDC.isZero()) CGM.CreateVTableBitSetEntry(BitsetsMD, VTable, AddressPoint, RD); } Index: lib/Driver/SanitizerArgs.cpp =================================================================== --- lib/Driver/SanitizerArgs.cpp +++ lib/Driver/SanitizerArgs.cpp @@ -39,6 +39,7 @@ TrappingSupported = (Undefined & ~Vptr) | UnsignedIntegerOverflow | LocalBounds | CFI, TrappingDefault = CFI, + CFIClasses = CFIVCall | CFINVCall | CFIDerivedCast | CFIUnrelatedCast, }; enum CoverageFeature { @@ -674,6 +675,9 @@ TC.getCompilerRT(Args, "stats"))); addIncludeLinkerOption(TC, Args, CmdArgs, "__sanitizer_stats_register"); } + + if (Sanitizers.hasOneOf(CFIClasses)) + CmdArgs.push_back(Args.MakeArgString("-fdefault-class-scope=linkage-unit")); } SanitizerMask parseArgValues(const Driver &D, const llvm::opt::Arg *A, Index: lib/Driver/Tools.cpp =================================================================== --- lib/Driver/Tools.cpp +++ lib/Driver/Tools.cpp @@ -4391,32 +4391,6 @@ CmdArgs.push_back("-ffunction-sections"); } - if (Args.hasFlag(options::OPT_fwhole_program_vtables, - options::OPT_fno_whole_program_vtables, false)) { - if (!D.isUsingLTO()) - D.Diag(diag::err_drv_argument_only_allowed_with) - << "-fwhole-program-vtables" - << "-flto"; - CmdArgs.push_back("-fwhole-program-vtables"); - - clang::SmallString<64> Path(D.ResourceDir); - llvm::sys::path::append(Path, "vtables_blacklist.txt"); - if (llvm::sys::fs::exists(Path)) { - SmallString<64> BlacklistOpt("-fwhole-program-vtables-blacklist="); - BlacklistOpt += Path.str(); - CmdArgs.push_back(Args.MakeArgString(BlacklistOpt)); - } - - for (const Arg *A : - Args.filtered(options::OPT_fwhole_program_vtables_blacklist_EQ)) { - A->claim(); - if (!llvm::sys::fs::exists(A->getValue())) - D.Diag(clang::diag::err_drv_no_such_file) << A->getValue(); - } - - Args.AddAllArgs(CmdArgs, options::OPT_fwhole_program_vtables_blacklist_EQ); - } - if (Args.hasFlag(options::OPT_fdata_sections, options::OPT_fno_data_sections, UseSeparateSections)) { CmdArgs.push_back("-fdata-sections"); @@ -5752,6 +5726,18 @@ CmdArgs.push_back(I->getFilename()); } + bool WholeProgramVTables = + Args.hasFlag(options::OPT_fwhole_program_vtables, + options::OPT_fno_whole_program_vtables, false); + if (WholeProgramVTables) { + if (!D.isUsingLTO()) + D.Diag(diag::err_drv_argument_only_allowed_with) + << "-fwhole-program-vtables" + << "-flto"; + CmdArgs.push_back("-fwhole-program-vtables"); + } + Args.AddLastArg(CmdArgs, options::OPT_fdefault_class_scope_EQ); + // Finally add the compile command to the compilation. if (Args.hasArg(options::OPT__SLASH_fallback) && Output.getType() == types::TY_Object && Index: lib/Frontend/CompilerInvocation.cpp =================================================================== --- lib/Frontend/CompilerInvocation.cpp +++ lib/Frontend/CompilerInvocation.cpp @@ -482,8 +482,21 @@ Opts.DebugColumnInfo = Args.hasArg(OPT_dwarf_column_info); Opts.EmitCodeView = Args.hasArg(OPT_gcodeview); Opts.WholeProgramVTables = Args.hasArg(OPT_fwhole_program_vtables); - Opts.WholeProgramVTablesBlacklistFiles = - Args.getAllArgValues(OPT_fwhole_program_vtables_blacklist_EQ); + if (Arg *A = Args.getLastArg(OPT_fdefault_class_scope_EQ)) { + unsigned Val = + llvm::StringSwitch(A->getValue()) + .Case("global", unsigned(CodeGenOptions::ClassScopeGlobal)) + .Case("attrs", unsigned(CodeGenOptions::ClassScopeAttrs)) + .Case("linkage-unit", + unsigned(CodeGenOptions::ClassScopeLinkageUnit)) + .Default(~0U); + if (Val == ~0U) + Diags.Report(diag::err_drv_invalid_value) << A->getAsString(Args) + << A->getValue(); + else + Opts.setDefaultClassScope( + static_cast(Val)); + } Opts.SplitDwarfFile = Args.getLastArgValue(OPT_split_dwarf_file); Opts.DebugTypeExtRefs = Args.hasArg(OPT_dwarf_ext_refs); Opts.DebugExplicitImport = Triple.isPS4CPU(); Index: lib/Sema/SemaDecl.cpp =================================================================== --- lib/Sema/SemaDecl.cpp +++ lib/Sema/SemaDecl.cpp @@ -2192,7 +2192,12 @@ Sema::AvailabilityMergeKind AMK) { InheritableAttr *NewAttr = nullptr; unsigned AttrSpellingListIndex = Attr->getSpellingListIndex(); - if (const auto *AA = dyn_cast(Attr)) + if (isa(Attr) || isa(Attr)) + NewAttr = S.mergeClassScopeAttr(D, Attr->getRange(), AttrSpellingListIndex, + isa(Attr)); + else if (isa(D)) + return false; + else if (const auto *AA = dyn_cast(Attr)) NewAttr = S.mergeAvailabilityAttr(D, AA->getRange(), AA->getPlatform(), AA->getIntroduced(), AA->getDeprecated(), AA->getObsoleted(), AA->getUnavailable(), Index: lib/Sema/SemaDeclAttr.cpp =================================================================== --- lib/Sema/SemaDeclAttr.cpp +++ lib/Sema/SemaDeclAttr.cpp @@ -246,9 +246,9 @@ /// \brief Diagnose mutually exclusive attributes when present on a given /// declaration. Returns true if diagnosed. -template +template static bool checkAttrMutualExclusion(Sema &S, Decl *D, SourceRange Range, - IdentifierInfo *Ident) { + NameTy Ident) { if (AttrTy *A = D->getAttr()) { S.Diag(Range.getBegin(), diag::err_attributes_are_not_compatible) << Ident << A; @@ -5223,6 +5223,30 @@ << Attr.getName() << "2.0"; } +InheritableAttr *Sema::mergeClassScopeAttr(Decl *D, SourceRange Range, + int SpellingIndex, + bool IsLinkageUnitScope) { + if (IsLinkageUnitScope) { + if (checkAttrMutualExclusion( + *this, D, Range, "'linkage_unit_scope'")) + return nullptr; + return ::new (Context) LinkageUnitScopeAttr(Range, Context, SpellingIndex); + } else { + if (checkAttrMutualExclusion( + *this, D, Range, "'global_scope'")) + return nullptr; + return ::new (Context) + GlobalScopeAttr(Range, Context, SpellingIndex); + } +} + +static void handleClassScopeAttr(Sema &S, Decl *D, const AttributeList &Attr) { + if (InheritableAttr *A = S.mergeClassScopeAttr( + D, Attr.getRange(), Attr.getAttributeSpellingListIndex(), + Attr.getKind() == AttributeList::AT_LinkageUnitScope)) + D->addAttr(A); +} + /// Handles semantic checking for features that are common to all attributes, /// such as checking whether a parameter was properly specified, or the correct /// number of arguments were passed, etc. @@ -5724,6 +5748,10 @@ case AttributeList::AT_InternalLinkage: handleInternalLinkageAttr(S, D, Attr); break; + case AttributeList::AT_LinkageUnitScope: + case AttributeList::AT_GlobalScope: + handleClassScopeAttr(S, D, Attr); + break; // Microsoft attributes: case AttributeList::AT_MSNoVTable: Index: lib/Sema/SemaDeclCXX.cpp =================================================================== --- lib/Sema/SemaDeclCXX.cpp +++ lib/Sema/SemaDeclCXX.cpp @@ -7253,7 +7253,9 @@ ProcessDeclAttributeList(DeclRegionScope, Namespc, AttrList); - // FIXME: Should we be merging attributes? + if (PrevNS) + mergeDeclAttributes(Namespc, PrevNS); + if (const VisibilityAttr *Attr = Namespc->getAttr()) PushNamespaceVisibilityAttr(Attr, Loc); Index: runtime/CMakeLists.txt =================================================================== --- runtime/CMakeLists.txt +++ runtime/CMakeLists.txt @@ -148,15 +148,3 @@ VERBATIM) endif() endif() - -set(src "${CMAKE_CURRENT_SOURCE_DIR}/vtables_blacklist.txt") -set(dst "${LLVM_LIBRARY_OUTPUT_INTDIR}/clang/${CLANG_VERSION}/vtables_blacklist.txt") -add_custom_command(OUTPUT ${dst} - DEPENDS ${src} - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${src} ${dst} - COMMENT "Copying vtables blacklist") -add_custom_target(vtables_blacklist DEPENDS ${dst}) -if(TARGET clang) - add_dependencies(clang vtables_blacklist) -endif() -install(FILES ${src} DESTINATION lib${LLVM_LIBDIR_SUFFIX}/clang/${CLANG_VERSION}) Index: runtime/vtables_blacklist.txt =================================================================== --- runtime/vtables_blacklist.txt +++ /dev/null @@ -1,8 +0,0 @@ -# Standard library types. -type:std::* - -# The stdext namespace contains Microsoft standard library extensions. -type:stdext::* - -# Types with a uuid attribute, i.e. COM types. -type:attr:uuid Index: test/CodeGenCXX/bitset-blacklist.cpp =================================================================== --- test/CodeGenCXX/bitset-blacklist.cpp +++ /dev/null @@ -1,32 +0,0 @@ -// RUN: echo "type:attr:uuid" > %t.txt -// RUN: %clang_cc1 -fms-extensions -fsanitize=cfi-vcall -fsanitize-blacklist=%t.txt -emit-llvm -o - %s | FileCheck --check-prefix=CHECK --check-prefix=NOUUID %s -// RUN: %clang_cc1 -fms-extensions -fwhole-program-vtables -fwhole-program-vtables-blacklist=%t.txt -emit-llvm -o - %s | FileCheck --check-prefix=CHECK --check-prefix=NOUUID %s -// RUN: echo "type:std::*" > %t.txt -// RUN: %clang_cc1 -fms-extensions -fsanitize=cfi-vcall -fsanitize-blacklist=%t.txt -emit-llvm -o - %s | FileCheck --check-prefix=CHECK --check-prefix=NOSTD %s -// RUN: %clang_cc1 -fms-extensions -fwhole-program-vtables -fwhole-program-vtables-blacklist=%t.txt -emit-llvm -o - %s | FileCheck --check-prefix=CHECK --check-prefix=NOSTD %s - -struct __declspec(uuid("00000000-0000-0000-0000-000000000000")) S1 { - virtual void f(); -}; - -namespace std { - -struct S2 { - virtual void f(); -}; - -} - -// CHECK: define{{.*}}s1f -// NOSTD: llvm.bitset.test -// NOUUID-NOT: llvm.bitset.test -void s1f(S1 *s1) { - s1->f(); -} - -// CHECK: define{{.*}}s2f -// NOSTD-NOT: llvm.bitset.test -// NOUUID: llvm.bitset.test -void s2f(std::S2 *s2) { - s2->f(); -} Index: test/CodeGenCXX/bitset-inference.cpp =================================================================== --- /dev/null +++ test/CodeGenCXX/bitset-inference.cpp @@ -0,0 +1,98 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux -std=c++11 -fms-extensions -fvisibility hidden -fwhole-program-vtables -fdefault-class-scope=attrs -emit-llvm -o - %s | FileCheck --check-prefix=ITANIUM %s +// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc -std=c++11 -fms-extensions -fwhole-program-vtables -fdefault-class-scope=attrs -emit-llvm -o - %s | FileCheck --check-prefix=MS %s +// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc -fdefault-class-scope=global -std=c++11 -fms-extensions -fwhole-program-vtables -emit-llvm -o - %s | FileCheck --check-prefix=MS-INFER-GLOBAL %s +// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc -fdefault-class-scope=linkage-unit -std=c++11 -fms-extensions -fwhole-program-vtables -emit-llvm -o - %s | FileCheck --check-prefix=MS-INFER-LU %s + +struct C1 { + virtual void f(); +}; + +struct __attribute__((visibility("default"))) C2 { + virtual void f(); +}; + +struct __declspec(dllexport) C3 { + virtual void f(); +}; + +struct __declspec(dllimport) C4 { + virtual void f(); +}; + +struct __attribute__((visibility("default"))) [[clang::linkage_unit_scope]] C5 { + virtual void f(); +}; + +struct [[clang::global_scope]] C6 { + virtual void f(); +}; + +struct __declspec(uuid("00000000-0000-0000-0000-000000000000")) C7 { + virtual void f(); +}; + +namespace [[clang::global_scope]] ns {} + +namespace ns { + +struct C8 { + virtual void f(); + struct C9 { + virtual void f(); + }; + struct [[clang::linkage_unit_scope]] C10 { + virtual void f(); + }; +}; + +struct [[clang::linkage_unit_scope]] C11; +struct C11 { + virtual void f(); +}; + +} + +void f(C1 *c1, C2 *c2, C3 *c3, C4 *c4, C5 *c5, C6 *c6, C7 *c7, ns::C8 *c8, + ns::C8::C9 *c9, ns::C8::C10 *c10, ns::C11 *c11) { + // ITANIUM: bitset.test{{.*}}!"_ZTS2C1" + // MS: bitset.test{{.*}}!"?AUC1@@" + // MS-INFER-GLOBAL-NOT: bitset.test{{.*}}!"?AUC1@@" + // MS-INFER-LU: bitset.test{{.*}}!"?AUC1@@" + c1->f(); + // ITANIUM-NOT: bitset.test{{.*}}!"_ZTS2C2" + // MS: bitset.test{{.*}}!"?AUC2@@" + c2->f(); + // ITANIUM: bitset.test{{.*}}!"_ZTS2C3" + // MS-NOT: bitset.test{{.*}}!"?AUC3@@" + // MS-INFER-LU: bitset.test{{.*}}!"?AUC3@@" + c3->f(); + // ITANIUM: bitset.test{{.*}}!"_ZTS2C4" + // MS-NOT: bitset.test{{.*}}!"?AUC4@@" + c4->f(); + // ITANIUM: bitset.test{{.*}}!"_ZTS2C5" + // MS: bitset.test{{.*}}!"?AUC5@@" + // MS-INFER-GLOBAL: bitset.test{{.*}}!"?AUC5@@" + // MS-INFER-LU: bitset.test{{.*}}!"?AUC5@@" + c5->f(); + // ITANIUM-NOT: bitset.test{{.*}}!"_ZTS2C6" + // MS-NOT: bitset.test{{.*}}!"?AUC6@@" + // MS-INFER-GLOBAL-NOT: bitset.test{{.*}}!"?AUC6@@" + c6->f(); + // ITANIUM-NOT: bitset.test{{.*}}!"_ZTS2C7" + // MS-NOT: bitset.test{{.*}}!"?AUC7@@" + // MS-INFER-LU-NOT: bitset.test{{.*}}!"?AUC7@@" + c7->f(); + // ITANIUM-NOT: bitset.test{{.*}}!"_ZTSN2ns2C8E" + // MS-NOT: bitset.test{{.*}}!"?AUC8@ns@@" + // MS-INFER-LU-NOT: bitset.test{{.*}}!"?AUC8@ns@@" + c8->f(); + // ITANIUM-NOT: bitset.test{{.*}}!"_ZTSN2ns2C82C9E" + // MS-NOT: bitset.test{{.*}}!"?AUC9@C8@ns@@" + c9->f(); + // ITANIUM: bitset.test{{.*}}!"_ZTSN2ns2C83C10E" + // MS: bitset.test{{.*}}!"?AUC10@C8@ns@@" + c10->f(); + // ITANIUM: bitset.test{{.*}}!"_ZTSN2ns3C11E" + // MS: bitset.test{{.*}}!"?AUC11@ns@@" + c11->f(); +} Index: test/CodeGenCXX/bitsets.cpp =================================================================== --- test/CodeGenCXX/bitsets.cpp +++ test/CodeGenCXX/bitsets.cpp @@ -1,12 +1,12 @@ // Tests for the cfi-vcall feature: -// RUN: %clang_cc1 -triple x86_64-unknown-linux -fsanitize=cfi-vcall -fsanitize-trap=cfi-vcall -emit-llvm -o - %s | FileCheck --check-prefix=CFI --check-prefix=ITANIUM --check-prefix=ITANIUM-NDIAG --check-prefix=NDIAG %s -// RUN: %clang_cc1 -triple x86_64-unknown-linux -fsanitize=cfi-vcall -emit-llvm -o - %s | FileCheck --check-prefix=CFI --check-prefix=ITANIUM --check-prefix=ITANIUM-DIAG --check-prefix=DIAG --check-prefix=DIAG-ABORT %s -// RUN: %clang_cc1 -triple x86_64-unknown-linux -fsanitize=cfi-vcall -fsanitize-recover=cfi-vcall -emit-llvm -o - %s | FileCheck --check-prefix=CFI --check-prefix=ITANIUM --check-prefix=DIAG --check-prefix=DIAG-RECOVER %s -// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc -fsanitize=cfi-vcall -fsanitize-trap=cfi-vcall -emit-llvm -o - %s | FileCheck --check-prefix=CFI --check-prefix=MS --check-prefix=NDIAG %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux -fdefault-class-scope=linkage-unit -fsanitize=cfi-vcall -fsanitize-trap=cfi-vcall -emit-llvm -o - %s | FileCheck --check-prefix=CFI --check-prefix=ITANIUM --check-prefix=ITANIUM-NDIAG --check-prefix=NDIAG %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux -fdefault-class-scope=linkage-unit -fsanitize=cfi-vcall -emit-llvm -o - %s | FileCheck --check-prefix=CFI --check-prefix=ITANIUM --check-prefix=ITANIUM-DIAG --check-prefix=DIAG --check-prefix=DIAG-ABORT %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux -fdefault-class-scope=linkage-unit -fsanitize=cfi-vcall -fsanitize-recover=cfi-vcall -emit-llvm -o - %s | FileCheck --check-prefix=CFI --check-prefix=ITANIUM --check-prefix=DIAG --check-prefix=DIAG-RECOVER %s +// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc -fdefault-class-scope=linkage-unit -fsanitize=cfi-vcall -fsanitize-trap=cfi-vcall -emit-llvm -o - %s | FileCheck --check-prefix=CFI --check-prefix=MS --check-prefix=NDIAG %s // Tests for the whole-program-vtables feature: -// RUN: %clang_cc1 -triple x86_64-unknown-linux -fwhole-program-vtables -emit-llvm -o - %s | FileCheck --check-prefix=VTABLE-OPT --check-prefix=ITANIUM %s -// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc -fwhole-program-vtables -emit-llvm -o - %s | FileCheck --check-prefix=VTABLE-OPT --check-prefix=MS %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux -fdefault-class-scope=linkage-unit -fwhole-program-vtables -emit-llvm -o - %s | FileCheck --check-prefix=VTABLE-OPT --check-prefix=ITANIUM %s +// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc -fdefault-class-scope=linkage-unit -fwhole-program-vtables -emit-llvm -o - %s | FileCheck --check-prefix=VTABLE-OPT --check-prefix=MS %s // MS: @[[VTA:[0-9]*]] {{.*}} comdat($"\01??_7A@@6B@") // MS: @[[VTB:[0-9]*]] {{.*}} comdat($"\01??_7B@@6B0@@") Index: test/CodeGenCXX/cfi-blacklist.cpp =================================================================== --- /dev/null +++ test/CodeGenCXX/cfi-blacklist.cpp @@ -0,0 +1,29 @@ +// RUN: %clang_cc1 -fvisibility hidden -fms-extensions -fsanitize=cfi-vcall -emit-llvm -o - %s | FileCheck --check-prefix=CHECK --check-prefix=NOBL %s +// RUN: echo "type:std::*" > %t.txt +// RUN: %clang_cc1 -fvisibility hidden -fms-extensions -fsanitize=cfi-vcall -fsanitize-blacklist=%t.txt -emit-llvm -o - %s | FileCheck --check-prefix=CHECK --check-prefix=NOSTD %s + +struct S1 { + virtual void f(); +}; + +namespace std { + +struct S2 { + virtual void f(); +}; + +} + +// CHECK: define{{.*}}s1f +// NOBL: llvm.bitset.test +// NOSTD: llvm.bitset.test +void s1f(S1 *s1) { + s1->f(); +} + +// CHECK: define{{.*}}s2f +// NOBL: llvm.bitset.test +// NOSTD-NOT: llvm.bitset.test +void s2f(std::S2 *s2) { + s2->f(); +} Index: test/CodeGenCXX/cfi-cast.cpp =================================================================== --- test/CodeGenCXX/cfi-cast.cpp +++ test/CodeGenCXX/cfi-cast.cpp @@ -1,6 +1,6 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-linux -std=c++11 -fsanitize=cfi-derived-cast -fsanitize-trap=cfi-derived-cast -emit-llvm -o - %s | FileCheck -check-prefix=CHECK-DCAST %s -// RUN: %clang_cc1 -triple x86_64-unknown-linux -std=c++11 -fsanitize=cfi-unrelated-cast -fsanitize-trap=cfi-unrelated-cast -emit-llvm -o - %s | FileCheck -check-prefix=CHECK-UCAST %s -// RUN: %clang_cc1 -triple x86_64-unknown-linux -std=c++11 -fsanitize=cfi-unrelated-cast,cfi-cast-strict -fsanitize-trap=cfi-unrelated-cast,cfi-cast-strict -emit-llvm -o - %s | FileCheck -check-prefix=CHECK-UCAST-STRICT %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux -fdefault-class-scope=linkage-unit -std=c++11 -fsanitize=cfi-derived-cast -fsanitize-trap=cfi-derived-cast -emit-llvm -o - %s | FileCheck -check-prefix=CHECK-DCAST %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux -fdefault-class-scope=linkage-unit -std=c++11 -fsanitize=cfi-unrelated-cast -fsanitize-trap=cfi-unrelated-cast -emit-llvm -o - %s | FileCheck -check-prefix=CHECK-UCAST %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux -fdefault-class-scope=linkage-unit -std=c++11 -fsanitize=cfi-unrelated-cast,cfi-cast-strict -fsanitize-trap=cfi-unrelated-cast,cfi-cast-strict -emit-llvm -o - %s | FileCheck -check-prefix=CHECK-UCAST-STRICT %s // In this test the main thing we are searching for is something like // 'metadata !"1B"' where "1B" is the mangled name of the class we are Index: test/CodeGenCXX/cfi-nvcall.cpp =================================================================== --- test/CodeGenCXX/cfi-nvcall.cpp +++ test/CodeGenCXX/cfi-nvcall.cpp @@ -1,5 +1,5 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-linux -fsanitize=cfi-nvcall -emit-llvm -o - %s | FileCheck %s -// RUN: %clang_cc1 -triple x86_64-unknown-linux -fsanitize=cfi-nvcall,cfi-cast-strict -emit-llvm -o - %s | FileCheck --check-prefix=CHECK-STRICT %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux -fdefault-class-scope=linkage-unit -fsanitize=cfi-nvcall -emit-llvm -o - %s | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux -fdefault-class-scope=linkage-unit -fsanitize=cfi-nvcall,cfi-cast-strict -emit-llvm -o - %s | FileCheck --check-prefix=CHECK-STRICT %s struct A { virtual void f(); Index: test/CodeGenCXX/cfi-stats.cpp =================================================================== --- test/CodeGenCXX/cfi-stats.cpp +++ test/CodeGenCXX/cfi-stats.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-linux -fsanitize=cfi-vcall,cfi-nvcall,cfi-derived-cast,cfi-unrelated-cast,cfi-icall -fsanitize-stats -emit-llvm -o - %s | FileCheck --check-prefix=CHECK %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux -fvisibility hidden -fsanitize=cfi-vcall,cfi-nvcall,cfi-derived-cast,cfi-unrelated-cast,cfi-icall -fsanitize-stats -emit-llvm -o - %s | FileCheck --check-prefix=CHECK %s // CHECK: [[STATS:@[^ ]*]] = internal global { i8*, i32, [5 x [2 x i8*]] } { i8* null, i32 5, [5 x [2 x i8*]] // CHECK: {{\[\[}}2 x i8*] zeroinitializer, Index: test/Driver/default-class-scope.c =================================================================== --- /dev/null +++ test/Driver/default-class-scope.c @@ -0,0 +1,2 @@ +// RUN: %clang -target x86_64-unknown-linux -fdefault-class-scope=attrs -### %s 2>&1 | FileCheck --check-prefix=ATTRS %s +// ATTRS: "-fdefault-class-scope=attrs" Index: test/Driver/fsanitize.c =================================================================== --- test/Driver/fsanitize.c +++ test/Driver/fsanitize.c @@ -274,6 +274,10 @@ // RUN: %clang -target mips-unknown-linux -fsanitize=cfi-icall %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-CFI-ICALL-MIPS // CHECK-CFI-ICALL-MIPS: unsupported option '-fsanitize=cfi-icall' for target 'mips-unknown-linux' +// RUN: %clang -target x86_64-linux-gnu -fsanitize=cfi -flto -c %s -fdefault-class-scope=attrs -### 2>&1 | FileCheck %s --check-prefix=CHECK-CFI-CLASS-SCOPE +// CHECK-CFI-CLASS-SCOPE: -fdefault-class-scope=linkage-unit +// CHECK-CFI-CLASS-SCOPE: -fdefault-class-scope=attrs + // RUN: %clang -target x86_64-linux-gnu -fsanitize-trap=address -c %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-ASAN-TRAP // CHECK-ASAN-TRAP: error: unsupported argument 'address' to option '-fsanitize-trap' Index: test/Driver/whole-program-vtables.c =================================================================== --- test/Driver/whole-program-vtables.c +++ test/Driver/whole-program-vtables.c @@ -1,11 +1,2 @@ // RUN: %clang -target x86_64-unknown-linux -fwhole-program-vtables -### %s 2>&1 | FileCheck --check-prefix=NO-LTO %s // NO-LTO: invalid argument '-fwhole-program-vtables' only allowed with '-flto' - -// RUN: %clang -target x86_64-unknown-linux -resource-dir=%S/Inputs/resource_dir -flto -fwhole-program-vtables -### -c %s 2>&1 | FileCheck --check-prefix=BLACKLIST %s -// BLACKLIST: "-fwhole-program-vtables-blacklist={{.*}}vtables_blacklist.txt" - -// RUN: %clang -target x86_64-unknown-linux -fwhole-program-vtables-blacklist=nonexistent.txt -flto -fwhole-program-vtables -### -c %s 2>&1 | FileCheck --check-prefix=NON-EXISTENT-BLACKLIST %s -// NON-EXISTENT-BLACKLIST: no such file or directory: 'nonexistent.txt' - -// RUN: %clang -target x86_64-unknown-linux -fwhole-program-vtables-blacklist=%S/Inputs/resource_dir/vtables_blacklist.txt -flto -fwhole-program-vtables -### -c %s 2>&1 | FileCheck --check-prefix=CUSTOM-BLACKLIST %s -// CUSTOM-BLACKLIST: "-fwhole-program-vtables-blacklist={{.*}}Inputs/resource_dir/vtables_blacklist.txt" Index: test/Frontend/default-class-scope.c =================================================================== --- /dev/null +++ test/Frontend/default-class-scope.c @@ -0,0 +1,6 @@ +// RUN: %clang_cc1 -fdefault-class-scope=global %s +// RUN: %clang_cc1 -fdefault-class-scope=attrs %s +// RUN: %clang_cc1 -fdefault-class-scope=linkage-unit %s + +// RUN: not %clang_cc1 -fdefault-class-scope=foo %s 2>&1 | FileCheck %s +// CHECK: error: invalid value 'foo' in '-fdefault-class-scope=foo' Index: test/SemaCXX/attr-linkage-unit-scope.cpp =================================================================== --- /dev/null +++ test/SemaCXX/attr-linkage-unit-scope.cpp @@ -0,0 +1,51 @@ +// RUN: %clang_cc1 -std=c++11 -fsyntax-only -verify -DATTR=linkage_unit_scope %s +// RUN: %clang_cc1 -std=c++11 -fsyntax-only -verify -DATTR=global_scope %s + +int i [[clang::ATTR]]; // expected-warning {{attribute only applies to struct, union, class or namespace}} +typedef int t [[clang::ATTR]]; // expected-warning {{attribute only applies to struct, union, class or namespace}} +[[clang::ATTR]] void f(); // expected-warning {{attribute only applies to struct, union, class or namespace}} +void f() [[clang::ATTR]]; // expected-error {{attribute cannot be applied to types}} + +struct [[clang::ATTR]] s1 { + int i [[clang::ATTR]]; // expected-warning {{attribute only applies to struct, union, class or namespace}} + [[clang::ATTR]] void f(); // expected-warning {{attribute only applies to struct, union, class or namespace}} +}; + +struct [[clang::ATTR(1)]] s2 { // expected-error {{attribute takes no arguments}} + virtual void f(); +}; + +struct +[[clang::linkage_unit_scope]] // expected-error{{'linkage_unit_scope' and 'global_scope' attributes are not compatible}} +[[clang::global_scope]] // expected-note{{conflicting attribute is here}} +s3 {}; + +struct +[[clang::global_scope]] // expected-error{{'global_scope' and 'linkage_unit_scope' attributes are not compatible}} +[[clang::linkage_unit_scope]] // expected-note{{conflicting attribute is here}} +s4 {}; + +struct [[clang::ATTR]] s5; +struct s5 {}; + +struct s6; +struct [[clang::ATTR]] s6 {}; + +struct [[clang::ATTR]] s7; +struct s7; +struct s7 {}; + +struct [[clang::ATTR]] s8; +struct s8; +struct [[clang::ATTR]] s8 {}; + +struct s9; +struct [[clang::ATTR]] s9; + +struct [[clang::linkage_unit_scope]] s10; //expected-error{{attributes are not compatible}} +struct [[clang::global_scope]] s10 {}; // expected-note{{conflicting attribute is here}} + +namespace [[clang::ATTR]] ns1 {} + +namespace [[clang::linkage_unit_scope]] ns2 {} // expected-error{{attributes are not compatible}} +namespace [[clang::global_scope]] ns2 {} // expected-note{{conflicting attribute is here}} Index: utils/TableGen/ClangAttrEmitter.cpp =================================================================== --- utils/TableGen/ClangAttrEmitter.cpp +++ utils/TableGen/ClangAttrEmitter.cpp @@ -2527,6 +2527,7 @@ case ObjCProtocol | ObjCInterface: return "ExpectedObjectiveCInterfaceOrProtocol"; case Field | Var: return "ExpectedFieldOrGlobalVar"; + case GenericRecord | Namespace: return "ExpectedRecordOrNamespace"; } PrintFatalError(S.getLoc(),