diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -1591,7 +1591,7 @@ /// Extra information which affects how the function is called, like /// regparm and the calling convention. - unsigned ExtInfo : 13; + unsigned ExtInfo : 14; /// The ref-qualifier associated with a \c FunctionProtoType. /// @@ -1829,7 +1829,7 @@ Type(TypeClass tc, QualType canon, TypeDependence Dependence) : ExtQualsTypeCommonBase(this, canon.isNull() ? QualType(this_(), 0) : canon) { - static_assert(sizeof(*this) <= 8 + sizeof(ExtQualsTypeCommonBase), + static_assert(sizeof(*this) <= 16 + sizeof(ExtQualsTypeCommonBase), "changing bitfields changed sizeof(Type)!"); static_assert(alignof(decltype(*this)) % sizeof(void *) == 0, "Insufficient alignment!"); @@ -3672,6 +3672,8 @@ // | CC |noreturn|produces|nocallersavedregs|regparm|nocfcheck|cmsenscall| // |0 .. 4| 5 | 6 | 7 |8 .. 10| 11 | 12 | + // |kcfiunchecked| + // | 13 | // // regparm is either 0 (no regparm attribute) or the regparm value+1. enum { CallConvMask = 0x1F }; @@ -3684,6 +3686,7 @@ }; enum { NoCfCheckMask = 0x800 }; enum { CmseNSCallMask = 0x1000 }; + enum { KCFIUncheckedMask = 0x2000 }; uint16_t Bits = CC_C; ExtInfo(unsigned Bits) : Bits(static_cast(Bits)) {} @@ -3693,14 +3696,15 @@ // have all the elements (when reading an AST file for example). ExtInfo(bool noReturn, bool hasRegParm, unsigned regParm, CallingConv cc, bool producesResult, bool noCallerSavedRegs, bool NoCfCheck, - bool cmseNSCall) { + bool cmseNSCall, bool kcfiUnchecked) { assert((!hasRegParm || regParm < 7) && "Invalid regparm value"); Bits = ((unsigned)cc) | (noReturn ? NoReturnMask : 0) | (producesResult ? ProducesResultMask : 0) | (noCallerSavedRegs ? NoCallerSavedRegsMask : 0) | (hasRegParm ? ((regParm + 1) << RegParmOffset) : 0) | (NoCfCheck ? NoCfCheckMask : 0) | - (cmseNSCall ? CmseNSCallMask : 0); + (cmseNSCall ? CmseNSCallMask : 0) | + (kcfiUnchecked ? KCFIUncheckedMask : 0); } // Constructor with all defaults. Use when for example creating a @@ -3716,6 +3720,7 @@ bool getCmseNSCall() const { return Bits & CmseNSCallMask; } bool getNoCallerSavedRegs() const { return Bits & NoCallerSavedRegsMask; } bool getNoCfCheck() const { return Bits & NoCfCheckMask; } + bool getKCFIUnchecked() const { return Bits & KCFIUncheckedMask; } bool getHasRegParm() const { return ((Bits & RegParmMask) >> RegParmOffset) != 0; } unsigned getRegParm() const { @@ -3772,6 +3777,13 @@ return ExtInfo(Bits & ~NoCfCheckMask); } + ExtInfo withKCFIUnchecked(bool kcfiUnchecked) const { + if (kcfiUnchecked) + return ExtInfo(Bits | KCFIUncheckedMask); + else + return ExtInfo(Bits & ~KCFIUncheckedMask); + } + ExtInfo withRegParm(unsigned RegParm) const { assert(RegParm < 7 && "Invalid regparm value"); return ExtInfo((Bits & ~RegParmMask) | diff --git a/clang/include/clang/AST/TypeProperties.td b/clang/include/clang/AST/TypeProperties.td --- a/clang/include/clang/AST/TypeProperties.td +++ b/clang/include/clang/AST/TypeProperties.td @@ -287,6 +287,9 @@ def : Property<"cmseNSCall", Bool> { let Read = [{ node->getExtInfo().getCmseNSCall() }]; } + def : Property<"kcfiUnchecked", Bool> { + let Read = [{ node->getExtInfo().getKCFIUnchecked() }]; + } } let Class = FunctionNoProtoType in { @@ -294,7 +297,7 @@ auto extInfo = FunctionType::ExtInfo(noReturn, hasRegParm, regParm, callingConvention, producesResult, noCallerSavedRegs, noCfCheck, - cmseNSCall); + cmseNSCall, kcfiUnchecked); return ctx.getFunctionNoProtoType(returnType, extInfo); }]>; } @@ -328,7 +331,7 @@ auto extInfo = FunctionType::ExtInfo(noReturn, hasRegParm, regParm, callingConvention, producesResult, noCallerSavedRegs, noCfCheck, - cmseNSCall); + cmseNSCall, kcfiUnchecked); FunctionProtoType::ExtProtoInfo epi; epi.ExtInfo = extInfo; epi.Variadic = variadic; diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -3036,6 +3036,12 @@ let SimpleHandler = 1; } +def KCFIUnchecked : DeclOrTypeAttr, TargetSpecificAttr> { + let Spellings = [Clang<"kcfi_unchecked">]; + let Subjects = SubjectList<[FunctionLike]>; + let Documentation = [KCFIUncheckedDocs]; +} + // 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 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -5434,6 +5434,31 @@ }]; } +def KCFIUncheckedDocs : Documentation { + let Category = DocCatFunction; + let Content = [{ +The ``kcfi_unchecked`` attribute causes Clang to not emit :ref:`KCFI` +checks for indirect calls made through annotated function types. + +.. code-block:: c + + int f1(void) { return 0; } + + int (*p1)(void) = f1; + p1(); // checked + + int (*p2)(void) __attribute__((kcfi_unchecked)) = f1; + p2(); // unchecked + + typedef typeof(f1) * __attribute__((kcfi_unchecked) unchecked_t; + ((unchecked_t)f1)(); // unchecked + + unchecked_t p3 = f1; + p3(); // unchecked + +}]; +} + def ReinitializesDocs : Documentation { let Category = DocCatFunction; let Content = [{ diff --git a/clang/include/clang/CodeGen/CGFunctionInfo.h b/clang/include/clang/CodeGen/CGFunctionInfo.h --- a/clang/include/clang/CodeGen/CGFunctionInfo.h +++ b/clang/include/clang/CodeGen/CGFunctionInfo.h @@ -589,6 +589,9 @@ /// Log 2 of the maximum vector width. unsigned MaxVectorWidth : 4; + /// Whether this function has kcfi_unchecked attribute. + unsigned KCFIUnchecked : 1; + RequiredArgs Required; /// The struct representing all arguments passed in memory. Only used when @@ -677,6 +680,9 @@ /// Whether this function has nocf_check attribute. bool isNoCfCheck() const { return NoCfCheck; } + /// Whether this function has kcfi_unchecked attribute. + bool isKCFIUnchecked() const { return KCFIUnchecked; } + /// getASTCallingConvention() - Return the AST-specified calling /// convention. CallingConv getASTCallingConvention() const { @@ -703,7 +709,7 @@ return FunctionType::ExtInfo(isNoReturn(), getHasRegParm(), getRegParm(), getASTCallingConvention(), isReturnsRetained(), isNoCallerSavedRegs(), isNoCfCheck(), - isCmseNSCall()); + isCmseNSCall(), isKCFIUnchecked()); } CanQualType getReturnType() const { return getArgsBuffer()[0].type; } @@ -756,6 +762,7 @@ ID.AddInteger(RegParm); ID.AddBoolean(NoCfCheck); ID.AddBoolean(CmseNSCall); + ID.AddBoolean(KCFIUnchecked); ID.AddInteger(Required.getOpaqueData()); ID.AddBoolean(HasExtParameterInfos); if (HasExtParameterInfos) { @@ -784,6 +791,7 @@ ID.AddInteger(info.getRegParm()); ID.AddBoolean(info.getNoCfCheck()); ID.AddBoolean(info.getCmseNSCall()); + ID.AddBoolean(info.getKCFIUnchecked()); ID.AddInteger(required.getOpaqueData()); ID.AddBoolean(!paramInfos.empty()); if (!paramInfos.empty()) { diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -10043,6 +10043,8 @@ return {}; if (lbaseInfo.getNoCfCheck() != rbaseInfo.getNoCfCheck()) return {}; + if (lbaseInfo.getKCFIUnchecked() != rbaseInfo.getKCFIUnchecked()) + return {}; // FIXME: some uses, e.g. conditional exprs, really want this to be 'both'. bool NoReturn = lbaseInfo.getNoReturn() || rbaseInfo.getNoReturn(); diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp --- a/clang/lib/AST/ASTStructuralEquivalence.cpp +++ b/clang/lib/AST/ASTStructuralEquivalence.cpp @@ -619,6 +619,8 @@ return false; if (EI1.getNoCfCheck() != EI2.getNoCfCheck()) return false; + if (EI1.getKCFIUnchecked() != EI2.getKCFIUnchecked()) + return false; return true; } diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp --- a/clang/lib/AST/TypePrinter.cpp +++ b/clang/lib/AST/TypePrinter.cpp @@ -1004,6 +1004,8 @@ OS << " __attribute__((no_caller_saved_registers))"; if (Info.getNoCfCheck()) OS << " __attribute__((nocf_check))"; + if (Info.getKCFIUnchecked()) + OS << " __attribute__((kcfi_unchecked))"; } void TypePrinter::printFunctionNoProtoBefore(const FunctionNoProtoType *T, @@ -1728,6 +1730,9 @@ // FIXME: When Sema learns to form this AttributedType, avoid printing the // attribute again in printFunctionProtoAfter. case attr::AnyX86NoCfCheck: OS << "nocf_check"; break; + case attr::KCFIUnchecked: + OS << "kcfi_unchecked"; + break; case attr::CDecl: OS << "cdecl"; break; case attr::FastCall: OS << "fastcall"; break; case attr::StdCall: OS << "stdcall"; break; diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -825,6 +825,7 @@ FI->ReturnsRetained = info.getProducesResult(); FI->NoCallerSavedRegs = info.getNoCallerSavedRegs(); FI->NoCfCheck = info.getNoCfCheck(); + FI->KCFIUnchecked = info.getKCFIUnchecked(); FI->Required = required; FI->HasRegParm = info.getHasRegParm(); FI->RegParm = info.getRegParm(); diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -5315,9 +5315,12 @@ } } - if (SanOpts.has(SanitizerKind::KCFI) && IsIndirectCall) - EmitKCFICheck(Callee.getFunctionPointer(), - CGM.CreateKCFITypeId(QualType(FnType, 0))); + if (SanOpts.has(SanitizerKind::KCFI) && IsIndirectCall) { + if ((!TargetDecl || !TargetDecl->hasAttr()) && + !getFunctionExtInfo(CalleeType).getKCFIUnchecked()) + EmitKCFICheck(Callee.getFunctionPointer(), + CGM.CreateKCFITypeId(QualType(FnType, 0))); + } CallArgList Args; if (Chain) diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -8467,6 +8467,9 @@ case ParsedAttr::AT_AnyX86NoCfCheck: handleNoCfCheckAttr(S, D, AL); break; + case ParsedAttr::AT_KCFIUnchecked: + handleSimpleAttribute(S, D, AL); + break; case ParsedAttr::AT_NoThrow: if (!AL.isUsedAsTypeAttr()) handleSimpleAttribute(S, D, AL); diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp --- a/clang/lib/Sema/SemaType.cpp +++ b/clang/lib/Sema/SemaType.cpp @@ -136,6 +136,7 @@ case ParsedAttr::AT_CmseNSCall: \ case ParsedAttr::AT_AnyX86NoCallerSavedRegisters: \ case ParsedAttr::AT_AnyX86NoCfCheck: \ + case ParsedAttr::AT_KCFIUnchecked: \ CALLING_CONV_ATTRS_CASELIST // Microsoft-specific type qualifiers. @@ -7605,6 +7606,21 @@ return true; } + if (attr.getKind() == ParsedAttr::AT_KCFIUnchecked) { + if (S.CheckAttrTarget(attr) || S.CheckAttrNoArgs(attr)) + return true; + + // If this is not a function type, warning will be asserted by subject + // check. + if (!unwrapped.isFunctionType()) + return true; + + FunctionType::ExtInfo EI = + unwrapped.get()->getExtInfo().withKCFIUnchecked(true); + type = unwrapped.wrap(S, S.Context.adjustFunctionType(unwrapped.get(), EI)); + return true; + } + if (attr.getKind() == ParsedAttr::AT_Regparm) { unsigned value; if (S.CheckRegparmAttr(attr, value)) diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp --- a/clang/lib/Serialization/ASTWriter.cpp +++ b/clang/lib/Serialization/ASTWriter.cpp @@ -612,6 +612,7 @@ Abv->Add(BitCodeAbbrevOp(0)); // NoCallerSavedRegs Abv->Add(BitCodeAbbrevOp(0)); // NoCfCheck Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // CmseNSCall + Abv->Add(BitCodeAbbrevOp(0)); // KCFIUnchecked // FunctionProtoType Abv->Add(BitCodeAbbrevOp(0)); // IsVariadic Abv->Add(BitCodeAbbrevOp(0)); // HasTrailingReturn diff --git a/clang/test/CodeGen/kcfi_unchecked.c b/clang/test/CodeGen/kcfi_unchecked.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/kcfi_unchecked.c @@ -0,0 +1,126 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fsanitize=kcfi -o - %s | FileCheck %s + +#if !__has_feature(kcfi) +#error Missing kcfi +#endif + +// CHECK-LABEL: define dso_local i32 @f1(){{.*}} prefix i32 [[#%d,HASH:]] +int f1(void) { return 0; } + +typedef int (*fn_t)(void); +typedef int (*fn_unchecked_t)(void) __attribute__((kcfi_unchecked)); + +typedef typeof(f1) *fn_typeof_t; +typedef typeof(f1) *__attribute__((kcfi_unchecked)) fn_typeof_unchecked_t; + +// CHECK-LABEL: define{{.*}} i32 @checked() +int checked(void) { + // CHECK: call void @llvm.kcfi.check(i8* %[[#]], i32 [[#HASH]]) + // CHECK-NEXT: call i32 % + return ({ &f1; })(); +} +// CHECK-LABEL: define{{.*}} i32 @checked_typedef_cast() +int checked_typedef_cast(void) { + // CHECK: call void @llvm.kcfi.check(i8* %[[#]], i32 [[#HASH]]) + // CHECK-NEXT: call i32 % + return ({ (fn_t) & f1; })(); +} +// CHECK-LABEL: define{{.*}} i32 @checked_outside_typedef_cast() +int checked_outside_typedef_cast(void) { + // CHECK: call void @llvm.kcfi.check(i8* %[[#]], i32 [[#HASH]]) + // CHECK-NEXT: call i32 % + return ((fn_t)({ &f1; }))(); +} +// CHECK-LABEL: define{{.*}} i32 @checked_typeof_typedef_cast() +int checked_typeof_typedef_cast(void) { + // CHECK: call void @llvm.kcfi.check(i8* %[[#]], i32 [[#HASH]]) + // CHECK-NEXT: call i32 % + return ({ (fn_typeof_t) & f1; })(); +} +// CHECK-LABEL: define{{.*}} i32 @checked_var() +int checked_var(void) { + // CHECK: call void @llvm.kcfi.check(i8* %[[#]], i32 [[#HASH]]) + // CHECK-NEXT: call i32 % + fn_t p = f1; + return p(); +} +// CHECK-LABEL: define{{.*}} i32 @checked_param(i32 ()* +int checked_param(fn_t p) { + // CHECK: call void @llvm.kcfi.check(i8* %[[#]], i32 [[#HASH]]) + // CHECK-NEXT: call i32 % + return p(); +} +// CHECK-LABEL: define{{.*}} i32 @unchecked_typedef_cast() +int unchecked_typedef_cast(void) { + // CHECK-NOT: call void @llvm.kcfi.check + // CHECK: call i32 % + return ({ (fn_unchecked_t) & f1; })(); +} +// CHECK-LABEL: define{{.*}} i32 @unchecked_outside_typedef_cast() +int unchecked_outside_typedef_cast(void) { + // CHECK-NOT: call void @llvm.kcfi.check + // CHECK: call i32 % + return ((fn_unchecked_t)({ &f1; }))(); +} +// CHECK-LABEL: define{{.*}} i32 @unchecked_typeof_typedef_cast() +int unchecked_typeof_typedef_cast(void) { + // CHECK-NOT: call void @llvm.kcfi.check + // CHECK: call i32 % + return ({ (fn_typeof_unchecked_t) & f1; })(); +} +// CHECK-LABEL: define{{.*}} i32 @unchecked_compound_var() +int unchecked_compound_var(void) { + // CHECK-NOT: call void @llvm.kcfi.check + // CHECK: call i32 % + return ({ + fn_unchecked_t p = (fn_unchecked_t)f1; + p; + })(); +} +// CHECK-LABEL: define{{.*}} i32 @unchecked_compound_local_typedef_cast() +int unchecked_compound_local_typedef_cast(void) { + // CHECK-NOT: call void @llvm.kcfi.check + // CHECK: call i32 % + return ({ + typedef typeof(f1) *__attribute__((kcfi_unchecked)) fn_local_unchecked_t; + (fn_local_unchecked_t) & f1; + })(); +} +// CHECK-LABEL: define{{.*}} i32 @unchecked_var() +int unchecked_var(void) { + // CHECK-NOT: call void @llvm.kcfi.check + // CHECK: call i32 % + fn_unchecked_t p = (fn_unchecked_t)f1; + return p(); +} +// CHECK-LABEL: define{{.*}} i32 @unchecked_var_attr() +int unchecked_var_attr(void) { + // CHECK-NOT: call void @llvm.kcfi.check + // CHECK: call i32 % + fn_t __attribute__((kcfi_unchecked)) p = (fn_t __attribute__((kcfi_unchecked)))f1; + return p(); +} +// CHECK-LABEL: define{{.*}} i32 @unchecked_type_cast() +int unchecked_type_cast(void) { + // CHECK-NOT: call void @llvm.kcfi.check + // CHECK: call i32 @f1 + return ((fn_unchecked_t)f1)(); +} +// CHECK-LABEL: define{{.*}} i32 @unchecked_var_cast() +int unchecked_var_cast(void) { + // CHECK-NOT: call void @llvm.kcfi.check + // CHECK: call i32 @f1 + return ((fn_t __attribute__((kcfi_unchecked)))f1)(); +} +// CHECK-LABEL: define{{.*}} i32 @unchecked_cast() +int unchecked_cast(void) { + // CHECK-NOT: call void @llvm.kcfi.check + // CHECK: call i32 @f1 + return ((int (*__attribute__((kcfi_unchecked)))(void))f1)(); +} +// CHECK-LABEL: define{{.*}} i32 @unchecked_param(i32 ()* +int unchecked_param(fn_t __attribute__((kcfi_unchecked)) p) { + // CHECK-NOT: call void @llvm.kcfi.check + // CHECK: call i32 % + return p(); +} diff --git a/clang/test/CodeGen/kcfi_unchecked.cpp b/clang/test/CodeGen/kcfi_unchecked.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/kcfi_unchecked.cpp @@ -0,0 +1,49 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fsanitize=kcfi -o - %s | FileCheck %s + +#if !__has_feature(kcfi) +#error Missing kcfi +#endif + +// CHECK-LABEL: define{{.*}} i32 @_Z2f1v(){{.*}} prefix i32 [[#%d,HASH:]] +int f1(void) { return 0; } + +// CHECK-LABEL: define{{.*}} i32 @_Z2f2v(){{.*}} prefix i32 [[#%d,HASH2:]] +unsigned int f2(void) { return 1; } + +template int call(T p) { return p(); } + +// CHECK-LABEL: define{{.*}} i32 @_Z7checkedv() +int checked() { + // CHECK: call void @llvm.kcfi.check(i8* %[[#]], i32 [[#HASH]]) + // CHECK-NEXT: call{{.*}} i32 %[[#]]() + return ({ &f1; })(); +} + +// CHECK-LABEL: define{{.*}} i32 @_Z16checked_templatev() +int checked_template() { + // CHECK: call{{.*}} i32 @_Z4callIPFjvEEiT_(i32 ()*{{.*}} @_Z2f2v) + return call(f2); +} + +// CHECK-LABEL: define{{.*}} i32 @_Z4callIPFjvEEiT_(i32 ()*{{.*}} %p) +// CHECK: call void @llvm.kcfi.check(i8* %[[#]], i32 [[#HASH2]]) +// CHECK-NEXT: call{{.*}} i32 %[[#]]() + +// CHECK-LABEL: define{{.*}} i32 @_Z19unchecked_template1v() +int unchecked_template1() { + using unchecked_t = int (*)() [[clang::kcfi_unchecked]]; + unchecked_t p = (unchecked_t)f1; + // CHECK: call{{.*}} i32 @_Z4callIPFivEEiT_(i32 ()*{{.*}} %[[#]]) + return call(p); +} + +// CHECK-LABEL: define{{.*}} i32 @_Z4callIPFivEEiT_(i32 ()*{{.*}} %p) +// CHECK-NOT: call void @llvm.kcfi.check +// CHECK: call{{.*}} i32 %[[#]]() + +// CHECK-LABEL: define{{.*}} i32 @_Z19unchecked_template2v() +int unchecked_template2() { + using unchecked_t = int (*)() [[clang::kcfi_unchecked]]; + // CHECK: call{{.*}} i32 @_Z4callIPFivEEiT_(i32 ()*{{.*}} @_Z2f2v) + return call((unchecked_t)f2); +} diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test --- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -76,6 +76,7 @@ // CHECK-NEXT: IFunc (SubjectMatchRule_function) // CHECK-NEXT: InitPriority (SubjectMatchRule_variable) // CHECK-NEXT: InternalLinkage (SubjectMatchRule_variable, SubjectMatchRule_function, SubjectMatchRule_record) +// CHECK-NEXT: KCFIUnchecked (SubjectMatchRule_hasType_functionType) // CHECK-NEXT: LTOVisibilityPublic (SubjectMatchRule_record) // CHECK-NEXT: Leaf (SubjectMatchRule_function) // CHECK-NEXT: LoaderUninitialized (SubjectMatchRule_variable_is_global) diff --git a/clang/test/Sema/attr-kcfi_unchecked.c b/clang/test/Sema/attr-kcfi_unchecked.c new file mode 100644 --- /dev/null +++ b/clang/test/Sema/attr-kcfi_unchecked.c @@ -0,0 +1,18 @@ +// RUN: %clang_cc1 %s -verify -fsyntax-only + +int a __attribute__((kcfi_unchecked)); // expected-warning {{'kcfi_unchecked' attribute only applies to functions and function pointers}} +void *p __attribute__((kcfi_unchecked)); // expected-warning {{'kcfi_unchecked' attribute only applies to functions and function pointers}} +void (*f)(void) __attribute__((kcfi_unchecked)); +void (*g)(void) __attribute__((kcfi_unchecked("argument"))); // expected-error {{'kcfi_unchecked' attribute takes no arguments}} + +typedef unsigned long l_unchecked_t __attribute__((kcfi_unchecked)); // expected-warning {{'kcfi_unchecked' attribute only applies to functions and function pointers}} +typedef int (*f_unchecked_t)(void) __attribute__((kcfi_unchecked)); + +void f1(unsigned long p __attribute__((kcfi_unchecked))) {} // expected-warning {{'kcfi_unchecked' attribute only applies to functions and function pointers}} +void f2(void *p __attribute__((kcfi_unchecked))) {} // expected-warning {{'kcfi_unchecked' attribute only applies to functions and function pointers}} +void f3(void (*p)(void) __attribute__((kcfi_unchecked))) {} + +void f4(void) __attribute__((kcfi_unchecked)) {} +void test(void) { + ((void (*__attribute__((kcfi_unchecked)))(void))f4)(); +} diff --git a/clang/test/Sema/attr-kcfi_unchecked.cpp b/clang/test/Sema/attr-kcfi_unchecked.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Sema/attr-kcfi_unchecked.cpp @@ -0,0 +1,14 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-unknown -verify -std=c++11 -fsyntax-only %s + +typedef void (*fn_unchecked_t)(void) [[clang::kcfi_unchecked]]; // no-warning +typedef void (*fn_t)(void); + +int [[clang::kcfi_unchecked]] i; // expected-error {{'kcfi_unchecked' attribute cannot be applied to types}} +void f1(double i [[clang::kcfi_unchecked]]) {} // expected-warning {{'kcfi_unchecked' attribute only applies to functions and function pointers}} + +void f2(fn_t f) { + fn_unchecked_t p = f; // expected-error {{cannot initialize a variable of type}} + p(); // no-warning +} + +[[clang::kcfi_unchecked("argument")]] int f3(); // expected-error {{'kcfi_unchecked' attribute takes no arguments}}