From a5b8757506b07e3091fe243b6c1e004220d3cba3 Mon Sep 17 00:00:00 2001 From: Michael Forster Date: Thu, 13 Aug 2020 14:54:02 +0200 Subject: [PATCH] Introduce ns_error_domain attribute. ns_error_domain can be used by, e.g. NS_ERROR_ENUM, in order to identify a global declaration representing the domain constant. Introduces the attribute, Sema handling, diagnostics, and test case. This is cherry-picked from https://github.com/llvm/llvm-project-staging/commit/a14779f504b02ad0e4dbc39d6d10cadc7ed4cfd0 and adapted to updated Clang APIs. Reviewed By: gribozavr2, aaron.ballman Differential Revision: https://reviews.llvm.org/D84005 --- clang/include/clang/Basic/Attr.td | 7 ++ clang/include/clang/Basic/AttrDocs.td | 32 +++++++++ .../clang/Basic/DiagnosticSemaKinds.td | 5 ++ clang/lib/Sema/SemaDeclAttr.cpp | 31 +++++++++ clang/test/AST/ast-print-attr.c | 11 ++++ ...a-attribute-supported-attributes-list.test | 1 + clang/test/Sema/ns_error_enum.m | 66 +++++++++++++++++++ clang/utils/TableGen/ClangAttrEmitter.cpp | 2 + 8 files changed, 155 insertions(+) create mode 100644 clang/test/Sema/ns_error_enum.m diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 3d8ad705f91f..f525d3566dbb 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -1878,6 +1878,13 @@ def ObjCBridgeRelated : InheritableAttr { let Documentation = [Undocumented]; } +def NSErrorDomain : InheritableAttr { + let Spellings = [GNU<"ns_error_domain">]; + let Subjects = SubjectList<[Enum], ErrorDiag>; + let Args = [DeclArgument]; + let Documentation = [NSErrorDomainDocs]; +} + def NSReturnsRetained : DeclOrTypeAttr { let Spellings = [Clang<"ns_returns_retained">]; // let Subjects = SubjectList<[ObjCMethod, ObjCProperty, Function]>; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 83990721d7f7..29ad179e46e9 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -3339,6 +3339,38 @@ arguments, with arbitrary offsets. }]; } +def NSErrorDomainDocs : Documentation { + let Category = DocCatDecl; + let Content = [{ +In Cocoa frameworks in Objective-C, one can group related error codes in enums +and categorize these enums with error domains. + +The ``ns_error_domain`` attribute indicates a global ``NSString`` constant +representing the error domain that an error code belongs to. For pointer +uniqueness and code size this is a constant symbol, not a literal. + +The domain and error code need to be used together. The ``ns_error_domain`` +attribute links error codes to their domain at the source level. + +This metadata is useful for documentation purposes, for static analysis, and for +improving interoperability between Objective-C and Swift. It is not used for +code generation in Objective-C. + +For example: + + .. code-block:: objc + + #define NS_ERROR_ENUM(_type, _name, _domain) \ + enum _name : _type _name; enum __attribute__((ns_error_domain(_domain))) _name : _type + + extern NSString *const MyErrorDomain; + typedef NS_ERROR_ENUM(unsigned char, MyErrorEnum, MyErrorDomain) { + MyErrFirst, + MyErrSecond, + }; + }]; +} + def OMPDeclareSimdDocs : Documentation { let Category = DocCatFunction; let Heading = "#pragma omp declare simd"; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index f2e939da3050..e3edddf6a3f2 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -9476,6 +9476,11 @@ def err_nsconsumed_attribute_mismatch : Error< def err_nsreturns_retained_attribute_mismatch : Error< "overriding method has mismatched ns_returns_%select{not_retained|retained}0" " attributes">; +def err_nserrordomain_invalid_decl : Error< + "domain argument %select{|%1 }0does not refer to global constant">; +def err_nserrordomain_wrong_type : Error< + "domain argument %0 does not point to an NSString constant">; + def warn_nsconsumed_attribute_mismatch : Warning< err_nsconsumed_attribute_mismatch.Text>, InGroup; def warn_nsreturns_retained_attribute_mismatch : Warning< diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index d46c791b0e3a..bb19bd097564 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -21,6 +21,7 @@ #include "clang/AST/ExprCXX.h" #include "clang/AST/Mangle.h" #include "clang/AST/RecursiveASTVisitor.h" +#include "clang/AST/Type.h" #include "clang/Basic/CharInfo.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/TargetBuiltins.h" @@ -30,12 +31,15 @@ #include "clang/Sema/DelayedDiagnostic.h" #include "clang/Sema/Initialization.h" #include "clang/Sema/Lookup.h" +#include "clang/Sema/ParsedAttr.h" #include "clang/Sema/Scope.h" #include "clang/Sema/ScopeInfo.h" #include "clang/Sema/SemaInternal.h" +#include "llvm/ADT/Optional.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/MathExtras.h" +#include "llvm/Support/raw_ostream.h" using namespace clang; using namespace sema; @@ -5322,6 +5326,30 @@ static void handleObjCRequiresSuperAttr(Sema &S, Decl *D, D->addAttr(::new (S.Context) ObjCRequiresSuperAttr(S.Context, Attrs)); } +static void handleNSErrorDomain(Sema &S, Decl *D, const ParsedAttr &AL) { + auto *E = AL.getArgAsExpr(0); + auto Loc = E ? E->getBeginLoc() : AL.getLoc(); + + auto *DRE = dyn_cast(AL.getArgAsExpr(0)); + if (!DRE) { + S.Diag(Loc, diag::err_nserrordomain_invalid_decl) << 0; + return; + } + + auto *VD = dyn_cast(DRE->getDecl()); + if (!VD) { + S.Diag(Loc, diag::err_nserrordomain_invalid_decl) << 1 << DRE->getDecl(); + return; + } + + if (!isNSStringType(VD->getType(), S.Context)) { + S.Diag(Loc, diag::err_nserrordomain_wrong_type) << VD; + return; + } + + D->addAttr(::new (S.Context) NSErrorDomainAttr(S.Context, AL, VD)); +} + static void handleObjCBridgeAttr(Sema &S, Decl *D, const ParsedAttr &AL) { IdentifierLoc *Parm = AL.isArgIdent(0) ? AL.getArgAsIdent(0) : nullptr; @@ -7093,6 +7121,9 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, case ParsedAttr::AT_ObjCBoxable: handleObjCBoxable(S, D, AL); break; + case ParsedAttr::AT_NSErrorDomain: + handleNSErrorDomain(S, D, AL); + break; case ParsedAttr::AT_CFAuditedTransfer: handleSimpleAttributeWithExclusions(S, D, AL); diff --git a/clang/test/AST/ast-print-attr.c b/clang/test/AST/ast-print-attr.c index d223d607ed50..90a396303441 100644 --- a/clang/test/AST/ast-print-attr.c +++ b/clang/test/AST/ast-print-attr.c @@ -15,3 +15,14 @@ using C = int (*)() [[gnu::cdecl]]; int fun_asm() asm("test"); // CHECK: int var_asm asm("test"); int var_asm asm("test"); + + +@interface NSString +@end + +extern NSString *const MyErrorDomain; +// CHECK: enum __attribute__((ns_error_domain(MyErrorDomain))) MyErrorEnum { +enum __attribute__((ns_error_domain(MyErrorDomain))) MyErrorEnum { + MyErrFirst, + MyErrSecond, +}; diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test index c23a93218802..194c92e40eec 100644 --- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -80,6 +80,7 @@ // CHECK-NEXT: MipsShortCall (SubjectMatchRule_function) // CHECK-NEXT: NSConsumed (SubjectMatchRule_variable_is_parameter) // CHECK-NEXT: NSConsumesSelf (SubjectMatchRule_objc_method) +// CHECK-NEXT: NSErrorDomain (SubjectMatchRule_enum) // CHECK-NEXT: Naked (SubjectMatchRule_function) // CHECK-NEXT: NoBuiltin (SubjectMatchRule_function) // CHECK-NEXT: NoCommon (SubjectMatchRule_variable) diff --git a/clang/test/Sema/ns_error_enum.m b/clang/test/Sema/ns_error_enum.m new file mode 100644 index 000000000000..c8323d903d15 --- /dev/null +++ b/clang/test/Sema/ns_error_enum.m @@ -0,0 +1,66 @@ +// RUN: %clang_cc1 -verify %s -x objective-c +// RUN: %clang_cc1 -verify %s -x objective-c++ + + +#define CF_ENUM(_type, _name) enum _name : _type _name; enum _name : _type +#define NS_ENUM(_type, _name) CF_ENUM(_type, _name) + +#define NS_ERROR_ENUM(_type, _name, _domain) \ + enum _name : _type _name; enum __attribute__((ns_error_domain(_domain))) _name : _type + +typedef NS_ENUM(unsigned, MyEnum) { + MyFirst, + MySecond, +}; + +typedef NS_ENUM(invalidType, MyInvalidEnum) { +// expected-error@-1{{unknown type name 'invalidType'}} +// expected-error@-2{{unknown type name 'invalidType'}} + MyFirstInvalid, + MySecondInvalid, +}; + +@interface NSString +@end + +extern NSString *const MyErrorDomain; +typedef NS_ERROR_ENUM(unsigned char, MyErrorEnum, MyErrorDomain) { + MyErrFirst, + MyErrSecond, +}; + +typedef NSString *const NsErrorDomain; +extern NsErrorDomain MyTypedefErrorDomain; +typedef NS_ERROR_ENUM(unsigned char, MyTypedefErrorEnum, MyTypedefErrorDomain) { + MyTypedefErrFirst, + MyTypedefErrSecond, +}; + +extern char *const WrongErrorDomainType; +enum __attribute__((ns_error_domain(WrongErrorDomainType))) MyWrongErrorDomainType { MyWrongErrorDomain }; +// expected-error@-1{{domain argument 'WrongErrorDomainType' does not point to an NSString constant}} + +struct __attribute__((ns_error_domain(MyErrorDomain))) MyStructWithErrorDomain {}; +// expected-error@-1{{'ns_error_domain' attribute only applies to enums}} + +int __attribute__((ns_error_domain(MyErrorDomain))) NotTagDecl; + // expected-error@-1{{'ns_error_domain' attribute only applies to enums}} + +enum __attribute__((ns_error_domain())) NoArg { NoArgError }; +// expected-error@-1{{'ns_error_domain' attribute takes one argument}} + +enum __attribute__((ns_error_domain(MyErrorDomain, MyErrorDomain))) TwoArgs { TwoArgsError }; +// expected-error@-1{{'ns_error_domain' attribute takes one argument}} + +typedef NS_ERROR_ENUM(unsigned char, MyErrorEnumInvalid, InvalidDomain) { + // expected-error@-1{{use of undeclared identifier 'InvalidDomain'}} + MyErrFirstInvalid, + MyErrSecondInvalid, +}; + +typedef NS_ERROR_ENUM(unsigned char, MyErrorEnumInvalid, "domain-string"); + // expected-error@-1{{domain argument does not refer to global constant}} + +void foo() {} +typedef NS_ERROR_ENUM(unsigned char, MyErrorEnumInvalidFunction, foo); + // expected-error@-1{{domain argument 'foo' does not refer to global constant}} diff --git a/clang/utils/TableGen/ClangAttrEmitter.cpp b/clang/utils/TableGen/ClangAttrEmitter.cpp index e21d68d8f921..1ed81eefe981 100644 --- a/clang/utils/TableGen/ClangAttrEmitter.cpp +++ b/clang/utils/TableGen/ClangAttrEmitter.cpp @@ -329,6 +329,8 @@ namespace { // empty string but are then recorded as a nullptr. OS << "\" << (get" << getUpperName() << "() ? get" << getUpperName() << "()->getName() : \"\") << \""; + else if (type == "VarDecl *") + OS << "\" << get" << getUpperName() << "()->getName() << \""; else if (type == "TypeSourceInfo *") OS << "\" << get" << getUpperName() << "().getAsString() << \""; else if (type == "ParamIdx")