diff --git a/clang/include/clang/AST/DeclBase.h b/clang/include/clang/AST/DeclBase.h --- a/clang/include/clang/AST/DeclBase.h +++ b/clang/include/clang/AST/DeclBase.h @@ -1943,8 +1943,12 @@ bool isNamespace() const { return getDeclKind() == Decl::Namespace; } + // Determines if the namespace is exactly `std`. bool isStdNamespace() const; + // Determines if the namespace is `std`, or a namespace nested in `std`. + bool isStdNestedNamespace() const; + bool isInlineNamespace() const; /// Determines whether this context is dependent on a 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 @@ -1957,6 +1957,13 @@ let Documentation = [AllocAlignDocs]; } +def NoAddress : InheritableAttr { + let Spellings = [Clang<"no_address">]; + let Subjects = SubjectList<[Function], ErrorDiag>; + let Documentation = [NoAddressDocs]; + let SimpleHandler = 0; +} + def NoReturn : InheritableAttr { let Spellings = [GCC<"noreturn">, Declspec<"noreturn">]; // FIXME: Does GCC allow this on the function instead? 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 @@ -429,16 +429,16 @@ let Category = DocCatStmt; let Content = [{ If a statement is marked ``nomerge`` and contains call expressions, those call -expressions inside the statement will not be merged during optimization. This +expressions inside the statement will not be merged during optimization. This attribute can be used to prevent the optimizer from obscuring the source location of certain calls. For example, it will prevent tail merging otherwise identical code sequences that raise an exception or terminate the program. Tail merging normally reduces the precision of source location information, making stack traces less useful for debugging. This attribute gives the user control -over the tradeoff between code size and debug information precision. +over the tradeoff between code size and debug information precision. -``nomerge`` attribute can also be used as function attribute to prevent all -calls to the specified function from merging. It has no effect on indirect +``nomerge`` attribute can also be used as function attribute to prevent all +calls to the specified function from merging. It has no effect on indirect calls. }]; } @@ -3561,7 +3561,7 @@ with pointers in the C family of languages. The various nullability attributes indicate whether a particular pointer can be null or not, which makes APIs more expressive and can help static analysis tools identify bugs involving null -pointers. Clang supports several kinds of nullability attributes: the +pointers. Clang supports several kinds of nullability attributes: the ``nonnull`` and ``returns_nonnull`` attributes indicate which function or method parameters and result types can never be null, while nullability type qualifiers indicate which pointer types can be null (``_Nullable``) or cannot @@ -3737,7 +3737,7 @@ The ``returns_nonnull`` attribute implies that returning a null pointer is undefined behavior, which the optimizer may take advantage of. The ``_Nonnull`` type qualifier indicates that a pointer cannot be null in a more general manner -(because it is part of the type system) and does not imply undefined behavior, +(because it is part of the type system) and does not imply undefined behavior, making it more widely applicable }]; } @@ -5578,15 +5578,15 @@ let Content = [{ Code can indicate CFG checks are not wanted with the ``__declspec(guard(nocf))`` attribute. This directs the compiler to not insert any CFG checks for the entire -function. This approach is typically used only sparingly in specific situations -where the programmer has manually inserted "CFG-equivalent" protection. The -programmer knows that they are calling through some read-only function table -whose address is obtained through read-only memory references and for which the -index is masked to the function table limit. This approach may also be applied -to small wrapper functions that are not inlined and that do nothing more than -make a call through a function pointer. Since incorrect usage of this directive -can compromise the security of CFG, the programmer must be very careful using -the directive. Typically, this usage is limited to very small functions that +function. This approach is typically used only sparingly in specific situations +where the programmer has manually inserted "CFG-equivalent" protection. The +programmer knows that they are calling through some read-only function table +whose address is obtained through read-only memory references and for which the +index is masked to the function table limit. This approach may also be applied +to small wrapper functions that are not inlined and that do nothing more than +make a call through a function pointer. Since incorrect usage of this directive +can compromise the security of CFG, the programmer must be very careful using +the directive. Typically, this usage is limited to very small functions that only call one function. `Control Flow Guard documentation ` @@ -5961,3 +5961,16 @@ - ``enforce_tcb_leaf(Name)`` indicates that this function is a part of the TCB named ``Name`` }]; } + +def NoAddressDocs : Documentation { + let Category = DocCatFunction; + let Content = [{ + The ``no_address`` attribute is an opt-in mechanism for the C++ standard library to prevent users + from taking the address of standard library functions, function templates, and member functions. + + Although opt-in, the attribute poisons the entire overload set, meaning that only one overload + needs the attribute per set (although such usage is discouraged). Until there is confidence that + this attribute doesn't have many sharp edges, usage is restricted to only entities in namespace + ``std`` (and nested namespaces). + }]; +} diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -4366,6 +4366,11 @@ def err_addrof_function_disabled_by_enable_if_attr : Error< "cannot take address of function %0 because it has one or more " "non-tautological enable_if conditions">; +def err_no_address_std_only : Error< + "[[clang::no_address]] can only be used by the C++ standard library">; +def err_function_has_no_address : Error< + "cannot take address of '%0' because %select{functions|member functions}1 " + "in %2 are not addressable">; def err_addrof_function_constraints_not_satisfied : Error< "cannot take address of function %0 because its constraints are not " "satisfied">; diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -3207,7 +3207,6 @@ return false; } - MultiVersionKind FunctionDecl::getMultiVersionKind() const { if (hasAttr()) return MultiVersionKind::Target; diff --git a/clang/lib/AST/DeclBase.cpp b/clang/lib/AST/DeclBase.cpp --- a/clang/lib/AST/DeclBase.cpp +++ b/clang/lib/AST/DeclBase.cpp @@ -1135,6 +1135,16 @@ return II && II->isStr("std"); } +bool DeclContext::isStdNestedNamespace() const { + if (isStdNamespace()) + return true; + + if (!getParent()->getRedeclContext()->isTranslationUnit()) + return getParent()->getRedeclContext()->isStdNestedNamespace(); + + return false; +} + bool DeclContext::isDependentContext() const { if (isFileContext()) return false; 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 @@ -7614,6 +7614,15 @@ D->addAttr(AttrTy::Create(S.Context, Argument, AL)); } +static void handleNoAddressAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + DeclContext *C = D->getDeclContext(); + if (!C->isStdNestedNamespace()) { + S.Diag(AL.getLoc(), diag::err_no_address_std_only); + return; + } + D->addAttr(::new (S.Context) NoAddressAttr(S.Context, AL)); +} + template static AttrTy *mergeEnforceTCBAttrImpl(Sema &S, Decl *D, const AttrTy &AL) { // Check if the new redeclaration has different leaf-ness in the same TCB. @@ -8090,6 +8099,9 @@ case ParsedAttr::AT_InternalLinkage: handleInternalLinkageAttr(S, D, AL); break; + case ParsedAttr::AT_NoAddress: + handleNoAddressAttr(S, D, AL); + break; // Microsoft attributes: case ParsedAttr::AT_LayoutVersion: diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -12,7 +12,9 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/CXXInheritance.h" +#include "clang/AST/Decl.h" #include "clang/AST/DeclObjC.h" +#include "clang/AST/DeclTemplate.h" #include "clang/AST/DependenceFlags.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" @@ -34,6 +36,7 @@ #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallString.h" +#include "llvm/Support/Casting.h" #include #include @@ -1807,10 +1810,14 @@ // Function-to-pointer conversion (C++ 4.3). SCS.First = ICK_Function_To_Pointer; - if (auto *DRE = dyn_cast(From->IgnoreParenCasts())) - if (auto *FD = dyn_cast(DRE->getDecl())) - if (!S.checkAddressOfFunctionIsAvailable(FD)) + if (auto *DRE = dyn_cast(From->IgnoreParenCasts())) { + if (auto *FD = dyn_cast(DRE->getDecl())) { + if (!S.checkAddressOfFunctionIsAvailable(FD) || + FD->hasAttr()) { return false; + } + } + } // An lvalue of function type T can be converted to an rvalue of // type "pointer to T." The result is a pointer to the @@ -10197,6 +10204,17 @@ } } + if (FD->hasAttr()) { + if (Complain) { + if (!InOverloadResolution) { + S.Diag(Loc, diag::err_function_has_no_address) + << FD->getQualifiedNameAsString() << isa(FD) + << FD->getEnclosingNamespaceContext(); + } + } + return false; + } + auto I = llvm::find_if(FD->parameters(), [](const ParmVarDecl *P) { return P->hasAttr(); }); @@ -10214,6 +10232,7 @@ S.Diag(Loc, diag::err_address_of_function_with_pass_object_size_params) << FD << ParamNo; } + return false; } @@ -12180,6 +12199,27 @@ S.EraseUnwantedCUDAMatches(dyn_cast(S.CurContext), Matches); } + static bool HasNoAddressAttr(const NamedDecl *D) { + if (auto *FD = dyn_cast(D)) + return FD->hasAttr(); + if (auto *FTD = dyn_cast(D)) + if (auto *FD = FTD->getTemplatedDecl()) + return FD->hasAttr(); + return false; + } + + static bool IsMethod(const NamedDecl *D) { + if (auto *TFD = dyn_cast(D)) + return isa(TFD->getTemplatedDecl()); + return isa(D); + } + + UnresolvedLookupExpr *GetOverloadSet() const { + if (auto *E = dyn_cast(SourceExpr->IgnoreParenCasts())) + return dyn_cast(E->getSubExpr()); + return dyn_cast(SourceExpr->IgnoreParenCasts()); + } + public: void ComplainNoMatchesFound() const { assert(Matches.empty()); @@ -12241,6 +12281,24 @@ /*TakingAddress=*/true); } + bool AnyMatchHasNoAddress() const { + UnresolvedLookupExpr *OverloadSet = GetOverloadSet(); + assert(OverloadSet != nullptr); + return llvm::any_of(OverloadSet->decls(), HasNoAddressAttr); + } + + void ComplainMatchHasNoAddress() const { + auto *OverloadSet = GetOverloadSet(); + assert(OverloadSet != nullptr); + auto D = llvm::find_if(OverloadSet->decls(), HasNoAddressAttr); + assert(D != OverloadSet->decls_end()); + auto *C = (*D)->getDeclContext(); + + S.Diag(OvlExpr->getBeginLoc(), diag::err_function_has_no_address) + << D->getQualifiedNameAsString() << IsMethod(*D) + << C->getEnclosingNamespaceContext(); + } + bool hadMultipleCandidates() const { return (OvlExpr->getNumDecls() > 1); } int getNumMatches() const { return Matches.size(); } @@ -12285,13 +12343,14 @@ int NumMatches = Resolver.getNumMatches(); FunctionDecl *Fn = nullptr; bool ShouldComplain = Complain && !Resolver.hasComplained(); - if (NumMatches == 0 && ShouldComplain) { + if (Resolver.AnyMatchHasNoAddress() && ShouldComplain) + Resolver.ComplainMatchHasNoAddress(); + else if (NumMatches == 0 && ShouldComplain) { if (Resolver.IsInvalidFormOfPointerToMemberFunction()) Resolver.ComplainIsInvalidFormOfPointerToMemberFunction(); else Resolver.ComplainNoMatchesFound(); - } - else if (NumMatches > 1 && ShouldComplain) + } else if (NumMatches > 1 && ShouldComplain) Resolver.ComplainMultipleMatchesFound(); else if (NumMatches == 1) { Fn = Resolver.getMatchingFunctionDecl(); diff --git a/clang/test/SemaCXX/attr-no-address.cpp b/clang/test/SemaCXX/attr-no-address.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaCXX/attr-no-address.cpp @@ -0,0 +1,199 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s + +namespace std { + [[clang::no_address]] void one_overload(); + [[clang::no_address]] void two_overloads(); + [[clang::no_address]] void two_overloads(int); + + template + [[clang::no_address]] void many_overloads(T); + + struct type { + [[clang::no_address]] void one_overload(); + [[clang::no_address]] void two_overloads(); + [[clang::no_address]] void two_overloads() const; + + template + [[clang::no_address]] void many_overloads(T) &; + + template + [[clang::no_address]] void many_overloads(T) const&; + + template + [[clang::no_address]] void many_overloads(T) &&; + + template + [[clang::no_address]] void many_overloads(T) const&&; + + [[clang::no_address]] static void single_overload(); + [[clang::no_address]] static void double_overload(double); + [[clang::no_address]] static void double_overload(int); + + template + [[clang::no_address]] static void multi_overload(T); + }; + + namespace ranges { + [[clang::no_address]] void one_overload(); + [[clang::no_address]] void two_overloads(); + [[clang::no_address]] void two_overloads(int); + + template + [[clang::no_address]] void many_overloads(T); + } +} // namespace std + +void address_test() { + void (*fp1)() = &std::one_overload; + // expected-error@-1{{cannot take address of 'std::one_overload' because functions in namespace 'std' are not addressable}} + void (*fp2)() = std::one_overload; + // expected-error@-1{{cannot take address of 'std::one_overload' because functions in namespace 'std' are not addressable}} + void (*fp3)() = &std::two_overloads; + // expected-error@-1{{cannot take address of 'std::two_overloads' because functions in namespace 'std' are not addressable}} + void (*fp4)() = std::two_overloads; + // expected-error@-1{{cannot take address of 'std::two_overloads' because functions in namespace 'std' are not addressable}} + void (*fp5)(int) = &std::many_overloads; + // expected-error@-1{{cannot take address of 'std::many_overloads' because functions in namespace 'std' are not addressable}} + void (*fp6)(double) = std::many_overloads; + // expected-error@-1{{cannot take address of 'std::many_overloads' because functions in namespace 'std' are not addressable}} + void (*fp8)() = &std::ranges::one_overload; + // expected-error@-1{{cannot take address of 'std::ranges::one_overload' because functions in namespace 'std::ranges' are not addressable}} + void (*fp9)() = std::ranges::one_overload; + // expected-error@-1{{cannot take address of 'std::ranges::one_overload' because functions in namespace 'std::ranges' are not addressable}} + void (*fp10)() = &std::ranges::two_overloads; + // expected-error@-1{{cannot take address of 'std::ranges::two_overloads' because functions in namespace 'std::ranges' are not addressable}} + void (*fp11)() = std::ranges::two_overloads; + // expected-error@-1{{cannot take address of 'std::ranges::two_overloads' because functions in namespace 'std::ranges' are not addressable}} + void (*fp12)(int) = &std::ranges::many_overloads; + // expected-error@-1{{cannot take address of 'std::ranges::many_overloads' because functions in namespace 'std::ranges' are not addressable}} + void (*fp13)(double) = std::ranges::many_overloads; + // expected-error@-1{{cannot take address of 'std::ranges::many_overloads' because functions in namespace 'std::ranges' are not addressable}} + + void (&fr1)() = &std::one_overload; + // expected-error@-1{{cannot take address of 'std::one_overload' because functions in namespace 'std' are not addressable}} + void (*fr2)() = std::one_overload; + // expected-error@-1{{cannot take address of 'std::one_overload' because functions in namespace 'std' are not addressable}} + void (&fr3)() = &std::two_overloads; + // expected-error@-1{{cannot take address of 'std::two_overloads' because functions in namespace 'std' are not addressable}} + void (&fr4)() = std::two_overloads; + // expected-error@-1{{cannot take address of 'std::two_overloads' because functions in namespace 'std' are not addressable}} + void (&fr5)(int) = &std::many_overloads; + // expected-error@-1{{cannot take address of 'std::many_overloads' because functions in namespace 'std' are not addressable}} + void (&fr6)(double) = std::many_overloads; + // expected-error@-1{{cannot take address of 'std::many_overloads' because functions in namespace 'std' are not addressable}} + void (&fr8)() = &std::ranges::one_overload; + // expected-error@-1{{cannot take address of 'std::ranges::one_overload' because functions in namespace 'std::ranges' are not addressable}} + void (&fr9)() = std::ranges::one_overload; + // expected-error@-1{{cannot take address of 'std::ranges::one_overload' because functions in namespace 'std::ranges' are not addressable}} + void (&fr10)() = &std::ranges::two_overloads; + // expected-error@-1{{cannot take address of 'std::ranges::two_overloads' because functions in namespace 'std::ranges' are not addressable}} + void (&fr11)() = std::ranges::two_overloads; + // expected-error@-1{{cannot take address of 'std::ranges::two_overloads' because functions in namespace 'std::ranges' are not addressable}} + void (&fr12)(int) = &std::ranges::many_overloads; + // expected-error@-1{{cannot take address of 'std::ranges::many_overloads' because functions in namespace 'std::ranges' are not addressable}} + void (&fr13)(double) = std::ranges::many_overloads; + // expected-error@-1{{cannot take address of 'std::ranges::many_overloads' because functions in namespace 'std::ranges' are not addressable}} + + void (std::type::*mfp1)() = &std::type::one_overload; + // expected-error@-1{{cannot take address of 'std::type::one_overload' because member functions in namespace 'std' are not addressable}} + void (std::type::*mfp2)() = &std::type::two_overloads; + // expected-error@-1{{cannot take address of 'std::type::two_overloads' because member functions in namespace 'std' are not addressable}} + void (std::type::*mfp3)() const = &std::type::two_overloads; + // expected-error@-1{{cannot take address of 'std::type::two_overloads' because member functions in namespace 'std' are not addressable}} + void (std::type::*mfp4)(int) & = &std::type::many_overloads; + // expected-error@-1{{cannot take address of 'std::type::many_overloads' because member functions in namespace 'std' are not addressable}} + void (std::type::*mfp5)(int) const& = &std::type::many_overloads; + // expected-error@-1{{cannot take address of 'std::type::many_overloads' because member functions in namespace 'std' are not addressable}} + void (std::type::*mfp6)(int) && = &std::type::many_overloads; + // expected-error@-1{{cannot take address of 'std::type::many_overloads' because member functions in namespace 'std' are not addressable}} + void (std::type::*mfp7)(int) const&& = &std::type::many_overloads; + // expected-error@-1{{cannot take address of 'std::type::many_overloads' because member functions in namespace 'std' are not addressable}} + + void (*sfp1)() = &std::type::single_overload; + // expected-error@-1{{cannot take address of 'std::type::single_overload' because member functions in namespace 'std' are not addressable}} + void (*sfp2)() = std::type::single_overload; + // expected-error@-1{{cannot take address of 'std::type::single_overload' because member functions in namespace 'std' are not addressable}} + void (*sfp3)(double) = &std::type::double_overload; + // expected-error@-1{{cannot take address of 'std::type::double_overload' because member functions in namespace 'std' are not addressable}} + void (*sfp4)(int) = std::type::double_overload; + // expected-error@-1{{cannot take address of 'std::type::double_overload' because member functions in namespace 'std' are not addressable}} + void (*sfp5)(double) = &std::type::multi_overload; + // expected-error@-1{{cannot take address of 'std::type::multi_overload' because member functions in namespace 'std' are not addressable}} + void (*sfp6)(int) = std::type::multi_overload; + // expected-error@-1{{cannot take address of 'std::type::multi_overload' because member functions in namespace 'std' are not addressable}} + + void (&sfr1)() = &std::type::single_overload; + // expected-error@-1{{cannot take address of 'std::type::single_overload' because member functions in namespace 'std' are not addressable}} + void (&sfr2)() = std::type::single_overload; + // expected-error@-1{{cannot take address of 'std::type::single_overload' because member functions in namespace 'std' are not addressable}} + void (&sfr3)(double) = &std::type::double_overload; + // expected-error@-1{{cannot take address of 'std::type::double_overload' because member functions in namespace 'std' are not addressable}} + void (&sfr4)(int) = std::type::double_overload; + // expected-error@-1{{cannot take address of 'std::type::double_overload' because member functions in namespace 'std' are not addressable}} + void (&sfr5)(double) = &std::type::multi_overload; + // expected-error@-1{{cannot take address of 'std::type::multi_overload' because member functions in namespace 'std' are not addressable}} + void (&sfr6)(int) = std::type::multi_overload; + // expected-error@-1{{cannot take address of 'std::type::multi_overload' because member functions in namespace 'std' are not addressable}} +} + +void call_regression_test() { + std::one_overload(); + std::two_overloads(); + std::two_overloads(0); + std::many_overloads(0); + std::many_overloads(0.0); + + std::type s; + s.one_overload(); + s.two_overloads(); + static_cast(s).two_overloads(); + s.many_overloads(0); + s.many_overloads(1); +} + +namespace notstd { + [[clang::no_address]] void one_overload(); + // expected-error@-1 {{[[clang::no_address]] can only be used by the C++ standard library}} + [[clang::no_address]] void two_overloads(); + // expected-error@-1 {{[[clang::no_address]] can only be used by the C++ standard library}} + [[clang::no_address]] void two_overloads(int); + // expected-error@-1 {{[[clang::no_address]] can only be used by the C++ standard library}} + + template + [[clang::no_address]] void many_overloads(T); + // expected-error@-1 {{[[clang::no_address]] can only be used by the C++ standard library}} + + struct type { + [[clang::no_address]] void one_overload(); + // expected-error@-1 {{[[clang::no_address]] can only be used by the C++ standard library}} + [[clang::no_address]] void two_overloads(); + // expected-error@-1 {{[[clang::no_address]] can only be used by the C++ standard library}} + [[clang::no_address]] void two_overloads() const; + // expected-error@-1 {{[[clang::no_address]] can only be used by the C++ standard library}} + template + [[clang::no_address]] void many_overloads(T) &; + // expected-error@-1 {{[[clang::no_address]] can only be used by the C++ standard library}} + template + [[clang::no_address]] void many_overloads(T) const&; + // expected-error@-1 {{[[clang::no_address]] can only be used by the C++ standard library}} + template + [[clang::no_address]] void many_overloads(T) &&; + // expected-error@-1 {{[[clang::no_address]] can only be used by the C++ standard library}} + template + [[clang::no_address]] void many_overloads(T) const&&; + // expected-error@-1 {{[[clang::no_address]] can only be used by the C++ standard library}} + }; + + namespace std { + [[clang::no_address]] void one_overload(); + // expected-error@-1 {{[[clang::no_address]] can only be used by the C++ standard library}} + [[clang::no_address]] void two_overloads(); + // expected-error@-1 {{[[clang::no_address]] can only be used by the C++ standard library}} + [[clang::no_address]] void two_overloads(int); + // expected-error@-1 {{[[clang::no_address]] can only be used by the C++ standard library}} + + template + [[clang::no_address]] void many_overloads(T); + // expected-error@-1 {{[[clang::no_address]] can only be used by the C++ standard library}} + } +} // namespace notstd