diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -122,7 +122,8 @@ C Language Changes in Clang --------------------------- -- ... +- Support for ``__attribute__((error("")))`` and + ``__attribute__((warning("")))`` function attributes have been added. C++ Language Changes in Clang ----------------------------- 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 @@ -3812,3 +3812,17 @@ let Documentation = [EnforceTCBLeafDocs]; bit InheritEvenIfAlreadyPresent = 1; } + +def Error : Attr { + let Spellings = [GCC<"error">]; + let Args = [StringArgument<"UserDiagnostic">]; + let Subjects = SubjectList<[Function], ErrorDiag>; + let Documentation = [ErrorAttrDocs]; +} + +def Warning : Attr { + let Spellings = [GCC<"warning">]; + let Args = [StringArgument<"UserDiagnostic">]; + let Subjects = SubjectList<[Function]>; + let Documentation = [WarningAttrDocs]; +} 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 @@ -6015,3 +6015,24 @@ - ``enforce_tcb_leaf(Name)`` indicates that this function is a part of the TCB named ``Name`` }]; } + +def ErrorAttrDocs : Documentation { + let Category = DocCatFunction; + let Content = [{ + The ``error`` function attribute can be used to specify a custom diagnostic + to be emitted when a call to such a function is not eliminated via + optimizations. This can be used to create compile time assertions that + depend on optimizations, while providing diagnostics pointing to precise + locations of the call site in the source. + }]; +} +def WarningAttrDocs : Documentation { + let Category = DocCatFunction; + let Content = [{ + The ``warning`` function attribute can be used to specify a custom + diagnostic to be emitted when a call to such a function is not eliminated + via optimizations. This can be used to create compile time assertions that + depend on optimizations, while providing diagnostics pointing to precise + locations of the call site in the source. + }]; +} diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td --- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td +++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td @@ -72,6 +72,11 @@ def err_fe_backend_unsupported : Error<"%0">, BackendInfo; def warn_fe_backend_unsupported : Warning<"%0">, BackendInfo; +def err_fe_backend_user_diagnostic : + Error<"call to %0 declared with attribute error: %1">, BackendInfo; +def warn_fe_backend_user_diagnostic : + Warning<"call to %0 declared with attribute warning: %1">, BackendInfo, InGroup; + def err_fe_invalid_code_complete_file : Error< "cannot locate code-completion file %0">, DefaultFatal; def err_fe_dependency_file_requires_MT : Error< diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -1202,6 +1202,7 @@ def BackendOptimizationRemarkMissed : DiagGroup<"pass-missed">; def BackendOptimizationRemarkAnalysis : DiagGroup<"pass-analysis">; def BackendOptimizationFailure : DiagGroup<"pass-failed">; +def BackendUserDiagnostic : DiagGroup<"user-diagnostic">; // Instrumentation based profiling warnings. def ProfileInstrMissing : DiagGroup<"profile-instr-missing">; 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 @@ -5304,6 +5304,17 @@ TargetDecl->hasAttr()) getDebugInfo()->addHeapAllocSiteMetadata(CI, RetTy->getPointeeType(), Loc); + // Add metadata if calling an __attribute__((error(""))) or warning fn. + if (TargetDecl) + if (TargetDecl->hasAttr() || + TargetDecl->hasAttr()) { + llvm::ConstantInt *Line = + llvm::ConstantInt::get(Int32Ty, Loc.getRawEncoding()); + llvm::ConstantAsMetadata *MD = llvm::ConstantAsMetadata::get(Line); + llvm::MDTuple *MDT = llvm::MDNode::get(getLLVMContext(), {MD}); + CI->setMetadata("srcloc", MDT); + } + // 4. Finish the call. // If the call doesn't return, finish the basic block and clear the diff --git a/clang/lib/CodeGen/CodeGenAction.cpp b/clang/lib/CodeGen/CodeGenAction.cpp --- a/clang/lib/CodeGen/CodeGenAction.cpp +++ b/clang/lib/CodeGen/CodeGenAction.cpp @@ -401,6 +401,7 @@ const llvm::OptimizationRemarkAnalysisAliasing &D); void OptimizationFailureHandler( const llvm::DiagnosticInfoOptimizationFailure &D); + void UserDiagnosticHandler(const DiagnosticInfoUser &D); }; void BackendConsumer::anchor() {} @@ -758,6 +759,32 @@ EmitOptimizationMessage(D, diag::warn_fe_backend_optimization_failure); } +void BackendConsumer::UserDiagnosticHandler(const DiagnosticInfoUser &D) { + unsigned DiagID = diag::err_fe_backend_user_diagnostic; + llvm::StringRef CalleeName = D.getFunctionName(); + SourceLocation LocCookie = + SourceLocation::getFromRawEncoding(D.getLocCookie()); + + if (const Decl *D = Gen->GetDeclForMangledName(CalleeName)) { + if (const auto *FD = dyn_cast(D)) { + assert((FD->hasAttr() || FD->hasAttr()) && + "expected error or warning function attribute"); + + if (FD->hasAttr()) { + llvm::errs() << "should be a warning diag\n"; + DiagID = diag::warn_fe_backend_user_diagnostic; + } + + CalleeName = FD->getName(); + } + } + + if (LocCookie.isValid()) + Diags.Report(LocCookie, DiagID) << CalleeName << D.getMsgStr(); + else + Diags.Report(DiagID) << CalleeName << D.getMsgStr(); +} + /// This function is invoked when the backend needs /// to report something to the user. void BackendConsumer::DiagnosticHandlerImpl(const DiagnosticInfo &DI) { @@ -833,6 +860,9 @@ case llvm::DK_Unsupported: UnsupportedDiagHandler(cast(DI)); return; + case llvm::DK_UserDiagnostic: + UserDiagnosticHandler(cast(DI)); + return; default: // Plugin IDs are not bound to any value as they are set dynamically. ComputeDiagRemarkID(Severity, backend_plugin, DiagID); diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -2172,6 +2172,13 @@ CalleeIdx, PayloadIndices, /* VarArgsArePassed */ false)})); } + + // TODO: move to CodeGenModule::ConstructAttributeList() or + // CodeGenFunction::EmitCall() ??? + if (const auto *EA = FD->getAttr()) + F->addFnAttr("user-diagnostic", EA->getUserDiagnostic()); + if (const auto *WA = FD->getAttr()) + F->addFnAttr("user-diagnostic", WA->getUserDiagnostic()); } void CodeGenModule::addUsedGlobal(llvm::GlobalValue *GV) { 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 @@ -946,6 +946,19 @@ D->addAttr(::new (S.Context) EnableIfAttr(S.Context, AL, Cond, Msg)); } +static void handleErrorAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + StringRef Str; + if (!S.checkStringLiteralArgumentAttr(AL, 0, Str)) + return; + D->addAttr(::new (S.Context) ErrorAttr(S.Context, AL, Str)); +} +static void handleWarningAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + StringRef Str; + if (!S.checkStringLiteralArgumentAttr(AL, 0, Str)) + return; + D->addAttr(::new (S.Context) WarningAttr(S.Context, AL, Str)); +} + namespace { /// Determines if a given Expr references any of the given function's /// ParmVarDecls, or the function's implicit `this` parameter (if applicable). @@ -7843,6 +7856,9 @@ case ParsedAttr::AT_EnableIf: handleEnableIfAttr(S, D, AL); break; + case ParsedAttr::AT_Error: + handleErrorAttr(S, D, AL); + break; case ParsedAttr::AT_DiagnoseIf: handleDiagnoseIfAttr(S, D, AL); break; @@ -8058,6 +8074,9 @@ case ParsedAttr::AT_TypeVisibility: handleVisibilityAttr(S, D, AL, true); break; + case ParsedAttr::AT_Warning: + handleWarningAttr(S, D, AL); + break; case ParsedAttr::AT_WarnUnusedResult: handleWarnUnusedResult(S, D, AL); break; diff --git a/clang/test/CodeGen/attr-error.c b/clang/test/CodeGen/attr-error.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/attr-error.c @@ -0,0 +1,11 @@ +// RUN: %clang_cc1 -emit-llvm -o - %s | FileCheck %s +__attribute__((error("oh no"))) void foo(void); + +void bar(void) { + foo(); +} + +// CHECK: call void @foo(), !srcloc [[SRCLOC:![0-9]+]] +// CHECK: declare void @foo() [[ATTR:#[0-9]+]] +// CHECK: attributes [[ATTR]] = {{{.*}}"user-diagnostic"="oh no" +// CHECK: [[SRCLOC]] = !{i32 {{[0-9]+}}} diff --git a/clang/test/CodeGen/attr-warning.c b/clang/test/CodeGen/attr-warning.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/attr-warning.c @@ -0,0 +1,11 @@ +// RUN: %clang_cc1 -emit-llvm -o - %s | FileCheck %s +__attribute__((warning("oh no"))) void foo(void); + +void bar(void) { + foo(); +} + +// CHECK: call void @foo(), !srcloc [[SRCLOC:![0-9]+]] +// CHECK: declare void @foo() [[ATTR:#[0-9]+]] +// CHECK: attributes [[ATTR]] = {{{.*}}"user-diagnostic"="oh no" +// CHECK: [[SRCLOC]] = !{i32 {{[0-9]+}}} diff --git a/clang/test/Frontend/backend-attribute-error-warning-optimize.c b/clang/test/Frontend/backend-attribute-error-warning-optimize.c new file mode 100644 --- /dev/null +++ b/clang/test/Frontend/backend-attribute-error-warning-optimize.c @@ -0,0 +1,15 @@ +// REQUIRES: x86-registered-target +// RUN: %clang_cc1 -O2 -verify -emit-codegen-only %s + +__attribute__((error("oh no foo"))) void foo(void); + +__attribute__((error("oh no bar"))) void bar(void); + +int x(void) { + return 8 % 2 == 1; +} +void baz(void) { + foo(); // expected-error {{call to foo declared with attribute error: oh no foo}} + if (x()) + bar(); +} diff --git a/clang/test/Frontend/backend-attribute-error-warning.c b/clang/test/Frontend/backend-attribute-error-warning.c new file mode 100644 --- /dev/null +++ b/clang/test/Frontend/backend-attribute-error-warning.c @@ -0,0 +1,22 @@ +// REQUIRES: x86-registered-target +// RUN: %clang_cc1 -verify -emit-codegen-only %s + +__attribute__((error("oh no foo"))) void foo(void); + +__attribute__((error("oh no bar"))) void bar(void); + +int x(void) { + return 8 % 2 == 1; +} + +__attribute__((warning("oh no quux"))) void quux(void); + +__attribute__((error("demangle me"))) void __compiletime_assert_455(void); + +void baz(void) { + foo(); // expected-error {{call to foo declared with attribute error: oh no foo}} + if (x()) + bar(); // expected-error {{call to bar declared with attribute error: oh no bar}} + quux(); // expected-warning {{call to quux declared with attribute warning: oh no quux}} + __compiletime_assert_455(); // expected-error {{call to __compiletime_assert_455 declared with attribute error: demangle me}} +} diff --git a/clang/test/Frontend/backend-attribute-error-warning.cpp b/clang/test/Frontend/backend-attribute-error-warning.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Frontend/backend-attribute-error-warning.cpp @@ -0,0 +1,29 @@ +// REQUIRES: x86-registered-target +// RUN: %clang_cc1 -verify -emit-codegen-only %s + +__attribute__((error("oh no foo"))) void foo(void); + +__attribute__((error("oh no bar"))) void bar(void); + +int x(void) { + return 8 % 2 == 1; +} + +__attribute__((warning("oh no quux"))) void quux(void); + +__attribute__((error("demangle me"))) void __compiletime_assert_455(void); + +template +__attribute__((error("demangle me, too"))) +T +nocall(T t); + +void baz(void) { + // Test that we demangle correctly in the diagnostic for C++. + foo(); // expected-error {{call to foo declared with attribute error: oh no foo}} + if (x()) + bar(); // expected-error {{call to bar declared with attribute error: oh no bar}} + quux(); // expected-warning {{call to quux declared with attribute warning: oh no quux}} + __compiletime_assert_455(); // expected-error {{call to __compiletime_assert_455 declared with attribute error: demangle me}} + nocall(42); // expected-error {{call to nocall declared with attribute error: demangle me, too}} +} 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 @@ -61,6 +61,7 @@ // CHECK-NEXT: EnforceTCB (SubjectMatchRule_function) // CHECK-NEXT: EnforceTCBLeaf (SubjectMatchRule_function) // CHECK-NEXT: EnumExtensibility (SubjectMatchRule_enum) +// CHECK-NEXT: Error (SubjectMatchRule_function) // CHECK-NEXT: ExcludeFromExplicitInstantiation (SubjectMatchRule_variable, SubjectMatchRule_function, SubjectMatchRule_record) // CHECK-NEXT: ExternalSourceSymbol ((SubjectMatchRule_record, SubjectMatchRule_enum, SubjectMatchRule_enum_constant, SubjectMatchRule_field, SubjectMatchRule_function, SubjectMatchRule_namespace, SubjectMatchRule_objc_category, SubjectMatchRule_objc_implementation, SubjectMatchRule_objc_interface, SubjectMatchRule_objc_method, SubjectMatchRule_objc_property, SubjectMatchRule_objc_protocol, SubjectMatchRule_record, SubjectMatchRule_type_alias, SubjectMatchRule_variable)) // CHECK-NEXT: FlagEnum (SubjectMatchRule_enum) @@ -177,6 +178,7 @@ // CHECK-NEXT: VecTypeHint (SubjectMatchRule_function) // CHECK-NEXT: WarnUnused (SubjectMatchRule_record) // CHECK-NEXT: WarnUnusedResult (SubjectMatchRule_objc_method, SubjectMatchRule_enum, SubjectMatchRule_record, SubjectMatchRule_hasType_functionType) +// CHECK-NEXT: Warning (SubjectMatchRule_function) // CHECK-NEXT: Weak (SubjectMatchRule_variable, SubjectMatchRule_function, SubjectMatchRule_record) // CHECK-NEXT: WeakRef (SubjectMatchRule_variable, SubjectMatchRule_function) // CHECK-NEXT: WebAssemblyExportName (SubjectMatchRule_function) diff --git a/clang/test/Sema/attr-error.c b/clang/test/Sema/attr-error.c new file mode 100644 --- /dev/null +++ b/clang/test/Sema/attr-error.c @@ -0,0 +1,20 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s +#if !__has_attribute(error) +#error "error attribute missing" +#endif + +__attribute__((error("don't call me!"))) int foo(void); + +__attribute__((error)) // expected-error {{'error' attribute takes one argument}} +int +bad0(void); + +int bad1(__attribute__((error("bad1"))) int param); // expected-error {{'error' attribute only applies to functions}} + +int bad2(void) { + __attribute__((error("bad2"))); // expected-error {{'error' attribute cannot be applied to a statement}} +} + +__attribute__((error(3))) // expected-error {{'error' attribute requires a string}} +int +bad3(void); diff --git a/clang/test/Sema/attr-warning.c b/clang/test/Sema/attr-warning.c new file mode 100644 --- /dev/null +++ b/clang/test/Sema/attr-warning.c @@ -0,0 +1,20 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s +#if !__has_attribute(warning) +#warning "warning attribute missing" +#endif + +__attribute__((warning("don't call me!"))) int foo(void); + +__attribute__((warning)) // expected-error {{'warning' attribute takes one argument}} +int +bad0(void); + +int bad1(__attribute__((warning("bad1"))) int param); // expected-warning {{'warning' attribute only applies to functions}} + +int bad2(void) { + __attribute__((warning("bad2"))); // expected-error {{'warning' attribute cannot be applied to a statement}} +} + +__attribute__((warning(3))) // expected-error {{'warning' attribute requires a string}} +int +bad3(void); diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -2037,6 +2037,12 @@ show that no exceptions passes by it. This is normally the case for the ELF x86-64 abi, but it can be disabled for some compilation units. +``"user-diagnostic"=""`` + This attribute provides a diagnostic string to emit when a call of a + function with this attribute is not eliminated via optimization. Front ends + can provide optional ``srcloc`` metadata nodes on call sites of such + callees to attach information about where in the source language such a + call came from. ``nocf_check`` This attribute indicates that no control-flow check will be performed on the attributed entity. It disables -fcf-protection=<> for a specific diff --git a/llvm/include/llvm/IR/DiagnosticInfo.h b/llvm/include/llvm/IR/DiagnosticInfo.h --- a/llvm/include/llvm/IR/DiagnosticInfo.h +++ b/llvm/include/llvm/IR/DiagnosticInfo.h @@ -79,6 +79,7 @@ DK_PGOProfile, DK_Unsupported, DK_SrcMgr, + DK_UserDiagnostic, DK_FirstPluginKind // Must be last value to work with // getNextAvailablePluginDiagnosticKind }; @@ -1070,6 +1071,25 @@ } }; +class DiagnosticInfoUser : public DiagnosticInfo { + StringRef MsgStr; + StringRef CalleeName; + unsigned LocCookie; + +public: + DiagnosticInfoUser(StringRef MsgStr, StringRef CalleeName, unsigned LocCookie, + DiagnosticSeverity Severity = DS_Error) + : DiagnosticInfo(DK_UserDiagnostic, Severity), MsgStr(MsgStr), + CalleeName(CalleeName), LocCookie(LocCookie) {} + StringRef getMsgStr() const { return MsgStr; } + StringRef getFunctionName() const { return CalleeName; } + unsigned getLocCookie() const { return LocCookie; } + void print(DiagnosticPrinter &DP) const override; + static bool classof(const DiagnosticInfo *DI) { + return DI->getKind() == DK_UserDiagnostic; + } +}; + } // end namespace llvm #endif // LLVM_IR_DIAGNOSTICINFO_H diff --git a/llvm/lib/CodeGen/PreISelIntrinsicLowering.cpp b/llvm/lib/CodeGen/PreISelIntrinsicLowering.cpp --- a/llvm/lib/CodeGen/PreISelIntrinsicLowering.cpp +++ b/llvm/lib/CodeGen/PreISelIntrinsicLowering.cpp @@ -14,6 +14,7 @@ #include "llvm/CodeGen/PreISelIntrinsicLowering.h" #include "llvm/Analysis/ObjCARCInstKind.h" #include "llvm/CodeGen/Passes.h" +#include "llvm/IR/DiagnosticInfo.h" #include "llvm/IR/Function.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/Instructions.h" @@ -204,6 +205,25 @@ Changed |= lowerObjCCall(F, "objc_sync_exit"); break; } + // TODO: is this the best pass for this? + for (BasicBlock &BB : F) { + for (Instruction &I : BB) { + if (CallBase *CB = dyn_cast(&I)) { + if (Function *Callee = CB->getCalledFunction()) { + if (Callee->hasFnAttribute("user-diagnostic")) { + unsigned LocCookie = 0; + if (MDNode *MD = CB->getMetadata("srcloc")) + LocCookie = mdconst::extract(MD->getOperand(0)) + ->getZExtValue(); + DiagnosticInfoUser DU( + Callee->getFnAttribute("user-diagnostic").getValueAsString(), + Callee->getName(), LocCookie); + F.getContext().diagnose(DU); + } + } + } + } + } } return Changed; } diff --git a/llvm/lib/IR/DiagnosticInfo.cpp b/llvm/lib/IR/DiagnosticInfo.cpp --- a/llvm/lib/IR/DiagnosticInfo.cpp +++ b/llvm/lib/IR/DiagnosticInfo.cpp @@ -401,3 +401,7 @@ void OptimizationRemarkAnalysisFPCommute::anchor() {} void OptimizationRemarkAnalysisAliasing::anchor() {} + +void DiagnosticInfoUser::print(DiagnosticPrinter &DP) const { + DP << "call to " << getFunctionName() << ": " << getMsgStr(); +} diff --git a/llvm/test/CodeGen/Generic/attr-user-diagnostic.ll b/llvm/test/CodeGen/Generic/attr-user-diagnostic.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/Generic/attr-user-diagnostic.ll @@ -0,0 +1,9 @@ +; RUN: not llc -stop-after=pre-isel-intrinsic-lowering %s 2>&1 | FileCheck %s + +declare void @foo() "user-diagnostic"="oh no" +define void @bar() { + call void @foo() + ret void +} + +; CHECK: error: call to foo: oh no