diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index 48f6486e4266e..a6c26a07800c3 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -1985,6 +1985,10 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase { LLVM_PREFERRED_TYPE(bool) unsigned HasTrailingReturn : 1; + /// Whether this function has is a cfi unchecked callee. + LLVM_PREFERRED_TYPE(bool) + unsigned CFIUncheckedCallee : 1; + /// Extra information which affects how the function is called, like /// regparm and the calling convention. LLVM_PREFERRED_TYPE(CallingConv) @@ -2566,6 +2570,8 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase { bool isSignableIntegerType(const ASTContext &Ctx) const; bool isAnyPointerType() const; // Any C pointer or ObjC object pointer bool isCountAttributedType() const; + bool isCFIUncheckedCalleeFunctionType() const; + bool hasPointeeToToCFIUncheckedCalleeFunctionType() const; bool isBlockPointerType() const; bool isVoidPointerType() const; bool isReferenceType() const; @@ -4713,6 +4719,10 @@ class FunctionType : public Type { /// type. bool getNoReturnAttr() const { return getExtInfo().getNoReturn(); } + /// Determine whether this is a function prototype that includes the + /// cfi_unchecked_callee attribute. + bool getCFIUncheckedCalleeAttr() const; + bool getCmseNSCallAttr() const { return getExtInfo().getCmseNSCall(); } CallingConv getCallConv() const { return getExtInfo().getCC(); } ExtInfo getExtInfo() const { return ExtInfo(FunctionTypeBits.ExtInfo); } @@ -5249,8 +5259,12 @@ class FunctionProtoType final /// the various bits of extra information about a function prototype. struct ExtProtoInfo { FunctionType::ExtInfo ExtInfo; + LLVM_PREFERRED_TYPE(bool) unsigned Variadic : 1; + LLVM_PREFERRED_TYPE(bool) unsigned HasTrailingReturn : 1; + LLVM_PREFERRED_TYPE(bool) + unsigned CFIUncheckedCallee : 1; unsigned AArch64SMEAttributes : 9; Qualifiers TypeQuals; RefQualifierKind RefQualifier = RQ_None; @@ -5260,12 +5274,12 @@ class FunctionProtoType final FunctionEffectsRef FunctionEffects; ExtProtoInfo() - : Variadic(false), HasTrailingReturn(false), + : Variadic(false), HasTrailingReturn(false), CFIUncheckedCallee(false), AArch64SMEAttributes(SME_NormalFunction) {} ExtProtoInfo(CallingConv CC) : ExtInfo(CC), Variadic(false), HasTrailingReturn(false), - AArch64SMEAttributes(SME_NormalFunction) {} + CFIUncheckedCallee(false), AArch64SMEAttributes(SME_NormalFunction) {} ExtProtoInfo withExceptionSpec(const ExceptionSpecInfo &ESI) { ExtProtoInfo Result(*this); @@ -5273,6 +5287,12 @@ class FunctionProtoType final return Result; } + ExtProtoInfo withCFIUncheckedCallee(bool CFIUncheckedCallee) { + ExtProtoInfo Result(*this); + Result.CFIUncheckedCallee = CFIUncheckedCallee; + return Result; + } + bool requiresFunctionProtoTypeExtraBitfields() const { return ExceptionSpec.Type == EST_Dynamic || requiresFunctionProtoTypeArmAttributes() || @@ -5432,6 +5452,7 @@ class FunctionProtoType final EPI.Variadic = isVariadic(); EPI.EllipsisLoc = getEllipsisLoc(); EPI.HasTrailingReturn = hasTrailingReturn(); + EPI.CFIUncheckedCallee = hasCFIUncheckedCallee(); EPI.ExceptionSpec = getExceptionSpecInfo(); EPI.TypeQuals = getMethodQuals(); EPI.RefQualifier = getRefQualifier(); @@ -5557,6 +5578,10 @@ class FunctionProtoType final /// Whether this function prototype has a trailing return type. bool hasTrailingReturn() const { return FunctionTypeBits.HasTrailingReturn; } + bool hasCFIUncheckedCallee() const { + return FunctionTypeBits.CFIUncheckedCallee; + } + Qualifiers getMethodQuals() const { if (hasExtQualifiers()) return *getTrailingObjects(); @@ -8391,6 +8416,27 @@ inline bool Type::isObjectPointerType() const { return false; } +inline bool Type::isCFIUncheckedCalleeFunctionType() const { + if (const auto *Fn = getAs()) + return Fn->hasCFIUncheckedCallee(); + return false; +} + +inline bool Type::hasPointeeToToCFIUncheckedCalleeFunctionType() const { + QualType Pointee; + if (const auto *PT = getAs()) + Pointee = PT->getPointeeType(); + else if (const auto *RT = getAs()) + Pointee = RT->getPointeeType(); + else if (const auto *MPT = getAs()) + Pointee = MPT->getPointeeType(); + else if (const auto *DT = getAs()) + Pointee = DT->getPointeeType(); + else + return false; + return Pointee->isCFIUncheckedCalleeFunctionType(); +} + inline bool Type::isFunctionPointerType() const { if (const auto *T = getAs()) return T->getPointeeType()->isFunctionType(); diff --git a/clang/include/clang/AST/TypeProperties.td b/clang/include/clang/AST/TypeProperties.td index f43d3ebd229d2..6e44bce893e79 100644 --- a/clang/include/clang/AST/TypeProperties.td +++ b/clang/include/clang/AST/TypeProperties.td @@ -317,6 +317,9 @@ let Class = FunctionProtoType in { def : Property<"trailingReturn", Bool> { let Read = [{ node->hasTrailingReturn() }]; } + def : Property<"cfiUncheckedCallee", Bool> { + let Read = [{ node->hasCFIUncheckedCallee() }]; + } def : Property<"methodQualifiers", Qualifiers> { let Read = [{ node->getMethodQuals() }]; } @@ -353,6 +356,7 @@ let Class = FunctionProtoType in { epi.ExtInfo = extInfo; epi.Variadic = variadic; epi.HasTrailingReturn = trailingReturn; + epi.CFIUncheckedCallee = cfiUncheckedCallee; epi.TypeQuals = methodQualifiers; epi.RefQualifier = refQualifier; epi.ExceptionSpec = exceptionSpecifier; diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 216084344c00d..67b7525e806fb 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -3111,6 +3111,11 @@ def NoDeref : TypeAttr { let Documentation = [NoDerefDocs]; } +def CFIUncheckedCallee : TypeAttr { + let Spellings = [Clang<"cfi_unchecked_callee">]; + let Documentation = [CFIUncheckedCalleeDocs]; +} + def ReqdWorkGroupSize : InheritableAttr { // Does not have a [[]] spelling because it is an OpenCL-related attribute. let Spellings = [GNU<"reqd_work_group_size">]; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 65d66dd398ad1..a5898b743a7ea 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -6869,6 +6869,54 @@ for references or Objective-C object pointers. }]; } +def CFIUncheckedCalleeDocs : Documentation { + let Category = DocCatType; + let Content = [{ +``cfi_unchecked_callee`` is a function type attribute which prevents the compiler from instrumenting +`Control Flow Integrity `_ checks on indirect +function calls. Specifically, the attribute has the following semantics: + +1. Indirect calls to a function type with this attribute will not be instrumented with CFI. That is, + the indirect call will not be checked. Note that this only changes the behavior for indirect calls + on pointers to function types having this attribute. It does not prevent all indirect function calls + for a given type from being checked. +2. All direct references to a function whose type has this attribute will always reference the + function definition rather than an entry in the CFI jump table. +3. When a pointer to a function with this attribute is implicitly cast to a pointer to a function + without this attribute, the compiler will give a warning saying this attribute is discarded. This + warning can be silenced with an explicit cast. Note an explicit cast just disables the warning, so + direct references to a function with a ``cfi_unchecked_callee`` attribute will still reference the + function definition rather than the CFI jump table. + +.. code-block:: c + + #define CFI_UNCHECKED_CALLEE __attribute__((cfi_unchecked_callee)) + + void no_cfi() CFI_UNCHECKED_CALLEE {} + + void (*with_cfi)() = no_cfi; // warning: implicit conversion discards `cfi_unchecked_callee` attribute. + // `with_cfi` also points to the actual definition of `no_cfi` rather than + // its jump table entry. + + void invoke(void (CFI_UNCHECKED_CALLEE *func)()) { + func(); // CFI will not instrument this indirect call. + + void (*func2)() = func; // warning: implicit conversion discards `cfi_unchecked_callee` attribute. + + func2(); // CFI will instrument this indirect call. Users should be careful however because if this + // references a function with type `cfi_unchecked_callee`, then the CFI check may incorrectly + // fail because the reference will be to the function definition rather than the CFI jump + // table entry. + } + +This attribute can only be applied on functions or member functions. This attribute can be a good +alternative to ``no_sanitize("cfi")`` if you only want to disable innstrumentation for specific indirect +calls rather than applying ``no_sanitize("cfi")`` on the whole function containing indirect call. Note +that ``cfi_unchecked_attribute`` is a type attribute doesn't disable CFI instrumentation on a function +body. +}]; +} + def ReinitializesDocs : Documentation { let Category = DocCatFunction; let Content = [{ diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 6f1e8d9fc74e6..547499ae0f19e 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -12711,6 +12711,11 @@ def warn_noderef_on_non_pointer_or_array : Warning< def warn_noderef_to_dereferenceable_pointer : Warning< "casting to dereferenceable pointer removes 'noderef' attribute">, InGroup; +def warn_cast_discards_cfi_unchecked_callee + : Warning<"implicit conversion from %0 to %1 discards " + "'cfi_unchecked_callee' attribute">, + InGroup>; + def err_builtin_launder_invalid_arg : Error< "%select{non-pointer|function pointer|void pointer}0 argument to " "'__builtin_launder' is not allowed">; diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 8040f1ac6af01..d63bf9aa010c5 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -2645,6 +2645,16 @@ class Sema final : public SemaBase { /// void*). void DiscardMisalignedMemberAddress(const Type *T, Expr *E); + /// Returns true if `From` is a function or pointer to a function with the + /// `cfi_unchecked_callee` attribute but `To` is a function or pointer to + /// function without this attribute. + bool DiscardingCFIUncheckedCallee(QualType From, QualType To) const; + + /// Returns true if `From` is a function or pointer to a function without the + /// `cfi_unchecked_callee` attribute but `To` is a function or pointer to + /// function with this attribute. + bool AddingCFIUncheckedCallee(QualType From, QualType To) const; + /// This function calls Action when it determines that E designates a /// misaligned member due to the packed attribute. This is used to emit /// local diagnostics like in reference binding. @@ -10254,9 +10264,15 @@ class Sema final : public SemaBase { bool CStyle, bool &ObjCLifetimeConversion); /// Determine whether the conversion from FromType to ToType is a valid - /// conversion that strips "noexcept" or "noreturn" off the nested function - /// type. - bool IsFunctionConversion(QualType FromType, QualType ToType) const; + /// conversion that strips "noexcept" or "noreturn" or "cfi_unchecked_callee" + /// off the nested function type. This also checks if "cfi_unchecked_callee" + /// was added to the function type. If "cfi_unchecked_callee" is added and + /// `AddingCFIUncheckedCallee` is provided, it will be set to true. The same + /// thing applies for `DiscardingCFIUncheckedCallee` if the attribute is + /// discarded. + bool IsFunctionConversion(QualType FromType, QualType ToType, + bool *DiscardingCFIUncheckedCallee = nullptr, + bool *AddingCFIUncheckedCallee = nullptr) const; /// Same as `IsFunctionConversion`, but if this would return true, it sets /// `ResultTy` to `ToType`. diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index 1ab18a5c26c0f..e32ee3985801f 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -3571,6 +3571,12 @@ QualType QualType::getNonLValueExprType(const ASTContext &Context) const { return *this; } +bool FunctionType::getCFIUncheckedCalleeAttr() const { + if (const auto *FPT = getAs()) + return FPT->hasCFIUncheckedCallee(); + return false; +} + StringRef FunctionType::getNameForCallConv(CallingConv CC) { switch (CC) { case CC_C: @@ -3662,6 +3668,7 @@ FunctionProtoType::FunctionProtoType(QualType result, ArrayRef params, FunctionTypeBits.HasExtParameterInfos = !!epi.ExtParameterInfos; FunctionTypeBits.Variadic = epi.Variadic; FunctionTypeBits.HasTrailingReturn = epi.HasTrailingReturn; + FunctionTypeBits.CFIUncheckedCallee = epi.CFIUncheckedCallee; if (epi.requiresFunctionProtoTypeExtraBitfields()) { FunctionTypeBits.HasExtraBitfields = true; @@ -3929,6 +3936,7 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID &ID, QualType Result, ID.AddInteger((EffectCount << 3) | (HasConds << 2) | (epi.AArch64SMEAttributes << 1) | epi.HasTrailingReturn); + ID.AddInteger(epi.CFIUncheckedCallee); for (unsigned Idx = 0; Idx != EffectCount; ++Idx) { ID.AddInteger(epi.FunctionEffects.Effects[Idx].toOpaqueInt32()); diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp index 4793ef38c2c46..4d37476cb8421 100644 --- a/clang/lib/AST/TypePrinter.cpp +++ b/clang/lib/AST/TypePrinter.cpp @@ -1050,6 +1050,9 @@ void TypePrinter::printFunctionProtoAfter(const FunctionProtoType *T, OS << "))"; } + if (T->hasCFIUncheckedCallee()) + OS << " __attribute__((cfi_unchecked_callee))"; + if (T->hasTrailingReturn()) { OS << " -> "; print(T->getReturnType(), OS, StringRef()); @@ -2090,6 +2093,9 @@ void TypePrinter::printAttributedAfter(const AttributedType *T, case attr::NoDeref: OS << "noderef"; break; + case attr::CFIUncheckedCallee: + OS << "cfi_unchecked_callee"; + break; case attr::AcquireHandle: OS << "acquire_handle"; break; diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp index 40419deea07b3..eb569c04b047c 100644 --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -3053,9 +3053,13 @@ static LValue EmitFunctionDeclLValue(CodeGenFunction &CGF, const Expr *E, GlobalDecl GD) { const FunctionDecl *FD = cast(GD.getDecl()); llvm::Constant *V = CGF.CGM.getFunctionPointer(GD); + QualType ETy = E->getType(); + if (ETy->isCFIUncheckedCalleeFunctionType()) { + if (auto *GV = dyn_cast(V)) + V = llvm::NoCFIValue::get(GV); + } CharUnits Alignment = CGF.getContext().getDeclAlign(FD); - return CGF.MakeAddrLValue(V, E->getType(), Alignment, - AlignmentSource::Decl); + return CGF.MakeAddrLValue(V, ETy, Alignment, AlignmentSource::Decl); } static LValue EmitCapturedFieldLValue(CodeGenFunction &CGF, const FieldDecl *FD, @@ -6349,10 +6353,13 @@ RValue CodeGenFunction::EmitCall(QualType CalleeType, FD && FD->hasAttr()) CGM.getTargetCodeGenInfo().setOCLKernelStubCallingConvention(FnType); + bool CFIUnchecked = + CalleeType->hasPointeeToToCFIUncheckedCalleeFunctionType(); + // If we are checking indirect calls and this call is indirect, check that the // function pointer is a member of the bit set for the function type. if (SanOpts.has(SanitizerKind::CFIICall) && - (!TargetDecl || !isa(TargetDecl))) { + (!TargetDecl || !isa(TargetDecl)) && !CFIUnchecked) { SanitizerScope SanScope(this); EmitSanitizerStatReport(llvm::SanStat_CFI_ICall); ApplyDebugLocation ApplyTrapDI( diff --git a/clang/lib/CodeGen/CGExprConstant.cpp b/clang/lib/CodeGen/CGExprConstant.cpp index 6ba45f42db4d1..069cc09cd91d3 100644 --- a/clang/lib/CodeGen/CGExprConstant.cpp +++ b/clang/lib/CodeGen/CGExprConstant.cpp @@ -2236,8 +2236,12 @@ ConstantLValueEmitter::tryEmitBase(const APValue::LValueBase &base) { return ConstantLValue(C); }; - if (const auto *FD = dyn_cast(D)) - return PtrAuthSign(CGM.getRawFunctionPointer(FD)); + if (const auto *FD = dyn_cast(D)) { + llvm::Constant *C = CGM.getRawFunctionPointer(FD); + if (FD->getType()->isCFIUncheckedCalleeFunctionType()) + C = llvm::NoCFIValue::get(cast(C)); + return PtrAuthSign(C); + } if (const auto *VD = dyn_cast(D)) { // We can never refer to a variable with local storage. diff --git a/clang/lib/CodeGen/CGPointerAuth.cpp b/clang/lib/CodeGen/CGPointerAuth.cpp index bf22912988d38..dcef01a5eb6d3 100644 --- a/clang/lib/CodeGen/CGPointerAuth.cpp +++ b/clang/lib/CodeGen/CGPointerAuth.cpp @@ -520,6 +520,11 @@ llvm::Constant *CodeGenModule::getMemberFunctionPointer(llvm::Constant *Pointer, Pointer, PointerAuth.getKey(), nullptr, cast_or_null(PointerAuth.getDiscriminator())); + if (const auto *MFT = dyn_cast(FT.getTypePtr())) { + if (MFT->hasPointeeToToCFIUncheckedCalleeFunctionType()) + Pointer = llvm::NoCFIValue::get(cast(Pointer)); + } + return Pointer; } diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp index a2984941947c7..aac7bb7db1550 100644 --- a/clang/lib/CodeGen/ItaniumCXXABI.cpp +++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp @@ -693,6 +693,17 @@ CGCallee ItaniumCXXABI::EmitLoadOfMemberFunctionPointer( llvm::Constant *CheckTypeDesc; bool ShouldEmitCFICheck = CGF.SanOpts.has(SanitizerKind::CFIMFCall) && CGM.HasHiddenLTOVisibility(RD); + + if (ShouldEmitCFICheck) { + if (const auto *BinOp = dyn_cast(E)) { + if (BinOp->isPtrMemOp() && + BinOp->getRHS() + ->getType() + ->hasPointeeToToCFIUncheckedCalleeFunctionType()) + ShouldEmitCFICheck = false; + } + } + bool ShouldEmitVFEInfo = CGM.getCodeGenOpts().VirtualFunctionElimination && CGM.HasHiddenLTOVisibility(RD); bool ShouldEmitWPDInfo = diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 49593e55a6933..059386d638bd1 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -11912,6 +11912,46 @@ static void DiagnoseMixedUnicodeImplicitConversion(Sema &S, const Type *Source, } } +enum CFIUncheckedCalleeChange { + None, + Adding, + Discarding, +}; + +static CFIUncheckedCalleeChange AdjustingCFIUncheckedCallee(QualType From, + QualType To) { + QualType MaybePointee = From->getPointeeType(); + if (!MaybePointee.isNull() && MaybePointee->getAs()) + From = MaybePointee; + MaybePointee = To->getPointeeType(); + if (!MaybePointee.isNull() && MaybePointee->getAs()) + To = MaybePointee; + + if (const auto *FromFn = From->getAs()) { + if (const auto *ToFn = To->getAs()) { + if (FromFn->getCFIUncheckedCalleeAttr() && + !ToFn->getCFIUncheckedCalleeAttr()) + return Discarding; + if (!FromFn->getCFIUncheckedCalleeAttr() && + ToFn->getCFIUncheckedCalleeAttr()) + return Adding; + } + } + return None; +} + +bool Sema::DiscardingCFIUncheckedCallee(QualType From, QualType To) const { + From = Context.getCanonicalType(From); + To = Context.getCanonicalType(To); + return ::AdjustingCFIUncheckedCallee(From, To) == Discarding; +} + +bool Sema::AddingCFIUncheckedCallee(QualType From, QualType To) const { + From = Context.getCanonicalType(From); + To = Context.getCanonicalType(To); + return ::AdjustingCFIUncheckedCallee(From, To) == Adding; +} + void Sema::CheckImplicitConversion(Expr *E, QualType T, SourceLocation CC, bool *ICContext, bool IsListInit) { if (E->isTypeDependent() || E->isValueDependent()) return; @@ -12257,6 +12297,11 @@ void Sema::CheckImplicitConversion(Expr *E, QualType T, SourceLocation CC, if (Target->isBooleanType()) DiagnoseIntInBoolContext(*this, E); + if (DiscardingCFIUncheckedCallee(QualType(Source, 0), QualType(Target, 0))) { + Diag(CC, diag::warn_cast_discards_cfi_unchecked_callee) + << QualType(Source, 0) << QualType(Target, 0); + } + if (!Source->isIntegerType() || !Target->isIntegerType()) return; diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index a8e4023fb02ba..90477010dd972 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -872,6 +872,21 @@ static void handleDiagnoseIfAttr(Sema &S, Decl *D, const ParsedAttr &AL) { cast(D))); } +static void handleCFIUncheckedCalleeAttr(Sema &S, Decl *D, + const ParsedAttr &Attrs) { + if (hasDeclarator(D)) + return; + + if (!isa(D)) { + S.Diag(Attrs.getLoc(), diag::warn_attribute_wrong_decl_type) + << Attrs << Attrs.isRegularKeywordAttribute() + << ExpectedFunctionOrMethod; + return; + } + + D->addAttr(::new (S.Context) CFIUncheckedCalleeAttr(S.Context, Attrs)); +} + static void handleNoBuiltinAttr(Sema &S, Decl *D, const ParsedAttr &AL) { static constexpr const StringRef kWildcard = "*"; @@ -7115,6 +7130,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL, case ParsedAttr::AT_NoBuiltin: handleNoBuiltinAttr(S, D, AL); break; + case ParsedAttr::AT_CFIUncheckedCallee: + handleCFIUncheckedCalleeAttr(S, D, AL); + break; case ParsedAttr::AT_ExtVectorType: handleExtVectorTypeAttr(S, D, AL); break; diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 21bd7315e3dd4..d67e29d8d606e 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -9283,8 +9283,14 @@ static AssignConvertType checkPointerTypesForAssignment(Sema &S, return AssignConvertType::IncompatibleFunctionPointer; return AssignConvertType::IncompatiblePointer; } - if (!S.getLangOpts().CPlusPlus && S.IsFunctionConversion(ltrans, rtrans)) - return AssignConvertType::IncompatibleFunctionPointer; + bool DiscardingCFIUncheckedCallee, AddingCFIUncheckedCallee; + if (!S.getLangOpts().CPlusPlus && + S.IsFunctionConversion(ltrans, rtrans, &DiscardingCFIUncheckedCallee, + &AddingCFIUncheckedCallee)) { + // Allow conversions between CFIUncheckedCallee-ness. + if (!DiscardingCFIUncheckedCallee && !AddingCFIUncheckedCallee) + return AssignConvertType::IncompatibleFunctionPointer; + } if (IsInvalidCmseNSCallConversion(S, ltrans, rtrans)) return AssignConvertType::IncompatibleFunctionPointer; if (S.IsInvalidSMECallConversion(rtrans, ltrans)) @@ -12740,9 +12746,17 @@ QualType Sema::CheckCompareOperands(ExprResult &LHS, ExprResult &RHS, LangAS AddrSpaceR = RCanPointeeTy.getAddressSpace(); CastKind Kind = AddrSpaceL != AddrSpaceR ? CK_AddressSpaceConversion : CK_BitCast; + + const FunctionType *LFn = LCanPointeeTy->getAs(); + const FunctionType *RFn = RCanPointeeTy->getAs(); + bool LHSHasCFIUncheckedCallee = LFn && LFn->getCFIUncheckedCalleeAttr(); + bool RHSHasCFIUncheckedCallee = RFn && RFn->getCFIUncheckedCalleeAttr(); + bool ChangingCFIUncheckedCallee = + LHSHasCFIUncheckedCallee != RHSHasCFIUncheckedCallee; + if (LHSIsNull && !RHSIsNull) LHS = ImpCastExprToType(LHS.get(), RHSType, Kind); - else + else if (!ChangingCFIUncheckedCallee) RHS = ImpCastExprToType(RHS.get(), LHSType, Kind); } return computeResultTy(); diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index 7b08fb869ccca..84521a3f80ff4 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -6407,6 +6407,11 @@ QualType Sema::FindCompositePointerType(SourceLocation Loc, EPI1.ExtInfo = EPI1.ExtInfo.withNoReturn(Noreturn); EPI2.ExtInfo = EPI2.ExtInfo.withNoReturn(Noreturn); + bool CFIUncheckedCallee = + EPI1.CFIUncheckedCallee || EPI2.CFIUncheckedCallee; + EPI1.CFIUncheckedCallee = CFIUncheckedCallee; + EPI2.CFIUncheckedCallee = CFIUncheckedCallee; + // The result is nothrow if both operands are. SmallVector ExceptionTypeStorage; EPI1.ExceptionSpec = EPI2.ExceptionSpec = Context.mergeExceptionSpecs( diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index 390508a0fca31..66f84fc67b52f 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -1889,7 +1889,14 @@ bool Sema::TryFunctionConversion(QualType FromType, QualType ToType, return Changed; } -bool Sema::IsFunctionConversion(QualType FromType, QualType ToType) const { +bool Sema::IsFunctionConversion(QualType FromType, QualType ToType, + bool *DiscardingCFIUncheckedCallee, + bool *AddingCFIUncheckedCallee) const { + if (DiscardingCFIUncheckedCallee) + *DiscardingCFIUncheckedCallee = false; + if (AddingCFIUncheckedCallee) + *AddingCFIUncheckedCallee = false; + if (Context.hasSameUnqualifiedType(FromType, ToType)) return false; @@ -1944,9 +1951,34 @@ bool Sema::IsFunctionConversion(QualType FromType, QualType ToType) const { Changed = true; } + const auto *FromFPT = dyn_cast(FromFn); + const auto *ToFPT = dyn_cast(ToFn); + + if (FromFPT && ToFPT) { + if (FromFPT->hasCFIUncheckedCallee() && !ToFPT->hasCFIUncheckedCallee()) { + QualType NewTy = Context.getFunctionType( + FromFPT->getReturnType(), FromFPT->getParamTypes(), + FromFPT->getExtProtoInfo().withCFIUncheckedCallee(false)); + FromFPT = cast(NewTy.getTypePtr()); + FromFn = FromFPT; + Changed = true; + if (DiscardingCFIUncheckedCallee) + *DiscardingCFIUncheckedCallee = true; + } else if (!FromFPT->hasCFIUncheckedCallee() && + ToFPT->hasCFIUncheckedCallee()) { + QualType NewTy = Context.getFunctionType( + FromFPT->getReturnType(), FromFPT->getParamTypes(), + FromFPT->getExtProtoInfo().withCFIUncheckedCallee(true)); + FromFPT = cast(NewTy.getTypePtr()); + FromFn = FromFPT; + Changed = true; + if (AddingCFIUncheckedCallee) + *AddingCFIUncheckedCallee = true; + } + } + // Drop 'noexcept' if not present in target type. - if (const auto *FromFPT = dyn_cast(FromFn)) { - const auto *ToFPT = cast(ToFn); + if (FromFPT) { if (FromFPT->isNothrow() && !ToFPT->isNothrow()) { FromFn = cast( Context.getFunctionTypeWithExceptionSpec(QualType(FromFPT, 0), @@ -2510,12 +2542,15 @@ static bool IsStandardConversion(Sema &S, Expr* From, QualType ToType, SCS.setToType(2, FromType); - if (CanonFrom == CanonTo) - return true; - // If we have not converted the argument type to the parameter type, // this is a bad conversion sequence, unless we're resolving an overload in C. - if (S.getLangOpts().CPlusPlus || !InOverloadResolution) + // + // Permit conversions from a function without `cfi_unchecked_callee` to a + // function with `cfi_unchecked_callee`. + if (CanonFrom == CanonTo || S.AddingCFIUncheckedCallee(CanonFrom, CanonTo)) + return true; + + if ((S.getLangOpts().CPlusPlus || !InOverloadResolution)) return false; ExprResult ER = ExprResult{From}; diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp index 338b81fe89748..f863531580f38 100644 --- a/clang/lib/Sema/SemaType.cpp +++ b/clang/lib/Sema/SemaType.cpp @@ -155,6 +155,7 @@ static void diagnoseBadTypeAttribute(Sema &S, const ParsedAttr &attr, case ParsedAttr::AT_Blocking: \ case ParsedAttr::AT_Allocating: \ case ParsedAttr::AT_Regparm: \ + case ParsedAttr::AT_CFIUncheckedCallee: \ case ParsedAttr::AT_CmseNSCall: \ case ParsedAttr::AT_ArmStreaming: \ case ParsedAttr::AT_ArmStreamingCompatible: \ @@ -7814,6 +7815,27 @@ static bool handleFunctionTypeAttr(TypeProcessingState &state, ParsedAttr &attr, return true; } + if (attr.getKind() == ParsedAttr::AT_CFIUncheckedCallee) { + // Delay if this is not a prototyped function type. + if (!unwrapped.isFunctionType()) + return false; + + if (!unwrapped.get()->isFunctionProtoType()) { + S.Diag(attr.getLoc(), diag::warn_attribute_wrong_decl_type) + << attr << attr.isRegularKeywordAttribute() + << ExpectedFunctionWithProtoType; + attr.setInvalid(); + return true; + } + + const auto *FPT = unwrapped.get()->getAs(); + type = S.Context.getFunctionType( + FPT->getReturnType(), FPT->getParamTypes(), + FPT->getExtProtoInfo().withCFIUncheckedCallee(true)); + type = unwrapped.wrap(S, cast(type.getTypePtr())); + return true; + } + if (attr.getKind() == ParsedAttr::AT_CmseNSCall) { // Delay if this is not a function type. if (!unwrapped.isFunctionType()) diff --git a/clang/test/CodeGen/cfi-unchecked-callee-attribute-member-function.cpp b/clang/test/CodeGen/cfi-unchecked-callee-attribute-member-function.cpp new file mode 100644 index 0000000000000..637b5201ad6b4 --- /dev/null +++ b/clang/test/CodeGen/cfi-unchecked-callee-attribute-member-function.cpp @@ -0,0 +1,53 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fsanitize=cfi-mfcall -o - %s -fvisibility=hidden | FileCheck %s + +#define CFI_UNCHECKED_CALLEE __attribute__((cfi_unchecked_callee)) + +class A {}; + +// CHECK-LABEL: _Z14MemberFuncCallP1AMS_FvvE +void MemberFuncCall(A *s, void (A::*p)()) { + // CHECK: memptr.virtual: + // CHECK-NEXT: [[VTABLE:%.*]] = load ptr, ptr {{.*}}, align 8 + // CHECK-NEXT: [[OFFSET:%.*]] = sub i64 %memptr.ptr, 1 + // CHECK-NEXT: [[FUNC:%.*]] = getelementptr i8, ptr [[VTABLE]], i64 [[OFFSET]] + // CHECK-NEXT: [[VALID:%.*]] = call i1 @llvm.type.test(ptr [[FUNC]], metadata !"_ZTSM1AFvvE.virtual") + // CHECK-NEXT: [[FUNC:%.*]] = getelementptr i8, ptr [[VTABLE]], i64 [[OFFSET]] + // CHECK-NEXT: %memptr.virtualfn = load ptr, ptr [[FUNC]], align 8 + // CHECK-NEXT: {{.*}}= call i1 @llvm.type.test(ptr [[VTABLE]], metadata !"all-vtables") + // CHECK-NEXT: br i1 [[VALID]], label %[[CONT:.*]], label %handler.cfi_check_fail{{.*}} + + // CHECK: [[CONT]]: + // CHECK-NEXT: br label %memptr.end + + // CHECK: memptr.nonvirtual: + // CHECK-NEXT: %memptr.nonvirtualfn = inttoptr i64 %memptr.ptr to ptr + // CHECK-NEXT: [[VALID:%.*]] = call i1 @llvm.type.test(ptr %memptr.nonvirtualfn, metadata !"_ZTSM1AFvvE") + // CHECK-NEXT: [[VALID2:%.*]] = or i1 false, [[VALID]] + // CHECK-NEXT: br i1 [[VALID2]], label %[[CONT2:.*]], label %handler.cfi_check_fail{{.*}} + + // CHECK: [[CONT2]]: + // CHECK-NEXT: br label %memptr.end + + // CHECK: memptr.end: + // CHECK-NEXT: {{.*}} = phi ptr [ %memptr.virtualfn, %[[CONT]] ], [ %memptr.nonvirtualfn, %[[CONT2]] ] + (s->*p)(); +} + +// CHECK-LABEL: _Z19MemberFuncCallNoCFIP1AMS_FvvE +// CHECK-NOT: llvm.type.test +void MemberFuncCallNoCFI(A *s, void (CFI_UNCHECKED_CALLEE A::*p)()) { + // CHECK: memptr.virtual: + // CHECK-NEXT: [[VTABLE:%.*]] = load ptr, ptr {{.*}}, align 8 + // CHECK-NEXT: [[OFFSET:%.*]] = sub i64 %memptr.ptr, 1 + // CHECK-NEXT: [[FUNC:%.*]] = getelementptr i8, ptr [[VTABLE]], i64 [[OFFSET]] + // CHECK-NEXT: %memptr.virtualfn = load ptr, ptr [[FUNC]], align 8 + // CHECK-NEXT: br label %memptr.end + + // CHECK: memptr.nonvirtual: + // CHECK-NEXT: %memptr.nonvirtualfn = inttoptr i64 %memptr.ptr to ptr + // CHECK-NEXT: br label %memptr.end + + // CHECK: memptr.end: + // CHECK-NEXT: {{.*}} = phi ptr [ %memptr.virtualfn, %memptr.virtual ], [ %memptr.nonvirtualfn, %memptr.nonvirtual ] + (s->*p)(); +} diff --git a/clang/test/CodeGen/cfi-unchecked-callee-attribute.cpp b/clang/test/CodeGen/cfi-unchecked-callee-attribute.cpp new file mode 100644 index 0000000000000..feb7c9c30a219 --- /dev/null +++ b/clang/test/CodeGen/cfi-unchecked-callee-attribute.cpp @@ -0,0 +1,77 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fsanitize=cfi-icall -o - %s | FileCheck %s + +#define CFI_UNCHECKED_CALLEE __attribute__((cfi_unchecked_callee)) + +void unchecked(void) CFI_UNCHECKED_CALLEE {} + +/// All references to unchecked function with `cfi_unchecked_callee` should have the `cfi_unchecked_callee` wrapper. +// CHECK: @checked = global ptr no_cfi @_Z9uncheckedv +void (*checked)(void) = unchecked; + +// CHECK: @unchecked2 = global ptr no_cfi @_Z9uncheckedv +void (CFI_UNCHECKED_CALLEE *unchecked2)(void) = unchecked; + +// CHECK: @checked2 = global ptr no_cfi @_Z9uncheckedv +constexpr void (CFI_UNCHECKED_CALLEE *unchecked_constexpr)(void) = unchecked; +void (*checked2)(void) = unchecked_constexpr; + +/// Note we still reference the `no_cfi` function rather than the jump table entry. +/// The explicit cast will only silence the warning. +// CHECK: @checked_explicit_cast = global ptr no_cfi @_Z9uncheckedv +void (*checked_explicit_cast)(void) = (void (*)(void))unchecked; + +// CHECK: @checked_array = global [3 x ptr] [ptr no_cfi @_Z9uncheckedv, ptr no_cfi @_Z9uncheckedv, ptr no_cfi @_Z9uncheckedv] +void (*checked_array[])(void) = { + unchecked, + (void (*)(void))unchecked, + reinterpret_cast(unchecked), +}; + +void func_accepting_checked(void (*p)(void)) {} + +// CHECK-LABEL: _Z9InvokeCFIv +void InvokeCFI() { + // CHECK: %0 = load ptr, ptr @checked, align 8 + // CHECK: %1 = call i1 @llvm.type.test(ptr %0, metadata !"_ZTSFvvE") + checked(); +} + +// CHECK-LABEL: _Z11InvokeNoCFIv +void InvokeNoCFI() { + // CHECK: %0 = load ptr, ptr @unchecked2, align 8 + // CHECK: call void %0() + unchecked2(); +} + +struct A { + void unchecked_method() CFI_UNCHECKED_CALLEE {} + virtual void unchecked_virtual_method() CFI_UNCHECKED_CALLEE {} + static void unchecked_static_method() CFI_UNCHECKED_CALLEE {} + int unchecked_const_method() const CFI_UNCHECKED_CALLEE { return 0; } + int unchecked_const_method_int_arg(int n) const CFI_UNCHECKED_CALLEE { return 0; } +}; + +void h(void) { + // CHECK: store ptr no_cfi @_Z9uncheckedv, ptr %unchecked_local + void (*unchecked_local)(void) = unchecked; + + // CHECK: call void @_Z22func_accepting_checkedPFvvE(ptr noundef no_cfi @_Z9uncheckedv) + func_accepting_checked(unchecked); + + // CHECK: [[B:%.*]] = load ptr, ptr @checked + // CHECK-NEXT: call void @_Z22func_accepting_checkedPFvvE(ptr noundef [[B]]) + func_accepting_checked(checked); + + // CHECK: store { i64, i64 } { i64 ptrtoint (ptr no_cfi @_ZN1A16unchecked_methodEv to i64), i64 0 }, ptr %A1 + auto A1 = &A::unchecked_method; + /// Storing unchecked virtual function pointer stores an offset instead. This is part of the + /// normal Itanium C++ ABI, but let's make sure we don't change anything. + // CHECK: store { i64, i64 } { i64 1, i64 0 }, ptr %A2 + auto A2 = &A::unchecked_virtual_method; + // CHECK: store ptr no_cfi @_ZN1A23unchecked_static_methodEv, ptr %A3 + auto A3 = &A::unchecked_static_method; + // CHECK: store { i64, i64 } { i64 ptrtoint (ptr no_cfi @_ZNK1A22unchecked_const_methodEv to i64), i64 0 }, ptr %A4 + auto A4 = (int(CFI_UNCHECKED_CALLEE A::*)() const)(&A::unchecked_const_method); + // CHECK: store { i64, i64 } { i64 ptrtoint (ptr no_cfi @_ZNK1A30unchecked_const_method_int_argEi to i64), i64 0 }, ptr %A5 + auto A5 = (int(CFI_UNCHECKED_CALLEE A::*)(int) const)(&A::unchecked_const_method_int_arg); +} diff --git a/clang/test/Frontend/cfi-unchecked-callee-attribute-n3037.c b/clang/test/Frontend/cfi-unchecked-callee-attribute-n3037.c new file mode 100644 index 0000000000000..ae5c14096f837 --- /dev/null +++ b/clang/test/Frontend/cfi-unchecked-callee-attribute-n3037.c @@ -0,0 +1,31 @@ +// RUN: %clang_cc1 -Wall -Wno-unused -Wno-uninitialized -verify -std=c17 %s +// RUN: %clang_cc1 -Wall -Wno-unused -Wno-uninitialized -verify -std=c23 %s + +#define CFI_UNCHECKED_CALLEE __attribute__((cfi_unchecked_callee)) + +#if __STDC_VERSION__ >= 202311L +// expected-no-diagnostics +#endif + +#if __STDC_VERSION__ < 202311L +// expected-note@+2 2 {{previous definition is here}} +#endif +struct field_attr_test { + void (CFI_UNCHECKED_CALLEE *func)(void); +}; + +#if __STDC_VERSION__ < 202311L +// expected-error@+2{{redefinition of 'field_attr_test'}} +#endif +struct field_attr_test { + void (CFI_UNCHECKED_CALLEE *func)(void); +}; + +typedef void (CFI_UNCHECKED_CALLEE func_t)(void); + +#if __STDC_VERSION__ < 202311L +// expected-error@+2{{redefinition of 'field_attr_test'}} +#endif +struct field_attr_test { + func_t *func; +}; diff --git a/clang/test/Frontend/cfi-unchecked-callee-attribute.c b/clang/test/Frontend/cfi-unchecked-callee-attribute.c new file mode 100644 index 0000000000000..889c106c3ccf2 --- /dev/null +++ b/clang/test/Frontend/cfi-unchecked-callee-attribute.c @@ -0,0 +1,71 @@ +// RUN: %clang_cc1 -Wall -Wno-unused -Wno-uninitialized -verify %s + +#define CFI_UNCHECKED_CALLEE __attribute__((cfi_unchecked_callee)) + +void unchecked(void) CFI_UNCHECKED_CALLEE {} +void checked(void) {} + +void (*checked_ptr)(void) = unchecked; // expected-warning{{implicit conversion from 'void (void) __attribute__((cfi_unchecked_callee))' to 'void (*)(void)' discards 'cfi_unchecked_callee' attribute}} +void (CFI_UNCHECKED_CALLEE *unchecked_ptr)(void) = unchecked; +void (CFI_UNCHECKED_CALLEE *from_normal)(void) = checked; +void (CFI_UNCHECKED_CALLEE *c_no_function_decay)(void) = &unchecked; + +typedef void (CFI_UNCHECKED_CALLEE unchecked_func_t)(void); +typedef void (checked_func_t)(void); +typedef void (CFI_UNCHECKED_CALLEE *cfi_unchecked_func_ptr_t)(void); +typedef void (*checked_func_ptr_t)(void); +checked_func_t *cfi_func = unchecked; // expected-warning{{implicit conversion from 'void (void) __attribute__((cfi_unchecked_callee))' to 'void (*)(void)' discards 'cfi_unchecked_callee' attribute}} +unchecked_func_t *unchecked_func = unchecked; + +void CFI_UNCHECKED_CALLEE after_ret_type(void); +CFI_UNCHECKED_CALLEE void before_ret_type(void); +void (* CFI_UNCHECKED_CALLEE after_name)(void); + +void UsageOnImproperTypes() { + int CFI_UNCHECKED_CALLEE i; // expected-warning{{'cfi_unchecked_callee' only applies to function types; type here is 'int'}} + + /// The attribute must be used only on functions with prototypes. The omission of `void` means it is not prototyped. + void (CFI_UNCHECKED_CALLEE *noproto)(void); // expecteed-warning{{'cfi_unchecked_callee' attribute only applies to non-K&R-style functions}} +} + +/// Explicit casts suppress the warning. +void CheckCasts() { + void (*should_warn)(void) = unchecked; // expected-warning{{implicit conversion from 'void (void) __attribute__((cfi_unchecked_callee))' to 'void (*)(void)' discards 'cfi_unchecked_callee' attribute}} + + void (*no_warn_c_style_cast)(void) = (void (*)(void))unchecked; + + struct B {} CFI_UNCHECKED_CALLEE b; // expected-warning{{'cfi_unchecked_callee' attribute only applies to functions and methods}} + struct CFI_UNCHECKED_CALLEE C {} c; // expected-warning{{'cfi_unchecked_callee' attribute only applies to functions and methods}} + CFI_UNCHECKED_CALLEE struct D {} d; // expected-warning{{'cfi_unchecked_callee' only applies to function types; type here is 'struct D'}} + + void *ptr2 = (void *)unchecked; +} + +int checked_arg_func(checked_func_t *checked_func); + +void CheckDifferentConstructions() { + void (CFI_UNCHECKED_CALLEE *arr[10])(void); + void (*cfi_elem)(void) = arr[1]; // expected-warning{{implicit conversion from 'void (*)(void) __attribute__((cfi_unchecked_callee))' to 'void (*)(void)' discards 'cfi_unchecked_callee' attribute}} + void (CFI_UNCHECKED_CALLEE *cfi_unchecked_elem)(void) = arr[1]; + + int invoke = checked_arg_func(unchecked); // expected-warning{{implicit conversion from 'void (void) __attribute__((cfi_unchecked_callee))' to 'void (*)(void)' discards 'cfi_unchecked_callee' attribute}} +} + +checked_func_t *returning_checked_func() { + return unchecked; // expected-warning{{implicit conversion from 'void (void) __attribute__((cfi_unchecked_callee))' to 'void (*)(void)' discards 'cfi_unchecked_callee' attribute}} +} + +void no_args(void) __attribute__((cfi_unchecked_callee(10))); // expected-error{{'cfi_unchecked_callee' attribute takes no arguments}} + +void Comparisons() { + /// Let's be able to compare checked and unchecked pointers without warnings. + unchecked == checked_ptr; + checked_ptr == unchecked; + unchecked == unchecked_ptr; + unchecked != checked_ptr; + checked_ptr != unchecked; + unchecked != unchecked_ptr; + + (void (*)(void))unchecked == checked_ptr; + checked_ptr == (void (*)(void))unchecked; +} diff --git a/clang/test/Frontend/cfi-unchecked-callee-attribute.cpp b/clang/test/Frontend/cfi-unchecked-callee-attribute.cpp new file mode 100644 index 0000000000000..f2c4e9e2f8890 --- /dev/null +++ b/clang/test/Frontend/cfi-unchecked-callee-attribute.cpp @@ -0,0 +1,235 @@ +// RUN: %clang_cc1 -Wall -Wno-unused -Wno-uninitialized -std=c++2b -verify %s + +#define CFI_UNCHECKED_CALLEE __attribute__((cfi_unchecked_callee)) + +void unchecked(void) CFI_UNCHECKED_CALLEE {} +void checked(void) {} + +void (*checked_ptr)(void) = unchecked; // expected-warning{{implicit conversion from 'void () __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 'cfi_unchecked_callee' attribute}} +void (CFI_UNCHECKED_CALLEE *unchecked_ptr)(void) = unchecked; +void (CFI_UNCHECKED_CALLEE *from_normal)(void) = checked; +void (CFI_UNCHECKED_CALLEE *c_no_function_decay)(void) = &unchecked; +void (CFI_UNCHECKED_CALLEE *arr[10])(void); +void (*cfi_elem)(void) = arr[1]; // expected-warning{{implicit conversion from 'void (*)() __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 'cfi_unchecked_callee' attribute}} +void (CFI_UNCHECKED_CALLEE *cfi_unchecked_elem)(void) = arr[1]; +void (CFI_UNCHECKED_CALLEE &ref)(void) = unchecked; +void (CFI_UNCHECKED_CALLEE &ref2)(void) = *unchecked; +void (&ref_cfi_checked)(void) = unchecked; // expected-warning{{implicit conversion from 'void () __attribute__((cfi_unchecked_callee))' to 'void ()' discards 'cfi_unchecked_callee' attribute}} +void (&ref_cfi_checked2)(void) = *unchecked; // expected-warning{{implicit conversion from 'void () __attribute__((cfi_unchecked_callee))' to 'void ()' discards 'cfi_unchecked_callee' attribute}} + +void (CFI_UNCHECKED_CALLEE *unchecked_from_deref)(void) = &*unchecked; +void (*checked_from_deref)(void) = &*unchecked; // expected-warning{{implicit conversion from 'void (*)() __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 'cfi_unchecked_callee' attribute}} + +typedef void (CFI_UNCHECKED_CALLEE unchecked_func_t)(void); +typedef void (checked_func_t)(void); +typedef void (CFI_UNCHECKED_CALLEE *unchecked_func_ptr_t)(void); +typedef void (*checked_func_ptr_t)(void); +checked_func_t *checked_func = unchecked; // expected-warning{{implicit conversion from 'void () __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 'cfi_unchecked_callee' attribute}} +unchecked_func_t *unchecked_func = unchecked; + +void CFI_UNCHECKED_CALLEE before_func(void); +CFI_UNCHECKED_CALLEE void before_return_type(void); +void (* CFI_UNCHECKED_CALLEE after_name)(void); + +void UsageOnImproperTypes() { + int CFI_UNCHECKED_CALLEE i; // expected-warning{{'cfi_unchecked_callee' only applies to function types; type here is 'int'}} +} + +/// Explicit casts suppress the warning. +void CheckCasts() { + void (*should_warn)(void) = unchecked; // expected-warning{{implicit conversion from 'void () __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 'cfi_unchecked_callee' attribute}} + + void (*no_warn_c_style_cast)(void) = (void (*)(void))unchecked; + void (*no_warn_static_cast)(void) = static_cast(unchecked); + void (*no_warn_reinterpret_cast)(void) = reinterpret_cast(unchecked); + unsigned long long ull = (unsigned long long)unchecked; + + struct A {}; + void (CFI_UNCHECKED_CALLEE A::*cfi_unchecked_member_ptr)(void); + void (A::*member_ptr)(void) = cfi_unchecked_member_ptr; // expected-warning{{implicit conversion from 'void (A::*)() __attribute__((cfi_unchecked_callee))' to 'void (A::*)()' discards 'cfi_unchecked_callee' attribute}} + + struct B {} CFI_UNCHECKED_CALLEE b; // expected-warning{{'cfi_unchecked_callee' attribute only applies to functions and methods}} + struct CFI_UNCHECKED_CALLEE C {} c; // expected-warning{{'cfi_unchecked_callee' attribute only applies to functions and methods}} + CFI_UNCHECKED_CALLEE struct D {} d; // expected-warning{{'cfi_unchecked_callee' only applies to function types; type here is 'struct D'}} + + void *ptr2 = (void *)unchecked; +} + +void CheckDifferentConstructions() { + checked_func_t *checked_func(unchecked_func); // expected-warning{{implicit conversion from 'void (*)() __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 'cfi_unchecked_callee' attribute}} + new (checked_func_t *)(unchecked_func); // expected-warning{{implicit conversion from 'void (*)() __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 'cfi_unchecked_callee' attribute}} + struct S { + checked_func_t *checked_func; + + S(unchecked_func_t *unchecked_func) : checked_func(unchecked_func) {} // expected-warning{{implicit conversion from 'void (*)() __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 'cfi_unchecked_callee' attribute}} + }; + + checked_func_t *checked_func2{unchecked_func}; // expected-warning{{implicit conversion from 'void (*)() __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 'cfi_unchecked_callee' attribute}} + checked_ptr = checked_func_ptr_t(unchecked); + + auto checked_auto = checked; + auto unchecked_auto = unchecked; + unchecked_ptr = checked_auto; + unchecked_ptr = unchecked_auto; + checked_ptr = checked_auto; + checked_ptr = unchecked_auto; // expected-warning{{implicit conversion from 'void (*)() __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 'cfi_unchecked_callee' attribute}} +} + +checked_func_t *returning_checked_func() { + return unchecked; // expected-warning{{implicit conversion from 'void () __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 'cfi_unchecked_callee' attribute}} +} + +int checked_arg_func(checked_func_t *checked_func); +int invoke = checked_arg_func(unchecked); // expected-warning{{implicit conversion from 'void () __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 'cfi_unchecked_callee' attribute}} + +template +struct S { + S(T *ptr) {} +}; +S s(checked); +S s2(unchecked); +S s3(checked); +S s4(unchecked); // expected-warning{{implicit conversion from 'void () __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 'cfi_unchecked_callee' attribute}} +S s5(checked); +S s6(unchecked); + +template +struct is_same { + static constexpr bool value = false; +}; +template +struct is_same { + static constexpr bool value = true; +}; + +template +struct ExpectingCFIUncheckedCallee { + static_assert(is_same::value); + ExpectingCFIUncheckedCallee(T *) {} + ExpectingCFIUncheckedCallee() = default; +}; +ExpectingCFIUncheckedCallee expecting; +ExpectingCFIUncheckedCallee expecting2(unchecked); + +void no_args() __attribute__((cfi_unchecked_callee(10))); // expected-error{{'cfi_unchecked_callee' attribute takes no arguments}} + +void bracket_cfi_unchecked(void) [[clang::cfi_unchecked_callee]] {} + +void BracketNotation() { + checked_ptr = bracket_cfi_unchecked; // expected-warning{{implicit conversion from 'void () __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 'cfi_unchecked_callee' attribute}} +} + +void Comparisons() { + /// Let's be able to compare checked and unchecked pointers without warnings. + unchecked == checked_ptr; + checked_ptr == unchecked; + unchecked == unchecked_ptr; + unchecked != checked_ptr; + checked_ptr != unchecked; + unchecked != unchecked_ptr; + + (void (*)(void))unchecked == checked_ptr; + checked_ptr == (void (*)(void))unchecked; + + struct S { + typedef void CB() CFI_UNCHECKED_CALLEE; + constexpr bool operator==(const S &other) const { + return cb == other.cb; + } + CB *cb; + }; +} + +/// Type aliasing +typedef void (BaseType)(void); +using WithoutAttr = BaseType; +using WithAttr = __attribute__((cfi_unchecked_callee)) BaseType; + +WithoutAttr *checked_func_alias = unchecked; // expected-warning{{implicit conversion from 'void () __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 'cfi_unchecked_callee' attribute}} +WithAttr *unchecked_func_allias = unchecked; +WithoutAttr *checked_func_alias2 = checked; +WithAttr *unchecked_func_alias2 = checked; + +using MyType = WithAttr; // expected-note{{previous definition is here}} +using MyType = WithoutAttr; // expected-error{{type alias redefinition with different types ('WithoutAttr' (aka 'void ()') vs 'WithAttr' (aka 'void () __attribute__((cfi_unchecked_callee))'))}} + +void MemberFunctionPointer() { + struct A { + void unchecked() CFI_UNCHECKED_CALLEE {} + virtual void unchecked_virtual() CFI_UNCHECKED_CALLEE {} + static void unchecked_static() CFI_UNCHECKED_CALLEE {} + void unchecked_explicit_this(this A&) CFI_UNCHECKED_CALLEE {} + int operator+=(int i) CFI_UNCHECKED_CALLEE { return i; } + + void checked() {} + virtual void checked_virtual() {} + static void checked_static() {} + void checked_explicit_this(this A&) {} + int operator-=(int i) { return i; } + }; + + void (CFI_UNCHECKED_CALLEE A::*unchecked)() = &A::unchecked; + unchecked = &A::unchecked_virtual; + void (CFI_UNCHECKED_CALLEE *unchecked_explicit_this)(A&) = &A::unchecked_explicit_this; + void (CFI_UNCHECKED_CALLEE *unchecked_static)() = &A::unchecked_static; + int (CFI_UNCHECKED_CALLEE A::*unchecked_overloaded)(int) = &A::operator+=; + + void (A::*checked)() = &A::unchecked; // expected-warning{{implicit conversion from 'void (A::*)() __attribute__((cfi_unchecked_callee))' to 'void (A::*)()' discards 'cfi_unchecked_callee' attribute}} + checked = &A::unchecked_virtual; // expected-warning{{implicit conversion from 'void (A::*)() __attribute__((cfi_unchecked_callee))' to 'void (A::*)()' discards 'cfi_unchecked_callee' attribute}} + void (*checked_explicit_this)(A&) = &A::unchecked_explicit_this; // expected-warning{{implicit conversion from 'void (*)(A &) __attribute__((cfi_unchecked_callee))' to 'void (*)(A &)' discards 'cfi_unchecked_callee' attribute}} + void (*checked_static)() = &A::unchecked_static; // expected-warning{{implicit conversion from 'void (*)() __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 'cfi_unchecked_callee' attribute}} + int (A::*checked_overloaded)(int) = &A::operator+=; // expected-warning{{implicit conversion from 'int (A::*)(int) __attribute__((cfi_unchecked_callee))' to 'int (A::*)(int)' discards 'cfi_unchecked_callee' attribute}} + + unchecked = &A::checked; + unchecked = &A::checked_virtual; + unchecked_explicit_this = &A::checked_explicit_this; + unchecked_static = &A::checked_static; + unchecked_overloaded = &A::operator-=; + + checked = &A::checked; + checked = &A::checked_virtual; + checked_explicit_this = &A::checked_explicit_this; + checked_static = &A::checked_static; + checked_overloaded = &A::operator-=; + + typedef void (CFI_UNCHECKED_CALLEE A::*WithAttr)(); + typedef void (CFI_UNCHECKED_CALLEE A::*WithoutAttr)(); + using WithoutAttr = decltype(unchecked); +} + +void lambdas() { + auto unchecked_lambda = [](void) CFI_UNCHECKED_CALLEE -> void {}; + auto checked_lambda = [](void) -> void {}; + void (CFI_UNCHECKED_CALLEE *unchecked_func)(void) = unchecked_lambda; + unchecked_func = checked_lambda; + void (*checked_func)(void) = unchecked_lambda; // expected-warning{{implicit conversion from 'void (*)() __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 'cfi_unchecked_callee' attribute}} + checked_func = checked_lambda; + + auto capture_by_value = [unchecked_lambda, checked_lambda]() { + void (CFI_UNCHECKED_CALLEE *unchecked_func)(void) = unchecked_lambda; + unchecked_func = checked_lambda; + void (*checked_func)(void) = unchecked_lambda; // expected-warning{{implicit conversion from 'void (*)() __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 'cfi_unchecked_callee' attribute}} + checked_func = checked_lambda; + }; + + auto capture_by_ref = [&unchecked_lambda, &checked_lambda]() { + void (CFI_UNCHECKED_CALLEE *unchecked_func)(void) = unchecked_lambda; + unchecked_func = checked_lambda; + void (*checked_func)(void) = unchecked_lambda; // expected-warning{{implicit conversion from 'void (*)() __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 'cfi_unchecked_callee' attribute}} + checked_func = checked_lambda; + }; + + auto capture_all_by_value = [=]() { + void (CFI_UNCHECKED_CALLEE *unchecked_func)(void) = unchecked_lambda; + unchecked_func = checked_lambda; + void (*checked_func)(void) = unchecked_lambda; // expected-warning{{implicit conversion from 'void (*)() __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 'cfi_unchecked_callee' attribute}} + checked_func = checked_lambda; + }; + + auto capture_all_by_ref = [&]() { + void (CFI_UNCHECKED_CALLEE *unchecked_func)(void) = unchecked_lambda; + unchecked_func = checked_lambda; + void (*checked_func)(void) = unchecked_lambda; // expected-warning{{implicit conversion from 'void (*)() __attribute__((cfi_unchecked_callee))' to 'void (*)()' discards 'cfi_unchecked_callee' attribute}} + checked_func = checked_lambda; + }; +}