Require objcImplementation members to avoid vtable

They either must be @objc dynamic (the dynamic is implicit) or final.
This commit is contained in:
Becca Royal-Gordon
2022-01-31 17:36:41 -08:00
parent e544c21f99
commit 0678a38ce4
4 changed files with 86 additions and 2 deletions

View File

@@ -1512,6 +1512,18 @@ NOTE(attr_objc_implementation_fixit_remove_category_name,none,
"remove arguments to implement the main '@interface' for this class",
())
ERROR(member_of_objc_implementation_not_objc_or_final,none,
"%0 %1 does not match any %0 declared in the headers for %2; did you use "
"the %0's Swift name?",
(DescriptiveDeclKind, ValueDecl *, ValueDecl *))
NOTE(fixit_add_objc_for_objc_implementation,none,
"add '@objc' to define an Objective-C-compatible %0 not declared in "
"the header",
(DescriptiveDeclKind))
NOTE(fixit_add_final_for_objc_implementation,none,
"add 'final' to define a Swift %0 that cannot be overridden",
(DescriptiveDeclKind))
ERROR(cdecl_not_at_top_level,none,
"@_cdecl can only be applied to global functions", ())
ERROR(cdecl_empty_name,none,

View File

@@ -987,6 +987,12 @@ IsDynamicRequest::evaluate(Evaluator &evaluator, ValueDecl *decl) const {
if (isa<ExtensionDecl>(dc) && dc->getSelfClassDecl())
return true;
// @objc declarations in @_objcImplementation extensions are implicitly
// dynamic.
if (auto ED = dyn_cast_or_null<ExtensionDecl>(dc->getAsDecl()))
if (ED->isObjCImplementation())
return true;
// If any of the declarations overridden by this declaration are dynamic
// or were imported from Objective-C, this declaration is dynamic.
// Don't do this if the declaration is not exposed to Objective-C; that's

View File

@@ -1213,6 +1213,29 @@ static void checkDynamicSelfType(ValueDecl *decl, Type type) {
}
}
/// Check that, if this declaration is a member of an `@_objcImplementation`
/// extension, it is either `final` or `@objc` (which may have been inferred by
/// checking whether it shadows an imported declaration).
static void checkObjCImplementationMemberAvoidsVTable(ValueDecl *VD) {
auto ED = dyn_cast<ExtensionDecl>(VD->getDeclContext());
if (!ED || !ED->isObjCImplementation()) return;
if (VD->isSemanticallyFinal() || VD->isObjC()) return;
auto &diags = VD->getASTContext().Diags;
diags.diagnose(VD, diag::member_of_objc_implementation_not_objc_or_final,
VD->getDescriptiveKind(), VD, ED->getExtendedNominal());
if (canBeRepresentedInObjC(VD))
diags.diagnose(VD, diag::fixit_add_objc_for_objc_implementation,
VD->getDescriptiveKind())
.fixItInsert(VD->getAttributeInsertionLoc(false), "@objc ");
diags.diagnose(VD, diag::fixit_add_final_for_objc_implementation,
VD->getDescriptiveKind())
.fixItInsert(VD->getAttributeInsertionLoc(true), "final ");
}
/// Build a default initializer string for the given pattern.
///
/// This string is suitable for display in diagnostics.
@@ -1836,6 +1859,10 @@ public:
(void) VD->isObjC();
(void) VD->isDynamic();
// If this is in an `@_objcImplementation` extension, check whether it's
// valid there.
checkObjCImplementationMemberAvoidsVTable(VD);
// Check for actor isolation of top-level and local declarations.
// Declarations inside types are handled in checkConformancesInContext()
// to avoid cycles involving associated type inference.

View File

@@ -5,6 +5,9 @@
@_objcImplementation extension ObjCClass {
func method(fromHeader1: CInt) {
// FIXME: OK, provides an implementation for the header's method.
// expected-error@-2 {{instance method 'method(fromHeader1:)' does not match any instance method declared in the headers for 'ObjCClass'; did you use the instance method's Swift name?}}
// expected-note@-3 {{add '@objc' to define an Objective-C-compatible instance method not declared in the header}} {{3-3=@objc }}
// expected-note@-4 {{add 'final' to define a Swift instance method that cannot be overridden}} {{3-3=final }}
}
@objc func method(fromHeader2: CInt) {
@@ -13,6 +16,9 @@
func categoryMethod(fromHeader3: CInt) {
// FIXME: Should complain about the wrong category
// expected-error@-2 {{instance method 'categoryMethod(fromHeader3:)' does not match any instance method declared in the headers for 'ObjCClass'; did you use the instance method's Swift name?}}
// expected-note@-3 {{add '@objc' to define an Objective-C-compatible instance method not declared in the header}} {{3-3=@objc }}
// expected-note@-4 {{add 'final' to define a Swift instance method that cannot be overridden}} {{3-3=final }}
}
@objc fileprivate func methodNot(fromHeader1: CInt) {
@@ -24,7 +30,9 @@
}
func methodNot(fromHeader3: CInt) {
// FIXME: Should complain about unmatched, un-attributed method
// expected-error@-1 {{instance method 'methodNot(fromHeader3:)' does not match any instance method declared in the headers for 'ObjCClass'; did you use the instance method's Swift name?}}
// expected-note@-2 {{add '@objc' to define an Objective-C-compatible instance method not declared in the header}} {{3-3=@objc }}
// expected-note@-3 {{add 'final' to define a Swift instance method that cannot be overridden}} {{3-3=final }}
}
}
@@ -32,10 +40,19 @@
@_objcImplementation(PresentAdditions) extension ObjCClass {
func method(fromHeader3: CInt) {
// FIXME: Should complain about wrong category
// expected-error@-2 {{instance method 'method(fromHeader3:)' does not match any instance method declared in the headers for 'ObjCClass'; did you use the instance method's Swift name?}}
// expected-note@-3 {{add '@objc' to define an Objective-C-compatible instance method not declared in the header}} {{3-3=@objc }}
// expected-note@-4 {{add 'final' to define a Swift instance method that cannot be overridden}} {{3-3=final }}
}
func categoryMethod(fromHeader1: CInt) {
// FIXME: OK, provides an implementation for the header's method.
// expected-error@-2 {{instance method 'categoryMethod(fromHeader1:)' does not match any instance method declared in the headers for 'ObjCClass'; did you use the instance method's Swift name?}}
// expected-note@-3 {{add '@objc' to define an Objective-C-compatible instance method not declared in the header}} {{3-3=@objc }}
// expected-note@-4 {{add 'final' to define a Swift instance method that cannot be overridden}} {{3-3=final }}
}
@objc func categoryMethod(fromHeader2: CInt) {
// OK, provides an implementation for the header's method.
}
@@ -48,7 +65,9 @@
}
func categoryMethodNot(fromHeader3: CInt) {
// FIXME: Should complain about unmatched, un-attributed method
// expected-error@-1 {{instance method 'categoryMethodNot(fromHeader3:)' does not match any instance method declared in the headers for 'ObjCClass'; did you use the instance method's Swift name?}}
// expected-note@-2 {{add '@objc' to define an Objective-C-compatible instance method not declared in the header}} {{3-3=@objc }}
// expected-note@-3 {{add 'final' to define a Swift instance method that cannot be overridden}} {{3-3=final }}
}
}
@@ -72,3 +91,23 @@
@_objcImplementation(WTF) extension SwiftClass {} // expected
// expected-error@-1 {{'@_objcImplementation' cannot be used to extend class 'SwiftClass' because it was defined by a Swift 'class' declaration, not an imported Objective-C '@interface' declaration}} {{1-27=}}
func usesAreNotAmbiguous(obj: ObjCClass) {
obj.method(fromHeader1: 1)
obj.method(fromHeader2: 2)
obj.method(fromHeader3: 3)
obj.method(fromHeader4: 4)
obj.methodNot(fromHeader1: 1)
obj.methodNot(fromHeader2: 2)
obj.methodNot(fromHeader3: 3)
obj.categoryMethod(fromHeader1: 1)
obj.categoryMethod(fromHeader2: 2)
obj.categoryMethod(fromHeader3: 3)
obj.categoryMethod(fromHeader4: 4)
obj.categoryMethodNot(fromHeader1: 1)
obj.categoryMethodNot(fromHeader2: 2)
obj.categoryMethodNot(fromHeader3: 3)
}