diff options
Diffstat (limited to 'clang')
-rw-r--r-- | clang/docs/ControlFlowIntegrity.rst | 48 | ||||
-rw-r--r-- | clang/include/clang/Basic/Attr.td | 6 | ||||
-rw-r--r-- | clang/include/clang/Basic/AttrDocs.td | 12 | ||||
-rw-r--r-- | clang/include/clang/Basic/CodeGenOptions.def | 2 | ||||
-rw-r--r-- | clang/include/clang/Driver/Options.td | 7 | ||||
-rw-r--r-- | clang/include/clang/Driver/SanitizerArgs.h | 1 | ||||
-rw-r--r-- | clang/lib/CodeGen/CodeGenFunction.cpp | 3 | ||||
-rw-r--r-- | clang/lib/CodeGen/CodeGenModule.cpp | 36 | ||||
-rw-r--r-- | clang/lib/Driver/SanitizerArgs.cpp | 7 | ||||
-rw-r--r-- | clang/lib/Frontend/CompilerInvocation.cpp | 2 | ||||
-rw-r--r-- | clang/lib/Sema/SemaDeclAttr.cpp | 3 | ||||
-rw-r--r-- | clang/test/CodeGen/cfi-icall-canonical-jump-tables.c | 24 | ||||
-rw-r--r-- | clang/test/CodeGen/cfi-icall-cross-dso.c | 8 | ||||
-rw-r--r-- | clang/test/Driver/fsanitize.c | 6 | ||||
-rw-r--r-- | clang/test/Misc/pragma-attribute-supported-attributes-list.test | 1 | ||||
-rw-r--r-- | clang/test/SemaCXX/attr-cfi-canonical-jump-table.cpp | 11 |
16 files changed, 159 insertions, 18 deletions
diff --git a/clang/docs/ControlFlowIntegrity.rst b/clang/docs/ControlFlowIntegrity.rst index f57bdf5d2cb..03e27d3ede8 100644 --- a/clang/docs/ControlFlowIntegrity.rst +++ b/clang/docs/ControlFlowIntegrity.rst @@ -235,6 +235,54 @@ long as the qualifiers for the type they point to match. For example, ``char*``, ``-fsanitize-cfi-icall-generalize-pointers`` is not compatible with ``-fsanitize-cfi-cross-dso``. +.. _cfi-canonical-jump-tables: + +``-fsanitize-cfi-canonical-jump-tables`` +---------------------------------------- + +The default behavior of Clang's indirect function call checker will replace +the address of each CFI-checked function in the output file's symbol table +with the address of a jump table entry which will pass CFI checks. We refer +to this as making the jump table `canonical`. This property allows code that +was not compiled with ``-fsanitize=cfi-icall`` to take a CFI-valid address +of a function, but it comes with a couple of caveats that are especially +relevant for users of cross-DSO CFI: + +- There is a performance and code size overhead associated with each + exported function, because each such function must have an associated + jump table entry, which must be emitted even in the common case where the + function is never address-taken anywhere in the program, and must be used + even for direct calls between DSOs, in addition to the PLT overhead. + +- There is no good way to take a CFI-valid address of a function written in + assembly or a language not supported by Clang. The reason is that the code + generator would need to insert a jump table in order to form a CFI-valid + address for assembly functions, but there is no way in general for the + code generator to determine the language of the function. This may be + possible with LTO in the intra-DSO case, but in the cross-DSO case the only + information available is the function declaration. One possible solution + is to add a C wrapper for each assembly function, but these wrappers can + present a significant maintenance burden for heavy users of assembly in + addition to adding runtime overhead. + +For these reasons, we provide the option of making the jump table non-canonical +with the flag ``-fno-sanitize-cfi-canonical-jump-tables``. When the jump +table is made non-canonical, symbol table entries point directly to the +function body. Any instances of a function's address being taken in C will +be replaced with a jump table address. + +This scheme does have its own caveats, however. It does end up breaking +function address equality more aggressively than the default behavior, +especially in cross-DSO mode which normally preserves function address +equality entirely. + +Furthermore, it is occasionally necessary for code not compiled with +``-fsanitize=cfi-icall`` to take a function address that is valid +for CFI. For example, this is necessary when a function's address +is taken by assembly code and then called by CFI-checking C code. The +``__attribute__((cfi_canonical_jump_table))`` attribute may be used to make +the jump table entry of a specific function canonical so that the external +code will end up taking a address for the function that will pass CFI checks. ``-fsanitize=cfi-icall`` and ``-fsanitize=function`` ---------------------------------------------------- diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 9f9292d4e5f..db13e9163d9 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -2436,6 +2436,12 @@ def NoSanitizeSpecific : InheritableAttr { let ASTNode = 0; } +def CFICanonicalJumpTable : InheritableAttr { + let Spellings = [Clang<"cfi_canonical_jump_table">]; + let Subjects = SubjectList<[Function], ErrorDiag>; + let Documentation = [CFICanonicalJumpTableDocs]; +} + // C/C++ Thread safety attributes (e.g. for deadlock, data race checking) // Not all of these attributes will be given a [[]] spelling. The attributes // which require access to function parameter names cannot use the [[]] spelling diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 0f10d903693..1d6c1ab3fca 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -2221,6 +2221,18 @@ to avoid false positives in other places. }]; } +def CFICanonicalJumpTableDocs : Documentation { + let Category = DocCatFunction; + let Heading = "cfi_canonical_jump_table"; + let Content = [{ +.. _langext-cfi_canonical_jump_table: + +Use ``__attribute__((cfi_canonical_jump_table))`` on a function declaration to +make the function's CFI jump table canonical. See :ref:`the CFI documentation +<cfi-canonical-jump-tables>` for more details. + }]; +} + def DocCatTypeSafety : DocumentationCategory<"Type Safety Checking"> { let Content = [{ Clang supports additional attributes to enable checking type safety properties diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def index 400711e7d19..736f201a12b 100644 --- a/clang/include/clang/Basic/CodeGenOptions.def +++ b/clang/include/clang/Basic/CodeGenOptions.def @@ -195,6 +195,8 @@ CODEGENOPT(SanitizeMinimalRuntime, 1, 0) ///< Use "_minimal" sanitizer runtime f ///< diagnostics. CODEGENOPT(SanitizeCfiICallGeneralizePointers, 1, 0) ///< Generalize pointer types in ///< CFI icall function signatures +CODEGENOPT(SanitizeCfiCanonicalJumpTables, 1, 0) ///< Make jump table symbols canonical + ///< instead of creating a local jump table. CODEGENOPT(SanitizeCoverageType, 2, 0) ///< Type of sanitizer coverage ///< instrumentation. CODEGENOPT(SanitizeCoverageIndirectCalls, 1, 0) ///< Enable sanitizer coverage diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 19022b4e2cd..0f2c314a337 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1061,6 +1061,13 @@ def fno_sanitize_cfi_cross_dso : Flag<["-"], "fno-sanitize-cfi-cross-dso">, def fsanitize_cfi_icall_generalize_pointers : Flag<["-"], "fsanitize-cfi-icall-generalize-pointers">, Group<f_clang_Group>, HelpText<"Generalize pointers in CFI indirect call type signature checks">; +def fsanitize_cfi_canonical_jump_tables : Flag<["-"], "fsanitize-cfi-canonical-jump-tables">, + Group<f_clang_Group>, + HelpText<"Make the jump table addresses canonical in the symbol table">; +def fno_sanitize_cfi_canonical_jump_tables : Flag<["-"], "fno-sanitize-cfi-canonical-jump-tables">, + Group<f_clang_Group>, + Flags<[CoreOption, DriverOption]>, + HelpText<"Do not make the jump table addresses canonical in the symbol table">; def fsanitize_stats : Flag<["-"], "fsanitize-stats">, Group<f_clang_Group>, HelpText<"Enable sanitizer statistics gathering.">; diff --git a/clang/include/clang/Driver/SanitizerArgs.h b/clang/include/clang/Driver/SanitizerArgs.h index e7de509d219..c37499e0f20 100644 --- a/clang/include/clang/Driver/SanitizerArgs.h +++ b/clang/include/clang/Driver/SanitizerArgs.h @@ -32,6 +32,7 @@ class SanitizerArgs { bool MsanUseAfterDtor = true; bool CfiCrossDso = false; bool CfiICallGeneralizePointers = false; + bool CfiCanonicalJumpTables = false; int AsanFieldPadding = 0; bool SharedRuntime = false; bool AsanUseAfterScope = true; diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp index eafe2667443..f7d9c8201d9 100644 --- a/clang/lib/CodeGen/CodeGenFunction.cpp +++ b/clang/lib/CodeGen/CodeGenFunction.cpp @@ -762,6 +762,9 @@ void CodeGenFunction::StartFunction(GlobalDecl GD, if (CGM.getCodeGenOpts().ProfileSampleAccurate) Fn->addFnAttr("profile-sample-accurate"); + if (D && D->hasAttr<CFICanonicalJumpTableAttr>()) + Fn->addFnAttr("cfi-canonical-jump-table"); + if (getLangOpts().OpenCL) { // Add metadata for a kernel function. if (const FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(D)) diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index b650526629b..8943b407bdc 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -535,6 +535,12 @@ void CodeGenModule::Release() { getModule().addModuleFlag(llvm::Module::Override, "Cross-DSO CFI", 1); } + if (LangOpts.Sanitize.has(SanitizerKind::CFIICall)) { + getModule().addModuleFlag(llvm::Module::Override, + "CFI Canonical Jump Tables", + CodeGenOpts.SanitizeCfiCanonicalJumpTables); + } + if (CodeGenOpts.CFProtectionReturn && Target.checkCFProtectionReturnSupported(getDiags())) { // Indicate that we want to instrument return control flow protection. @@ -1605,10 +1611,17 @@ void CodeGenModule::SetLLVMFunctionAttributesForDefinition(const Decl *D, F->setAlignment(2); } - // In the cross-dso CFI mode, we want !type attributes on definitions only. - if (CodeGenOpts.SanitizeCfiCrossDso) - if (auto *FD = dyn_cast<FunctionDecl>(D)) - CreateFunctionTypeMetadataForIcall(FD, F); + // In the cross-dso CFI mode with canonical jump tables, we want !type + // attributes on definitions only. + if (CodeGenOpts.SanitizeCfiCrossDso && + CodeGenOpts.SanitizeCfiCanonicalJumpTables) { + if (auto *FD = dyn_cast<FunctionDecl>(D)) { + // Skip available_externally functions. They won't be codegen'ed in the + // current module anyway. + if (getContext().GetGVALinkageForFunction(FD) != GVA_AvailableExternally) + CreateFunctionTypeMetadataForIcall(FD, F); + } + } // Emit type metadata on member functions for member function pointer checks. // These are only ever necessary on definitions; we're guaranteed that the @@ -1765,14 +1778,6 @@ void CodeGenModule::CreateFunctionTypeMetadataForIcall(const FunctionDecl *FD, if (isa<CXXMethodDecl>(FD) && !cast<CXXMethodDecl>(FD)->isStatic()) return; - // Additionally, if building with cross-DSO support... - if (CodeGenOpts.SanitizeCfiCrossDso) { - // Skip available_externally functions. They won't be codegen'ed in the - // current module anyway. - if (getContext().GetGVALinkageForFunction(FD) == GVA_AvailableExternally) - return; - } - llvm::Metadata *MD = CreateMetadataIdentifierForType(FD->getType()); F->addTypeMetadata(0, MD); F->addTypeMetadata(0, CreateMetadataIdentifierGeneralized(FD->getType())); @@ -1849,8 +1854,11 @@ void CodeGenModule::SetFunctionAttributes(GlobalDecl GD, llvm::Function *F, F->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global); // Don't emit entries for function declarations in the cross-DSO mode. This - // is handled with better precision by the receiving DSO. - if (!CodeGenOpts.SanitizeCfiCrossDso) + // is handled with better precision by the receiving DSO. But if jump tables + // are non-canonical then we need type metadata in order to produce the local + // jump table. + if (!CodeGenOpts.SanitizeCfiCrossDso || + !CodeGenOpts.SanitizeCfiCanonicalJumpTables) CreateFunctionTypeMetadataForIcall(FD, F); if (getLangOpts().OpenMP && FD->hasAttr<OMPDeclareSimdDeclAttr>()) diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp index 8e59e19be35..7d4cc539eaa 100644 --- a/clang/lib/Driver/SanitizerArgs.cpp +++ b/clang/lib/Driver/SanitizerArgs.cpp @@ -636,6 +636,10 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC, D.Diag(diag::err_drv_argument_not_allowed_with) << "-fsanitize-cfi-cross-dso" << "-fsanitize-cfi-icall-generalize-pointers"; + + CfiCanonicalJumpTables = + Args.hasFlag(options::OPT_fsanitize_cfi_canonical_jump_tables, + options::OPT_fno_sanitize_cfi_canonical_jump_tables, true); } Stats = Args.hasFlag(options::OPT_fsanitize_stats, @@ -976,6 +980,9 @@ void SanitizerArgs::addArgs(const ToolChain &TC, const llvm::opt::ArgList &Args, if (CfiICallGeneralizePointers) CmdArgs.push_back("-fsanitize-cfi-icall-generalize-pointers"); + if (CfiCanonicalJumpTables) + CmdArgs.push_back("-fsanitize-cfi-canonical-jump-tables"); + if (Stats) CmdArgs.push_back("-fsanitize-stats"); diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index 7d1b1965f93..fb5f1cc5ac7 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -1139,6 +1139,8 @@ static bool ParseCodeGenArgs(CodeGenOptions &Opts, ArgList &Args, InputKind IK, Opts.SanitizeCfiCrossDso = Args.hasArg(OPT_fsanitize_cfi_cross_dso); Opts.SanitizeCfiICallGeneralizePointers = Args.hasArg(OPT_fsanitize_cfi_icall_generalize_pointers); + Opts.SanitizeCfiCanonicalJumpTables = + Args.hasArg(OPT_fsanitize_cfi_canonical_jump_tables); Opts.SanitizeStats = Args.hasArg(OPT_fsanitize_stats); if (Arg *A = Args.getLastArg( OPT_fsanitize_address_poison_custom_array_cookie, diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 950dcac1bbc..2a72f7d5c61 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -7198,6 +7198,9 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, // Interacts with -fstack-protector options. handleSimpleAttribute<NoStackProtectorAttr>(S, D, AL); break; + case ParsedAttr::AT_CFICanonicalJumpTable: + handleSimpleAttribute<CFICanonicalJumpTableAttr>(S, D, AL); + break; case ParsedAttr::AT_StdCall: case ParsedAttr::AT_CDecl: case ParsedAttr::AT_FastCall: diff --git a/clang/test/CodeGen/cfi-icall-canonical-jump-tables.c b/clang/test/CodeGen/cfi-icall-canonical-jump-tables.c new file mode 100644 index 00000000000..cf7ca42781e --- /dev/null +++ b/clang/test/CodeGen/cfi-icall-canonical-jump-tables.c @@ -0,0 +1,24 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux -fsanitize=cfi-icall -fsanitize-cfi-cross-dso -emit-llvm -o - %s | FileCheck --check-prefixes=CHECK,NOCANON %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux -fsanitize=cfi-icall -fsanitize-cfi-cross-dso -fsanitize-cfi-canonical-jump-tables -emit-llvm -o - %s | FileCheck --check-prefixes=CHECK,CANON %s + +void ext(void); + +// CHECK: define void @f({{.*}} [[ATTR1:#[0-9]+]] +void f() { + ext(); +} + +// NOCANON: declare !type {{.*}} @ext() +// CANON: declare void @ext() + +// CHECK: define void @g({{.*}} [[ATTR2:#[0-9]+]] +__attribute__((cfi_canonical_jump_table)) void g() {} + +// CHECK: [[ATTR1]] = { +// CHECK-NOT: "cfi-canonical-jump-table" +// CHECK: } + +// CHECK: [[ATTR2]] = { {{.*}} "cfi-canonical-jump-table" {{.*}} } + +// NOCANON: !{i32 4, !"CFI Canonical Jump Tables", i32 0} +// CANON: !{i32 4, !"CFI Canonical Jump Tables", i32 1} diff --git a/clang/test/CodeGen/cfi-icall-cross-dso.c b/clang/test/CodeGen/cfi-icall-cross-dso.c index 67901c4f7d6..fbfe213c583 100644 --- a/clang/test/CodeGen/cfi-icall-cross-dso.c +++ b/clang/test/CodeGen/cfi-icall-cross-dso.c @@ -1,27 +1,27 @@ // RUN: %clang_cc1 -triple x86_64-unknown-linux -O1 -fno-experimental-new-pass-manager \ // RUN: -fsanitize=cfi-icall -fsanitize-cfi-cross-dso \ -// RUN: -emit-llvm -o - %s | FileCheck \ +// RUN: -fsanitize-cfi-canonical-jump-tables -emit-llvm -o - %s | FileCheck \ // RUN: --check-prefix=CHECK --check-prefix=CHECK-DIAG \ // RUN: --check-prefix=ITANIUM --check-prefix=ITANIUM-DIAG \ // RUN: %s // RUN: %clang_cc1 -triple x86_64-unknown-linux -O1 -fno-experimental-new-pass-manager \ // RUN: -fsanitize=cfi-icall -fsanitize-cfi-cross-dso -fsanitize-trap=cfi-icall \ -// RUN: -emit-llvm -o - %s | FileCheck \ +// RUN: -fsanitize-cfi-canonical-jump-tables -emit-llvm -o - %s | FileCheck \ // RUN: --check-prefix=CHECK \ // RUN: --check-prefix=ITANIUM --check-prefix=ITANIUM-TRAP \ // RUN: %s // RUN: %clang_cc1 -triple x86_64-pc-windows-msvc -O1 -fno-experimental-new-pass-manager \ // RUN: -fsanitize=cfi-icall -fsanitize-cfi-cross-dso \ -// RUN: -emit-llvm -o - %s | FileCheck \ +// RUN: -fsanitize-cfi-canonical-jump-tables -emit-llvm -o - %s | FileCheck \ // RUN: --check-prefix=CHECK --check-prefix=CHECK-DIAG \ // RUN: --check-prefix=MS --check-prefix=MS-DIAG \ // RUN: %s // RUN: %clang_cc1 -triple x86_64-pc-windows-msvc -O1 -fno-experimental-new-pass-manager \ // RUN: -fsanitize=cfi-icall -fsanitize-cfi-cross-dso -fsanitize-trap=cfi-icall \ -// RUN: -emit-llvm -o - %s | FileCheck \ +// RUN: -fsanitize-cfi-canonical-jump-tables -emit-llvm -o - %s | FileCheck \ // RUN: --check-prefix=CHECK \ // RUN: --check-prefix=MS --check-prefix=MS-TRAP \ // RUN: %s diff --git a/clang/test/Driver/fsanitize.c b/clang/test/Driver/fsanitize.c index 0caac04637d..6e8e61b2935 100644 --- a/clang/test/Driver/fsanitize.c +++ b/clang/test/Driver/fsanitize.c @@ -621,6 +621,12 @@ // RUN: %clang -target x86_64-linux-gnu -fsanitize=cfi-icall -fsanitize-cfi-icall-generalize-pointers -fsanitize-cfi-cross-dso -fvisibility=hidden -flto -c %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-CFI-GENERALIZE-AND-CROSS-DSO // CHECK-CFI-GENERALIZE-AND-CROSS-DSO: error: invalid argument '-fsanitize-cfi-cross-dso' not allowed with '-fsanitize-cfi-icall-generalize-pointers' +// RUN: %clang -target x86_64-linux-gnu -fsanitize=cfi-icall -fsanitize-cfi-canonical-jump-tables -fvisibility=hidden -flto -c %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-CFI-CANONICAL-JUMP-TABLES +// RUN: %clang -target x86_64-linux-gnu -fsanitize=cfi-icall -fno-sanitize-cfi-canonical-jump-tables -fvisibility=hidden -flto -c %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-NO-CFI-CANONICAL-JUMP-TABLES +// RUN: %clang -target x86_64-linux-gnu -fsanitize=cfi-icall -fvisibility=hidden -flto -c %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-CFI-CANONICAL-JUMP-TABLES +// CHECK-CFI-CANONICAL-JUMP-TABLES: -fsanitize-cfi-canonical-jump-tables +// CHECK-NO-CFI-CANONICAL-JUMP-TABLES-NOT: -fsanitize-cfi-canonical-jump-tables + // RUN: %clang -target x86_64-linux-gnu -fsanitize=cfi -fsanitize-stats -flto -c %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-CFI-STATS // CHECK-CFI-STATS: -fsanitize-stats diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test index cc2d3806018..83b8b9f0b9f 100644 --- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -21,6 +21,7 @@ // CHECK-NEXT: Availability ((SubjectMatchRule_record, SubjectMatchRule_enum, SubjectMatchRule_enum_constant, SubjectMatchRule_field, SubjectMatchRule_function, SubjectMatchRule_namespace, SubjectMatchRule_objc_category, SubjectMatchRule_objc_implementation, SubjectMatchRule_objc_interface, SubjectMatchRule_objc_method, SubjectMatchRule_objc_property, SubjectMatchRule_objc_protocol, SubjectMatchRule_record, SubjectMatchRule_type_alias, SubjectMatchRule_variable)) // CHECK-NEXT: CFAuditedTransfer (SubjectMatchRule_function) // CHECK-NEXT: CFConsumed (SubjectMatchRule_variable_is_parameter) +// CHECK-NEXT: CFICanonicalJumpTable (SubjectMatchRule_function) // CHECK-NEXT: CFUnknownTransfer (SubjectMatchRule_function) // CHECK-NEXT: CPUDispatch (SubjectMatchRule_function) // CHECK-NEXT: CPUSpecific (SubjectMatchRule_function) diff --git a/clang/test/SemaCXX/attr-cfi-canonical-jump-table.cpp b/clang/test/SemaCXX/attr-cfi-canonical-jump-table.cpp new file mode 100644 index 00000000000..a53fbaebaf5 --- /dev/null +++ b/clang/test/SemaCXX/attr-cfi-canonical-jump-table.cpp @@ -0,0 +1,11 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux -fsyntax-only -verify %s + +__attribute__((cfi_canonical_jump_table)) void fdecl(); + +__attribute__((cfi_canonical_jump_table)) void f() {} + +struct S { + __attribute__((cfi_canonical_jump_table)) void f() {} +}; + +__attribute__((cfi_canonical_jump_table)) int i; // expected-error {{'cfi_canonical_jump_table' attribute only applies to functions}} |