Skip to content

Commit 6bf422d

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. (cherry picked from commit 17ac5fe)
1 parent 9c7df7a commit 6bf422d

File tree

2 files changed

+115
-36
lines changed

2 files changed

+115
-36
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 71 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;
@@ -1950,6 +1960,13 @@ namespace {
19501960
using MutableVarParent
19511961
= llvm::PointerUnion<InOutExpr *, LoadExpr *, AssignExpr *>;
19521962

1963+
ApplyExpr *getImmediateApply() const {
1964+
if (applyStack.empty())
1965+
return nullptr;
1966+
1967+
return applyStack.back().dyn_cast<ApplyExpr *>();
1968+
}
1969+
19531970
const PatternBindingDecl *getTopPatternBindingDecl() const {
19541971
return patternBindingStack.empty() ? nullptr : patternBindingStack.back();
19551972
}
@@ -2207,7 +2224,7 @@ namespace {
22072224

22082225
const auto End = applyStack.rend();
22092226
for (auto I = applyStack.rbegin(); I != End; ++I)
2210-
if (auto call = dyn_cast<CallExpr>(*I)) {
2227+
if (auto call = dyn_cast<CallExpr>(I->dyn_cast<ApplyExpr *>())) {
22112228
if (setAsync) {
22122229
call->setImplicitlyAsync(*setAsync);
22132230
}
@@ -2261,6 +2278,11 @@ namespace {
22612278
}
22622279

22632280
PreWalkResult<Expr *> walkToExprPre(Expr *expr) override {
2281+
// Skip expressions that didn't make it to solution application
2282+
// because the constraint system diagnosed an error.
2283+
if (!expr->getType())
2284+
return Action::SkipChildren(expr);
2285+
22642286
if (auto *openExistential = dyn_cast<OpenExistentialExpr>(expr)) {
22652287
opaqueValues.push_back({
22662288
openExistential->getOpaqueValue(),
@@ -2295,6 +2317,7 @@ namespace {
22952317
recordMutableVarParent(load, load->getSubExpr());
22962318

22972319
if (auto lookup = dyn_cast<LookupExpr>(expr)) {
2320+
applyStack.push_back(lookup);
22982321
checkReference(lookup->getBase(), lookup->getMember(), lookup->getLoc(),
22992322
/*partialApply*/ llvm::None, lookup);
23002323
return Action::Continue(expr);
@@ -2337,7 +2360,7 @@ namespace {
23372360
// Self applications are checked as part of the outer call.
23382361
// However, we look for inout issues here.
23392362
if (applyStack.size() >= 2) {
2340-
ApplyExpr *outerCall = applyStack[applyStack.size() - 2];
2363+
auto outerCall = applyStack[applyStack.size() - 2];
23412364
if (isAsyncCall(outerCall)) {
23422365
// This call is a partial application within an async call.
23432366
// If the partial application take a value inout, it is bad.
@@ -2391,17 +2414,21 @@ namespace {
23912414
}
23922415

23932416
if (auto *apply = dyn_cast<ApplyExpr>(expr)) {
2394-
assert(applyStack.back() == apply);
2417+
assert(applyStack.back().get<ApplyExpr *>() == apply);
23952418
applyStack.pop_back();
23962419
}
23972420

23982421
// Clear out the mutable local variable parent map on the way out.
2399-
if (auto *declRefExpr = dyn_cast<DeclRefExpr>(expr))
2422+
if (auto *declRefExpr = dyn_cast<DeclRefExpr>(expr)) {
24002423
mutableLocalVarParent.erase(declRefExpr);
2401-
else if (auto *lookupExpr = dyn_cast<LookupExpr>(expr))
2424+
} else if (auto *lookupExpr = dyn_cast<LookupExpr>(expr)) {
24022425
mutableLocalVarParent.erase(lookupExpr);
2403-
else if (auto *inoutExpr = dyn_cast<InOutExpr>(expr))
2426+
2427+
assert(applyStack.back().dyn_cast<LookupExpr *>() == lookupExpr);
2428+
applyStack.pop_back();
2429+
} else if (auto *inoutExpr = dyn_cast<InOutExpr>(expr)) {
24042430
mutableLocalVarParent.erase(inoutExpr);
2431+
}
24052432

24062433
// Remove the tracked capture contexts.
24072434
if (auto captureList = dyn_cast<CaptureListExpr>(expr)) {
@@ -2616,28 +2643,31 @@ namespace {
26162643
/// Diagnose an inout argument passed into an async call
26172644
///
26182645
/// \returns true if we diagnosed the entity, \c false otherwise.
2619-
bool diagnoseInOutArg(const ApplyExpr *call, const InOutExpr *arg,
2620-
bool isPartialApply) {
2646+
bool diagnoseInOutArg(
2647+
llvm::PointerUnion<ApplyExpr *, LookupExpr *> call,
2648+
const InOutExpr *arg,
2649+
bool isPartialApply) {
26212650
// check that the call is actually async
26222651
if (!isAsyncCall(call))
26232652
return false;
26242653

26252654
bool result = false;
2626-
auto checkDiagnostic = [this, call, isPartialApply, &result](
2655+
auto diagnoseIsolatedInoutState = [this, call, isPartialApply, &result](
26272656
ConcreteDeclRef declRef, SourceLoc argLoc) {
26282657
auto decl = declRef.getDecl();
26292658
auto isolation = getActorIsolationForReference(decl, getDeclContext());
26302659
if (!isolation.isActorIsolated())
26312660
return;
26322661

26332662
if (isPartialApply) {
2663+
auto *apply = call.get<ApplyExpr *>();
26342664
// The partially applied InoutArg is a property of actor. This
26352665
// can really only happen when the property is a struct with a
26362666
// mutating async method.
2637-
if (auto partialApply = dyn_cast<ApplyExpr>(call->getFn())) {
2667+
if (auto partialApply = dyn_cast<ApplyExpr>(apply->getFn())) {
26382668
if (auto declRef = dyn_cast<DeclRefExpr>(partialApply->getFn())) {
26392669
ValueDecl *fnDecl = declRef->getDecl();
2640-
ctx.Diags.diagnose(call->getLoc(),
2670+
ctx.Diags.diagnose(apply->getLoc(),
26412671
diag::actor_isolated_mutating_func,
26422672
fnDecl->getName(), decl);
26432673
result = true;
@@ -2646,29 +2676,36 @@ namespace {
26462676
}
26472677
}
26482678

2679+
bool isImplicitlyAsync;
2680+
if (auto *apply = call.dyn_cast<ApplyExpr *>()) {
2681+
isImplicitlyAsync = apply->isImplicitlyAsync().has_value();
2682+
} else {
2683+
auto *lookup = call.get<LookupExpr *>();
2684+
isImplicitlyAsync = lookup->isImplicitlyAsync().has_value();
2685+
}
2686+
26492687
ctx.Diags.diagnose(argLoc, diag::actor_isolated_inout_state,
2650-
decl, call->isImplicitlyAsync().has_value());
2688+
decl, isImplicitlyAsync);
26512689
decl->diagnose(diag::kind_declared_here, decl->getDescriptiveKind());
26522690
result = true;
26532691
return;
26542692
};
2655-
auto expressionWalker = [baseArg = arg->getSubExpr(),
2656-
checkDiagnostic](Expr *expr) -> Expr * {
2657-
if (isa<InOutExpr>(expr))
2658-
return nullptr; // AST walker will hit this again
2693+
2694+
auto findIsolatedState = [&](Expr *expr) -> Expr * {
26592695
if (LookupExpr *lookup = dyn_cast<LookupExpr>(expr)) {
26602696
if (isa<DeclRefExpr>(lookup->getBase())) {
2661-
checkDiagnostic(lookup->getMember().getDecl(), baseArg->getLoc());
2697+
diagnoseIsolatedInoutState(lookup->getMember().getDecl(),
2698+
expr->getLoc());
26622699
return nullptr; // Diagnosed. Don't keep walking
26632700
}
26642701
}
26652702
if (DeclRefExpr *declRef = dyn_cast<DeclRefExpr>(expr)) {
2666-
checkDiagnostic(declRef->getDecl(), baseArg->getLoc());
2703+
diagnoseIsolatedInoutState(declRef->getDecl(), expr->getLoc());
26672704
return nullptr; // Diagnosed. Don't keep walking
26682705
}
26692706
return expr;
26702707
};
2671-
arg->getSubExpr()->forEachChildExpr(expressionWalker);
2708+
arg->getSubExpr()->forEachChildExpr(findIsolatedState);
26722709
return result;
26732710
}
26742711

@@ -3172,9 +3209,9 @@ namespace {
31723209

31733210
// If this declaration is a callee from the enclosing application,
31743211
// it's already been checked via the call.
3175-
if (!applyStack.empty()) {
3212+
if (auto *apply = getImmediateApply()) {
31763213
auto immediateCallee =
3177-
applyStack.back()->getCalledValue(/*skipFunctionConversions=*/true);
3214+
apply->getCalledValue(/*skipFunctionConversions=*/true);
31783215
if (decl == immediateCallee)
31793216
return false;
31803217
}

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)