From 634c601fe3c737b164c3f947721d802c6fe5e803 Mon Sep 17 00:00:00 2001 From: Vlad Tsyrklevich Date: Tue, 31 Oct 2017 22:39:44 +0000 Subject: [PATCH] [CFI] Add CFI-icall pointer type generalization Summary: This change allows generalizing pointers in type signatures used for cfi-icall by enabling the -fsanitize-cfi-icall-generalize-pointers flag. This works by 1) emitting an additional generalized type signature metadata node for functions and 2) llvm.type.test()ing for the generalized type for translation units with the flag specified. This flag is incompatible with -fsanitize-cfi-cross-dso because it would require emitting twice as many type hashes which would increase artifact size. Reviewers: pcc, eugenis Reviewed By: pcc Subscribers: kcc Differential Revision: https://reviews.llvm.org/D39358 llvm-svn: 317044 --- clang/docs/ClangCommandLineReference.rst | 4 ++ clang/docs/ControlFlowIntegrity.rst | 17 ++++++ clang/docs/UsersManual.rst | 5 ++ clang/include/clang/Driver/Options.td | 3 + clang/include/clang/Driver/SanitizerArgs.h | 1 + .../include/clang/Frontend/CodeGenOptions.def | 2 + clang/lib/CodeGen/CGExpr.cpp | 7 ++- clang/lib/CodeGen/CodeGenModule.cpp | 55 +++++++++++++++++++ clang/lib/CodeGen/CodeGenModule.h | 9 ++- clang/lib/Driver/SanitizerArgs.cpp | 10 ++++ clang/lib/Frontend/CompilerInvocation.cpp | 2 + clang/test/CodeGen/cfi-icall-cross-dso.c | 10 +++- clang/test/CodeGen/cfi-icall-generalize.c | 19 +++++++ clang/test/CodeGen/cfi-icall.c | 10 +++- clang/test/CodeGenCXX/cfi-icall.cpp | 19 ++++--- clang/test/Driver/fsanitize.c | 8 +++ 16 files changed, 165 insertions(+), 16 deletions(-) create mode 100644 clang/test/CodeGen/cfi-icall-generalize.c diff --git a/clang/docs/ClangCommandLineReference.rst b/clang/docs/ClangCommandLineReference.rst index 0cc9c71ebe63..0a0fce9190f4 100644 --- a/clang/docs/ClangCommandLineReference.rst +++ b/clang/docs/ClangCommandLineReference.rst @@ -740,6 +740,10 @@ Path to blacklist file for sanitizers Enable control flow integrity (CFI) checks for cross-DSO calls. +.. option:: -fsanitize-cfi-icall-generalize-pointers + +Generalize pointers in function type signatures used for Control Flow Integrity (CFI) indirect call checking + .. option:: -fsanitize-coverage=,..., -fno-sanitize-coverage=,... Specify the type of coverage instrumentation for Sanitizers diff --git a/clang/docs/ControlFlowIntegrity.rst b/clang/docs/ControlFlowIntegrity.rst index 04fb43a70f19..12b4610f8a28 100644 --- a/clang/docs/ControlFlowIntegrity.rst +++ b/clang/docs/ControlFlowIntegrity.rst @@ -215,6 +215,23 @@ shared library boundaries are handled as if the callee was not compiled with This scheme is currently only supported on the x86 and x86_64 architectures. +``-fsanitize-cfi-icall-generalize-pointers`` +-------------------------------------------- + +Mismatched pointer types are a common cause of cfi-icall check failures. +Translation units compiled with the ``-fsanitize-cfi-icall-generalize-pointers`` +flag relax pointer type checking for call sites in that translation unit, +applied across all functions compiled with ``-fsanitize=cfi-icall``. + +Specifically, pointers in return and argument types are treated as equivalent as +long as the qualifiers for the type they point to match. For example, ``char*`` +``char**`, and ``int*`` are considered equivalent types. However, ``char*`` and +``const char*`` are considered separate types. + +``-fsanitize-cfi-icall-generalize-pointers`` is not compatible with +``-fsanitize-cfi-cross-dso``. + + ``-fsanitize=cfi-icall`` and ``-fsanitize=function`` ---------------------------------------------------- diff --git a/clang/docs/UsersManual.rst b/clang/docs/UsersManual.rst index 2757ced1c24c..023f9f5528ba 100644 --- a/clang/docs/UsersManual.rst +++ b/clang/docs/UsersManual.rst @@ -1147,6 +1147,11 @@ are listed below. the behavior of sanitizers in the ``cfi`` group to allow checking of cross-DSO virtual and indirect calls. +.. option:: -fsanitize-cfi-icall-generalize-pointers + + Generalize pointers in return and argument types in function type signatures + checked by Control Flow Integrity indirect call checking. See + :doc:`ControlFlowIntegrity` for more details. .. option:: -fstrict-vtable-pointers diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index eef0eca713cd..e3476c721a7e 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -914,6 +914,9 @@ def fno_sanitize_cfi_cross_dso : Flag<["-"], "fno-sanitize-cfi-cross-dso">, Flags<[CoreOption, DriverOption]>, Group, HelpText<"Disable control flow integrity (CFI) checks for cross-DSO calls.">; +def fsanitize_cfi_icall_generalize_pointers : Flag<["-"], "fsanitize-cfi-icall-generalize-pointers">, + Group, + HelpText<"Generalize pointers in CFI indirect call type signature checks">; def fsanitize_stats : Flag<["-"], "fsanitize-stats">, Group, HelpText<"Enable sanitizer statistics gathering.">; diff --git a/clang/include/clang/Driver/SanitizerArgs.h b/clang/include/clang/Driver/SanitizerArgs.h index 52b6698857e8..19309c3f3b7f 100644 --- a/clang/include/clang/Driver/SanitizerArgs.h +++ b/clang/include/clang/Driver/SanitizerArgs.h @@ -32,6 +32,7 @@ class SanitizerArgs { int MsanTrackOrigins = 0; bool MsanUseAfterDtor = false; bool CfiCrossDso = false; + bool CfiICallGeneralizePointers = false; int AsanFieldPadding = 0; bool SharedRuntime = false; bool AsanUseAfterScope = true; diff --git a/clang/include/clang/Frontend/CodeGenOptions.def b/clang/include/clang/Frontend/CodeGenOptions.def index 8cac8e45c8e6..8f2aae2f140c 100644 --- a/clang/include/clang/Frontend/CodeGenOptions.def +++ b/clang/include/clang/Frontend/CodeGenOptions.def @@ -154,6 +154,8 @@ CODEGENOPT(SanitizeMemoryUseAfterDtor, 1, 0) ///< Enable use-after-delete detect CODEGENOPT(SanitizeCfiCrossDso, 1, 0) ///< Enable cross-dso support in CFI. CODEGENOPT(SanitizeMinimalRuntime, 1, 0) ///< Use "_minimal" sanitizer runtime for ///< diagnostics. +CODEGENOPT(SanitizeCfiICallGeneralizePointers, 1, 0) ///< Generalize pointer types in + ///< CFI icall function signatures CODEGENOPT(SanitizeCoverageType, 2, 0) ///< Type of sanitizer coverage ///< instrumentation. CODEGENOPT(SanitizeCoverageIndirectCalls, 1, 0) ///< Enable sanitizer coverage diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp index d3b03e556fba..88116f7d810e 100644 --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -4475,7 +4475,12 @@ RValue CodeGenFunction::EmitCall(QualType CalleeType, const CGCallee &OrigCallee SanitizerScope SanScope(this); EmitSanitizerStatReport(llvm::SanStat_CFI_ICall); - llvm::Metadata *MD = CGM.CreateMetadataIdentifierForType(QualType(FnType, 0)); + llvm::Metadata *MD; + if (CGM.getCodeGenOpts().SanitizeCfiICallGeneralizePointers) + MD = CGM.CreateMetadataIdentifierGeneralized(QualType(FnType, 0)); + else + MD = CGM.CreateMetadataIdentifierForType(QualType(FnType, 0)); + llvm::Value *TypeId = llvm::MetadataAsValue::get(getLLVMContext(), MD); llvm::Value *CalleePtr = Callee.getFunctionPointer(); diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 2254f3e70d58..b2a18a03f291 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -1152,6 +1152,7 @@ void CodeGenModule::CreateFunctionTypeMetadata(const FunctionDecl *FD, llvm::Metadata *MD = CreateMetadataIdentifierForType(FD->getType()); F->addTypeMetadata(0, MD); + F->addTypeMetadata(0, CreateMetadataIdentifierGeneralized(FD->getType())); // Emit a hash-based bit set entry for cross-DSO calls. if (CodeGenOpts.SanitizeCfiCrossDso) @@ -4543,6 +4544,60 @@ llvm::Metadata *CodeGenModule::CreateMetadataIdentifierForType(QualType T) { return InternalId; } +// Generalize pointer types to a void pointer with the qualifiers of the +// originally pointed-to type, e.g. 'const char *' and 'char * const *' +// generalize to 'const void *' while 'char *' and 'const char **' generalize to +// 'void *'. +static QualType GeneralizeType(ASTContext &Ctx, QualType Ty) { + if (!Ty->isPointerType()) + return Ty; + + return Ctx.getPointerType( + QualType(Ctx.VoidTy).withCVRQualifiers( + Ty->getPointeeType().getCVRQualifiers())); +} + +// Apply type generalization to a FunctionType's return and argument types +static QualType GeneralizeFunctionType(ASTContext &Ctx, QualType Ty) { + if (auto *FnType = Ty->getAs()) { + SmallVector GeneralizedParams; + for (auto &Param : FnType->param_types()) + GeneralizedParams.push_back(GeneralizeType(Ctx, Param)); + + return Ctx.getFunctionType( + GeneralizeType(Ctx, FnType->getReturnType()), + GeneralizedParams, FnType->getExtProtoInfo()); + } + + if (auto *FnType = Ty->getAs()) + return Ctx.getFunctionNoProtoType( + GeneralizeType(Ctx, FnType->getReturnType())); + + llvm_unreachable("Encountered unknown FunctionType"); +} + +llvm::Metadata *CodeGenModule::CreateMetadataIdentifierGeneralized(QualType T) { + T = GeneralizeFunctionType(getContext(), T); + + llvm::Metadata *&InternalId = GeneralizedMetadataIdMap[T.getCanonicalType()]; + if (InternalId) + return InternalId; + + if (isExternallyVisible(T->getLinkage())) { + std::string OutName; + llvm::raw_string_ostream Out(OutName); + getCXXABI().getMangleContext().mangleTypeName(T, Out); + Out << ".generalized"; + + InternalId = llvm::MDString::get(getLLVMContext(), Out.str()); + } else { + InternalId = llvm::MDNode::getDistinct(getLLVMContext(), + llvm::ArrayRef()); + } + + return InternalId; +} + /// Returns whether this module needs the "all-vtables" type identifier. bool CodeGenModule::NeedAllVtablesTypeId() const { // Returns true if at least one of vtable-based CFI checkers is enabled and diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h index 21bab6cfa054..7a47c576c0db 100644 --- a/clang/lib/CodeGen/CodeGenModule.h +++ b/clang/lib/CodeGen/CodeGenModule.h @@ -497,7 +497,9 @@ private: /// Mapping from canonical types to their metadata identifiers. We need to /// maintain this mapping because identifiers may be formed from distinct /// MDNodes. - llvm::DenseMap MetadataIdMap; + typedef llvm::DenseMap MetadataTypeMap; + MetadataTypeMap MetadataIdMap; + MetadataTypeMap GeneralizedMetadataIdMap; public: CodeGenModule(ASTContext &C, const HeaderSearchOptions &headersearchopts, @@ -1209,6 +1211,11 @@ public: /// internal identifiers). llvm::Metadata *CreateMetadataIdentifierForType(QualType T); + /// Create a metadata identifier for the generalization of the given type. + /// This may either be an MDString (for external identifiers) or a distinct + /// unnamed MDNode (for internal identifiers). + llvm::Metadata *CreateMetadataIdentifierGeneralized(QualType T); + /// Create and attach type metadata to the given function. void CreateFunctionTypeMetadata(const FunctionDecl *FD, llvm::Function *F); diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp index 037989680f2d..32c1c43a5bd9 100644 --- a/clang/lib/Driver/SanitizerArgs.cpp +++ b/clang/lib/Driver/SanitizerArgs.cpp @@ -520,6 +520,13 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC, // Without PIE, external function address may resolve to a PLT record, which // can not be verified by the target module. NeedPIE |= CfiCrossDso; + CfiICallGeneralizePointers = + Args.hasArg(options::OPT_fsanitize_cfi_icall_generalize_pointers); + + if (CfiCrossDso && CfiICallGeneralizePointers) + D.Diag(diag::err_drv_argument_not_allowed_with) + << "-fsanitize-cfi-cross-dso" + << "-fsanitize-cfi-icall-generalize-pointers"; } Stats = Args.hasFlag(options::OPT_fsanitize_stats, @@ -807,6 +814,9 @@ void SanitizerArgs::addArgs(const ToolChain &TC, const llvm::opt::ArgList &Args, if (CfiCrossDso) CmdArgs.push_back("-fsanitize-cfi-cross-dso"); + if (CfiICallGeneralizePointers) + CmdArgs.push_back("-fsanitize-cfi-icall-generalize-pointers"); + if (Stats) CmdArgs.push_back("-fsanitize-stats"); diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index 19e26c18bfdf..2c0d99b4befd 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -839,6 +839,8 @@ static bool ParseCodeGenArgs(CodeGenOptions &Opts, ArgList &Args, InputKind IK, false); Opts.SanitizeMinimalRuntime = Args.hasArg(OPT_fsanitize_minimal_runtime); Opts.SanitizeCfiCrossDso = Args.hasArg(OPT_fsanitize_cfi_cross_dso); + Opts.SanitizeCfiICallGeneralizePointers = + Args.hasArg(OPT_fsanitize_cfi_icall_generalize_pointers); Opts.SanitizeStats = Args.hasArg(OPT_fsanitize_stats); if (Arg *A = Args.getLastArg(OPT_fsanitize_address_use_after_scope, OPT_fno_sanitize_address_use_after_scope)) { diff --git a/clang/test/CodeGen/cfi-icall-cross-dso.c b/clang/test/CodeGen/cfi-icall-cross-dso.c index 636a9e4aedb4..43ab0e73b14a 100644 --- a/clang/test/CodeGen/cfi-icall-cross-dso.c +++ b/clang/test/CodeGen/cfi-icall-cross-dso.c @@ -46,7 +46,7 @@ void caller(void (*f)()) { // Check that we emit both string and hash based type entries for static void g(), // and don't emit them for the declaration of h(). -// CHECK: define internal void @g({{.*}} !type [[TVOID:![0-9]+]] !type [[TVOID_ID:![0-9]+]] +// CHECK: define internal void @g({{.*}} !type [[TVOID:![0-9]+]] !type [[TVOID_GENERALIZED:![0-9]+]] !type [[TVOID_ID:![0-9]+]] static void g(void) {} // CHECK: declare void @h({{[^!]*$}} @@ -60,9 +60,9 @@ Fn h1() { return &h; } -// CHECK: define void @bar({{.*}} !type [[TNOPROTO:![0-9]+]] !type [[TNOPROTO_ID:![0-9]+]] +// CHECK: define void @bar({{.*}} !type [[TNOPROTO:![0-9]+]] !type [[TNOPROTO_GENERALIZED:![0-9]+]] !type [[TNOPROTO_ID:![0-9]+]] // ITANIUM: define available_externally void @foo({{[^!]*$}} -// MS: define linkonce_odr void @foo({{.*}} !type [[TNOPROTO]] !type [[TNOPROTO_ID]] +// MS: define linkonce_odr void @foo({{.*}} !type [[TNOPROTO]] !type [[TNOPROTO_GENERALIZED:![0-9]+]] !type [[TNOPROTO_ID]] inline void foo() {} void bar() { foo(); } @@ -71,11 +71,15 @@ void bar() { foo(); } // Check that the type entries are correct. // ITANIUM: [[TVOID]] = !{i64 0, !"_ZTSFvvE"} +// ITANIUM: [[TVOID_GENERALIZED]] = !{i64 0, !"_ZTSFvvE.generalized"} // ITANIUM: [[TVOID_ID]] = !{i64 0, i64 9080559750644022485} // ITANIUM: [[TNOPROTO]] = !{i64 0, !"_ZTSFvE"} +// ITANIUM: [[TNOPROTO_GENERALIZED]] = !{i64 0, !"_ZTSFvE.generalized"} // ITANIUM: [[TNOPROTO_ID]] = !{i64 0, i64 6588678392271548388} // MS: [[TVOID]] = !{i64 0, !"?6AXXZ"} +// MS: [[TVOID_GENERALIZED]] = !{i64 0, !"?6AXXZ.generalized"} // MS: [[TVOID_ID]] = !{i64 0, i64 5113650790573562461} // MS: [[TNOPROTO]] = !{i64 0, !"?6AX@Z"} +// MS: [[TNOPROTO_GENERALIZED]] = !{i64 0, !"?6AX@Z.generalized"} // MS: [[TNOPROTO_ID]] = !{i64 0, i64 4195979634929632483} diff --git a/clang/test/CodeGen/cfi-icall-generalize.c b/clang/test/CodeGen/cfi-icall-generalize.c new file mode 100644 index 000000000000..c7c7b30a7a21 --- /dev/null +++ b/clang/test/CodeGen/cfi-icall-generalize.c @@ -0,0 +1,19 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux -fsanitize=cfi-icall -fsanitize-trap=cfi-icall -emit-llvm -o - %s | FileCheck --check-prefix=CHECK --check-prefix=UNGENERALIZED %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux -fsanitize=cfi-icall -fsanitize-trap=cfi-icall -fsanitize-cfi-icall-generalize-pointers -emit-llvm -o - %s | FileCheck --check-prefix=CHECK --check-prefix=GENERALIZED %s + +// Test that const char* is generalized to const void* and that const char** is +// generalized to void* + +// CHECK: define i32** @f({{.*}} !type [[TYPE:![0-9]+]] !type [[TYPE_GENERALIZED:![0-9]+]] +int** f(const char *a, const char **b) { + return (int**)0; +} + +void g(int** (*fp)(const char *, const char **)) { + // UNGENERALIZED: call i1 @llvm.type.test(i8* {{.*}}, metadata !"_ZTSFPPiPKcPS2_E") + // GENERALIZED: call i1 @llvm.type.test(i8* {{.*}}, metadata !"_ZTSFPvPKvS_E.generalized") + fp(0, 0); +} + +// CHECK: [[TYPE]] = !{i64 0, !"_ZTSFPPiPKcPS2_E"} +// CHECK: [[TYPE_GENERALIZED]] = !{i64 0, !"_ZTSFPvPKvS_E.generalized"} diff --git a/clang/test/CodeGen/cfi-icall.c b/clang/test/CodeGen/cfi-icall.c index ed34f4f44beb..5f346b66e81e 100644 --- a/clang/test/CodeGen/cfi-icall.c +++ b/clang/test/CodeGen/cfi-icall.c @@ -3,22 +3,26 @@ // Tests that we assign appropriate identifiers to unprototyped functions. -// CHECK: define void @f({{.*}} !type [[TVOID:![0-9]+]] +// CHECK: define void @f({{.*}} !type [[TVOID:![0-9]+]] !type [[TVOID_GENERALIZED:![0-9]+]] void f() { } void xf(); -// CHECK: define void @g({{.*}} !type [[TINT:![0-9]+]] +// CHECK: define void @g({{.*}} !type [[TINT:![0-9]+]] !type [[TINT_GENERALIZED:![0-9]+]] void g(int b) { void (*fp)() = b ? f : xf; // ITANIUM: call i1 @llvm.type.test(i8* {{.*}}, metadata !"_ZTSFvE") fp(); } -// CHECK: declare !type [[TVOID:![0-9]+]] void @xf({{.*}} +// CHECK: declare !type [[TVOID]] !type [[TVOID_GENERALIZED]] void @xf({{.*}} // ITANIUM-DAG: [[TVOID]] = !{i64 0, !"_ZTSFvE"} +// ITANIUM-DAG: [[TVOID_GENERALIZED]] = !{i64 0, !"_ZTSFvE.generalized"} // ITANIUM-DAG: [[TINT]] = !{i64 0, !"_ZTSFviE"} +// ITANIUM-DAG: [[TINT_GENERALIZED]] = !{i64 0, !"_ZTSFviE.generalized"} // MS-DAG: [[TVOID]] = !{i64 0, !"?6AX@Z"} +// MS-DAG: [[TVOID_GENERALIZED]] = !{i64 0, !"?6AX@Z.generalized"} // MS-DAG: [[TINT]] = !{i64 0, !"?6AXH@Z"} +// MS-DAG: [[TINT_GENERALIZED]] = !{i64 0, !"?6AXH@Z.generalized"} diff --git a/clang/test/CodeGenCXX/cfi-icall.cpp b/clang/test/CodeGenCXX/cfi-icall.cpp index c3c6ed309cc6..5f5778fc1f7c 100644 --- a/clang/test/CodeGenCXX/cfi-icall.cpp +++ b/clang/test/CodeGenCXX/cfi-icall.cpp @@ -8,19 +8,22 @@ namespace { struct S {}; -void f(S *s) { +void f(S s) { } } void g() { - void (*fp)(S *) = f; - // CHECK: call i1 @llvm.type.test(i8* {{.*}}, metadata [[VOIDS:![0-9]+]]) - fp(0); + struct S s; + void (*fp)(S) = f; + // CHECK: call i1 @llvm.type.test(i8* {{.*}}, metadata [[VOIDS1:![0-9]+]]) + fp(s); } -// ITANIUM: define internal void @_ZN12_GLOBAL__N_11fEPNS_1SE({{.*}} !type [[TS:![0-9]+]] -// MS: define internal void @"\01?f@?A@@YAXPEAUS@?A@@@Z"({{.*}} !type [[TS:![0-9]+]] +// ITANIUM: define internal void @_ZN12_GLOBAL__N_11fENS_1SE({{.*}} !type [[TS1:![0-9]+]] !type [[TS2:![0-9]+]] +// MS: define internal void @"\01?f@?A@@YAXUS@?A@@@Z"({{.*}} !type [[TS1:![0-9]+]] !type [[TS2:![0-9]+]] -// CHECK: [[VOIDS]] = distinct !{} -// CHECK: [[TS]] = !{i64 0, [[VOIDS]]} +// CHECK: [[VOIDS1]] = distinct !{} +// CHECK: [[TS1]] = !{i64 0, [[VOIDS1]]} +// CHECK: [[TS2]] = !{i64 0, [[VOIDS2:![0-9]+]]} +// CHECK: [[VOIDS2]] = distinct !{} diff --git a/clang/test/Driver/fsanitize.c b/clang/test/Driver/fsanitize.c index 92d5c5da72cc..dcca96ff3a89 100644 --- a/clang/test/Driver/fsanitize.c +++ b/clang/test/Driver/fsanitize.c @@ -480,6 +480,14 @@ // CHECK-CFI-NO-CROSS-DSO: -emit-llvm-bc // CHECK-CFI-NO-CROSS-DSO-NOT: -fsanitize-cfi-cross-dso +// RUN: %clang -target x86_64-linux-gnu -fsanitize=cfi-icall -fsanitize-cfi-icall-generalize-pointers -fvisibility=hidden -flto -c %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-CFI-GENERALIZE-POINTERS +// RUN: %clang -target x86_64-linux-gnu -fsanitize=cfi-icall -fvisibility=hidden -flto -c %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-NO-CFI-GENERALIZE-POINTERS +// CHECK-CFI-GENERALIZE-POINTERS: -fsanitize-cfi-icall-generalize-pointers +// CHECK-NO-CFI-GENERALIZE-POINTERS-NOT: -fsanitize-cfi-icall-generalize-pointers + +// 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 -fsanitize-stats -flto -c %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-CFI-STATS // CHECK-CFI-STATS: -fsanitize-stats -- GitLab