Skip to content

Commit 1e683a6

Browse files
committed
[Concurrency] Fix a few issues with actor-isolated 'inout' argument diagnostics.
ActorIsolationChecker's apply stack was not considering LookupExpr, which caused bogus diagnostics for inout arguments that happen to be subexpressions of async function calls. Similarly, inout arguments to async subscript calls were not diagnosed because the subscript application was not tracked in the apply stack. There's still an issue with async computed getter calls because the type-checked AST does not have an InOutExpr for the base expression.
1 parent d1436b3 commit 1e683a6

File tree

2 files changed

+110
-36
lines changed

2 files changed

+110
-36
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 66 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -556,20 +556,30 @@ findReference(Expr *expr) {
556556
///
557557
/// Note that this must be called after the implicitlyAsync flag has been set,
558558
/// or implicitly async calls will not return the correct value.
559-
static bool isAsyncCall(const ApplyExpr *call) {
560-
if (call->isImplicitlyAsync())
559+
static bool isAsyncCall(
560+
llvm::PointerUnion<ApplyExpr *, LookupExpr *> call) {
561+
562+
if (auto *apply = call.dyn_cast<ApplyExpr *>()) {
563+
if (apply->isImplicitlyAsync())
564+
return true;
565+
566+
// Effectively the same as doing a
567+
// `cast_or_null<FunctionType>(call->getFn()->getType())`, check the
568+
// result of that and then checking `isAsync` if it's defined.
569+
Type funcTypeType = apply->getFn()->getType();
570+
if (!funcTypeType)
571+
return false;
572+
AnyFunctionType *funcType = funcTypeType->getAs<AnyFunctionType>();
573+
if (!funcType)
574+
return false;
575+
return funcType->isAsync();
576+
}
577+
578+
auto *lookup = call.get<LookupExpr *>();
579+
if (lookup->isImplicitlyAsync())
561580
return true;
562581

563-
// Effectively the same as doing a
564-
// `cast_or_null<FunctionType>(call->getFn()->getType())`, check the
565-
// result of that and then checking `isAsync` if it's defined.
566-
Type funcTypeType = call->getFn()->getType();
567-
if (!funcTypeType)
568-
return false;
569-
AnyFunctionType *funcType = funcTypeType->getAs<AnyFunctionType>();
570-
if (!funcType)
571-
return false;
572-
return funcType->isAsync();
582+
return isAsyncDecl(lookup->getDecl());
573583
}
574584

575585
/// Determine whether we should diagnose data races within the current context.
@@ -1932,7 +1942,7 @@ namespace {
19321942
class ActorIsolationChecker : public ASTWalker {
19331943
ASTContext &ctx;
19341944
SmallVector<const DeclContext *, 4> contextStack;
1935-
SmallVector<ApplyExpr*, 4> applyStack;
1945+
SmallVector<llvm::PointerUnion<ApplyExpr *, LookupExpr *>, 4> applyStack;
19361946
SmallVector<std::pair<OpaqueValueExpr *, Expr *>, 4> opaqueValues;
19371947
SmallVector<const PatternBindingDecl *, 2> patternBindingStack;
19381948
llvm::function_ref<Type(Expr *)> getType;
@@ -1956,6 +1966,13 @@ namespace {
19561966
using MutableVarParent
19571967
= llvm::PointerUnion<InOutExpr *, LoadExpr *, AssignExpr *>;
19581968

1969+
ApplyExpr *getImmediateApply() const {
1970+
if (applyStack.empty())
1971+
return nullptr;
1972+
1973+
return applyStack.back().dyn_cast<ApplyExpr *>();
1974+
}
1975+
19591976
const PatternBindingDecl *getTopPatternBindingDecl() const {
19601977
return patternBindingStack.empty() ? nullptr : patternBindingStack.back();
19611978
}
@@ -2312,7 +2329,7 @@ namespace {
23122329

23132330
const auto End = applyStack.rend();
23142331
for (auto I = applyStack.rbegin(); I != End; ++I)
2315-
if (auto call = dyn_cast<CallExpr>(*I)) {
2332+
if (auto call = dyn_cast<CallExpr>(I->dyn_cast<ApplyExpr *>())) {
23162333
if (setAsync) {
23172334
call->setImplicitlyAsync(*setAsync);
23182335
}
@@ -2400,6 +2417,7 @@ namespace {
24002417
recordMutableVarParent(load, load->getSubExpr());
24012418

24022419
if (auto lookup = dyn_cast<LookupExpr>(expr)) {
2420+
applyStack.push_back(lookup);
24032421
checkReference(lookup->getBase(), lookup->getMember(), lookup->getLoc(),
24042422
/*partialApply*/ llvm::None, lookup);
24052423
return Action::Continue(expr);
@@ -2442,7 +2460,7 @@ namespace {
24422460
// Self applications are checked as part of the outer call.
24432461
// However, we look for inout issues here.
24442462
if (applyStack.size() >= 2) {
2445-
ApplyExpr *outerCall = applyStack[applyStack.size() - 2];
2463+
auto outerCall = applyStack[applyStack.size() - 2];
24462464
if (isAsyncCall(outerCall)) {
24472465
// This call is a partial application within an async call.
24482466
// If the partial application take a value inout, it is bad.
@@ -2500,17 +2518,21 @@ namespace {
25002518
}
25012519

25022520
if (auto *apply = dyn_cast<ApplyExpr>(expr)) {
2503-
assert(applyStack.back() == apply);
2521+
assert(applyStack.back().get<ApplyExpr *>() == apply);
25042522
applyStack.pop_back();
25052523
}
25062524

25072525
// Clear out the mutable local variable parent map on the way out.
2508-
if (auto *declRefExpr = dyn_cast<DeclRefExpr>(expr))
2526+
if (auto *declRefExpr = dyn_cast<DeclRefExpr>(expr)) {
25092527
mutableLocalVarParent.erase(declRefExpr);
2510-
else if (auto *lookupExpr = dyn_cast<LookupExpr>(expr))
2528+
} else if (auto *lookupExpr = dyn_cast<LookupExpr>(expr)) {
25112529
mutableLocalVarParent.erase(lookupExpr);
2512-
else if (auto *inoutExpr = dyn_cast<InOutExpr>(expr))
2530+
2531+
assert(applyStack.back().dyn_cast<LookupExpr *>() == lookupExpr);
2532+
applyStack.pop_back();
2533+
} else if (auto *inoutExpr = dyn_cast<InOutExpr>(expr)) {
25132534
mutableLocalVarParent.erase(inoutExpr);
2535+
}
25142536

25152537
// Remove the tracked capture contexts.
25162538
if (auto captureList = dyn_cast<CaptureListExpr>(expr)) {
@@ -2725,28 +2747,31 @@ namespace {
27252747
/// Diagnose an inout argument passed into an async call
27262748
///
27272749
/// \returns true if we diagnosed the entity, \c false otherwise.
2728-
bool diagnoseInOutArg(const ApplyExpr *call, const InOutExpr *arg,
2729-
bool isPartialApply) {
2750+
bool diagnoseInOutArg(
2751+
llvm::PointerUnion<ApplyExpr *, LookupExpr *> call,
2752+
const InOutExpr *arg,
2753+
bool isPartialApply) {
27302754
// check that the call is actually async
27312755
if (!isAsyncCall(call))
27322756
return false;
27332757

27342758
bool result = false;
2735-
auto checkDiagnostic = [this, call, isPartialApply, &result](
2759+
auto diagnoseIsolatedInoutState = [this, call, isPartialApply, &result](
27362760
ConcreteDeclRef declRef, SourceLoc argLoc) {
27372761
auto decl = declRef.getDecl();
27382762
auto isolation = getActorIsolationForReference(decl, getDeclContext());
27392763
if (!isolation.isActorIsolated())
27402764
return;
27412765

27422766
if (isPartialApply) {
2767+
auto *apply = call.get<ApplyExpr *>();
27432768
// The partially applied InoutArg is a property of actor. This
27442769
// can really only happen when the property is a struct with a
27452770
// mutating async method.
2746-
if (auto partialApply = dyn_cast<ApplyExpr>(call->getFn())) {
2771+
if (auto partialApply = dyn_cast<ApplyExpr>(apply->getFn())) {
27472772
if (auto declRef = dyn_cast<DeclRefExpr>(partialApply->getFn())) {
27482773
ValueDecl *fnDecl = declRef->getDecl();
2749-
ctx.Diags.diagnose(call->getLoc(),
2774+
ctx.Diags.diagnose(apply->getLoc(),
27502775
diag::actor_isolated_mutating_func,
27512776
fnDecl->getName(), decl);
27522777
result = true;
@@ -2755,29 +2780,36 @@ namespace {
27552780
}
27562781
}
27572782

2783+
bool isImplicitlyAsync;
2784+
if (auto *apply = call.dyn_cast<ApplyExpr *>()) {
2785+
isImplicitlyAsync = apply->isImplicitlyAsync().has_value();
2786+
} else {
2787+
auto *lookup = call.get<LookupExpr *>();
2788+
isImplicitlyAsync = lookup->isImplicitlyAsync().has_value();
2789+
}
2790+
27582791
ctx.Diags.diagnose(argLoc, diag::actor_isolated_inout_state,
2759-
decl, call->isImplicitlyAsync().has_value());
2792+
decl, isImplicitlyAsync);
27602793
decl->diagnose(diag::kind_declared_here, decl->getDescriptiveKind());
27612794
result = true;
27622795
return;
27632796
};
2764-
auto expressionWalker = [baseArg = arg->getSubExpr(),
2765-
checkDiagnostic](Expr *expr) -> Expr * {
2766-
if (isa<InOutExpr>(expr))
2767-
return nullptr; // AST walker will hit this again
2797+
2798+
auto findIsolatedState = [&](Expr *expr) -> Expr * {
27682799
if (LookupExpr *lookup = dyn_cast<LookupExpr>(expr)) {
27692800
if (isa<DeclRefExpr>(lookup->getBase())) {
2770-
checkDiagnostic(lookup->getMember().getDecl(), baseArg->getLoc());
2801+
diagnoseIsolatedInoutState(lookup->getMember().getDecl(),
2802+
expr->getLoc());
27712803
return nullptr; // Diagnosed. Don't keep walking
27722804
}
27732805
}
27742806
if (DeclRefExpr *declRef = dyn_cast<DeclRefExpr>(expr)) {
2775-
checkDiagnostic(declRef->getDecl(), baseArg->getLoc());
2807+
diagnoseIsolatedInoutState(declRef->getDecl(), expr->getLoc());
27762808
return nullptr; // Diagnosed. Don't keep walking
27772809
}
27782810
return expr;
27792811
};
2780-
arg->getSubExpr()->forEachChildExpr(expressionWalker);
2812+
arg->getSubExpr()->forEachChildExpr(findIsolatedState);
27812813
return result;
27822814
}
27832815

@@ -3284,9 +3316,9 @@ namespace {
32843316

32853317
// If this declaration is a callee from the enclosing application,
32863318
// it's already been checked via the call.
3287-
if (!applyStack.empty()) {
3319+
if (auto *apply = getImmediateApply()) {
32883320
auto immediateCallee =
3289-
applyStack.back()->getCalledValue(/*skipFunctionConversions=*/true);
3321+
apply->getCalledValue(/*skipFunctionConversions=*/true);
32903322
if (decl == immediateCallee)
32913323
return false;
32923324
}

test/Concurrency/actor_inout_isolation.swift

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,8 @@ extension TestActor {
136136
func callMutatingFunctionOnStruct() async {
137137
// expected-targeted-complete-warning @+4 {{passing argument of non-sendable type 'inout Point' outside of actor-isolated context may introduce data races}}
138138
// expected-error@+3:20{{cannot call mutating async function 'setComponents(x:y:)' on actor-isolated property 'position'}}
139-
// expected-error@+2:51{{actor-isolated property 'nextPosition' cannot be passed 'inout' to 'async' function call}}
140-
// expected-error@+1:71{{actor-isolated property 'nextPosition' cannot be passed 'inout' to 'async' function call}}
139+
// expected-error@+2:38{{actor-isolated property 'nextPosition' cannot be passed 'inout' to 'async' function call}}
140+
// expected-error@+1:58{{actor-isolated property 'nextPosition' cannot be passed 'inout' to 'async' function call}}
141141
await position.setComponents(x: &nextPosition.x, y: &nextPosition.y)
142142

143143
// expected-targeted-complete-warning @+4 {{passing argument of non-sendable type 'inout Point' outside of actor-isolated context may introduce data races}}
@@ -265,3 +265,45 @@ struct Dog {
265265
await cat?.meow()
266266
}
267267
}
268+
269+
@available(SwiftStdlib 5.1, *)
270+
func passToAsync(_: Int) async {}
271+
272+
@available(SwiftStdlib 5.1, *)
273+
func wrapInClosure(
274+
@_inheritActorContext _ block: @Sendable () async throws -> Void
275+
) async {}
276+
277+
@available(SwiftStdlib 5.1, *)
278+
extension Array {
279+
var mutateAsynchronously: Int {
280+
mutating get async { 0 }
281+
}
282+
283+
subscript(mutateAsynchronously i: Int) -> Int {
284+
mutating get async { 0 }
285+
}
286+
}
287+
288+
@available(SwiftStdlib 5.1, *)
289+
actor ProtectArray {
290+
var array: [Int] = []
291+
// expected-note@-1 {{property declared here}}
292+
293+
func test() async {
294+
// FIXME: this is invalid too!
295+
_ = await array.mutateAsynchronously
296+
// expected-targeted-complete-sns-warning@-1 {{non-sendable type '@lvalue [Int]' exiting actor-isolated context in call to non-isolated property 'mutateAsynchronously' cannot cross actor boundary}}
297+
298+
_ = await array[mutateAsynchronously: 0]
299+
// expected-error@-1 {{actor-isolated property 'array' cannot be passed 'inout' to 'async' function call}}
300+
// expected-targeted-complete-sns-warning@-2 {{non-sendable type 'inout Array<Int>' exiting actor-isolated context in call to non-isolated subscript 'subscript(mutateAsynchronously:)' cannot cross actor boundary}}
301+
302+
await passToAsync(array[0])
303+
304+
await wrapInClosure {
305+
_ = array[0]
306+
array.append(1)
307+
}
308+
}
309+
}

0 commit comments

Comments
 (0)