Skip to content

Commit e33ed77

Browse files
authored
Merge pull request #28135 from theblixguy/fix/SR_5115
[MiscDiagnostics] Diagnose passing a non-@objc dynamic KeyPath property to KVO observe
2 parents 98c7ccc + fab6ce9 commit e33ed77

File tree

3 files changed

+96
-0
lines changed

3 files changed

+96
-0
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4540,6 +4540,9 @@ WARNING(variable_never_mutated, none,
45404540
WARNING(variable_never_read, none,
45414541
"variable %0 was written to, but never read",
45424542
(Identifier))
4543+
WARNING(observe_keypath_property_not_objc_dynamic, none,
4544+
"passing reference to non-'@objc dynamic' property %0 to KVO method %1 "
4545+
"may lead to unexpected behavior or runtime trap", (DeclName, DeclName))
45434546

45444547
//------------------------------------------------------------------------------
45454548
// MARK: Debug diagnostics

lib/Sema/MiscDiagnostics.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3990,6 +3990,59 @@ static void diagnoseDeprecatedWritableKeyPath(const Expr *E,
39903990
const_cast<Expr *>(E)->walk(Walker);
39913991
}
39923992

3993+
static void maybeDiagnoseCallToKeyValueObserveMethod(const Expr *E,
3994+
const DeclContext *DC) {
3995+
class KVOObserveCallWalker : public ASTWalker {
3996+
const ASTContext &C;
3997+
3998+
public:
3999+
KVOObserveCallWalker(ASTContext &ctx) : C(ctx) {}
4000+
4001+
void maybeDiagnoseCallExpr(CallExpr *expr) {
4002+
auto fn = expr->getCalledValue();
4003+
if (!fn)
4004+
return;
4005+
if (fn->getModuleContext()->getName() != C.Id_Foundation)
4006+
return;
4007+
if (!fn->getFullName().isCompoundName("observe",
4008+
{"", "options", "changeHandler"}))
4009+
return;
4010+
auto args = cast<TupleExpr>(expr->getArg());
4011+
auto firstArg = dyn_cast<KeyPathExpr>(args->getElement(0));
4012+
if (!firstArg)
4013+
return;
4014+
auto lastComponent = firstArg->getComponents().back();
4015+
if (lastComponent.getKind() != KeyPathExpr::Component::Kind::Property)
4016+
return;
4017+
auto property = lastComponent.getDeclRef().getDecl();
4018+
if (!property)
4019+
return;
4020+
if (property->isObjCDynamic())
4021+
return;
4022+
C.Diags
4023+
.diagnose(expr->getLoc(),
4024+
diag::observe_keypath_property_not_objc_dynamic,
4025+
property->getFullName(), fn->getFullName())
4026+
.highlight(lastComponent.getLoc());
4027+
}
4028+
4029+
std::pair<bool, Expr *> walkToExprPre(Expr *E) override {
4030+
if (!E || isa<ErrorExpr>(E) || !E->getType())
4031+
return {false, E};
4032+
4033+
if (auto *CE = dyn_cast<CallExpr>(E)) {
4034+
maybeDiagnoseCallExpr(CE);
4035+
return {false, E};
4036+
}
4037+
4038+
return {true, E};
4039+
}
4040+
};
4041+
4042+
KVOObserveCallWalker Walker(DC->getASTContext());
4043+
const_cast<Expr *>(E)->walk(Walker);
4044+
}
4045+
39934046
//===----------------------------------------------------------------------===//
39944047
// High-level entry points.
39954048
//===----------------------------------------------------------------------===//
@@ -4004,6 +4057,7 @@ void swift::performSyntacticExprDiagnostics(const Expr *E,
40044057
diagRecursivePropertyAccess(E, DC);
40054058
diagnoseImplicitSelfUseInClosure(E, DC);
40064059
diagnoseUnintendedOptionalBehavior(E, DC);
4060+
maybeDiagnoseCallToKeyValueObserveMethod(E, DC);
40074061
if (!ctx.isSwiftVersionAtLeast(5))
40084062
diagnoseDeprecatedWritableKeyPath(E, DC);
40094063
if (!ctx.LangOpts.DisableAvailabilityChecking)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// RUN: %target-typecheck-verify-swift
2+
// REQUIRES: objc_interop
3+
4+
import Foundation
5+
6+
class Foo: NSObject {
7+
var number1 = 1
8+
dynamic var number2 = 2
9+
@objc var number3 = 3
10+
@objc dynamic var number4 = 4
11+
}
12+
13+
class Bar: NSObject {
14+
@objc dynamic let foo: Foo
15+
16+
init(foo: Foo) {
17+
self.foo = foo
18+
super.init()
19+
20+
_ = observe(\.foo.number1, options: [.new]) { _, change in
21+
// expected-warning@-1 {{passing reference to non-'@objc dynamic' property 'number1' to KVO method 'observe(_:options:changeHandler:)' may lead to unexpected behavior or runtime trap}}
22+
print("observer1")
23+
}
24+
25+
_ = observe(\.foo.number2, options: [.new]) { _, change in
26+
// expected-warning@-1 {{passing reference to non-'@objc dynamic' property 'number2' to KVO method 'observe(_:options:changeHandler:)' may lead to unexpected behavior or runtime trap}}
27+
print("observer2")
28+
}
29+
30+
_ = observe(\.foo.number3, options: [.new]) { _, change in
31+
// expected-warning@-1 {{passing reference to non-'@objc dynamic' property 'number3' to KVO method 'observe(_:options:changeHandler:)' may lead to unexpected behavior or runtime trap}}
32+
print("observer3")
33+
}
34+
35+
_ = observe(\.foo.number4, options: [.new]) { _, change in // Okay
36+
print("observer4")
37+
}
38+
}
39+
}

0 commit comments

Comments
 (0)