Skip to content

Commit 17ac5fe

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 17ac5fe

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;
@@ -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
}
@@ -2366,6 +2383,11 @@ namespace {
23662383
}
23672384

23682385
PreWalkResult<Expr *> walkToExprPre(Expr *expr) override {
2386+
// Skip expressions that didn't make it to solution application
2387+
// because the constraint system diagnosed an error.
2388+
if (!expr->getType())
2389+
return Action::SkipChildren(expr);
2390+
23692391
if (auto *openExistential = dyn_cast<OpenExistentialExpr>(expr)) {
23702392
opaqueValues.push_back({
23712393
openExistential->getOpaqueValue(),
@@ -2400,6 +2422,7 @@ namespace {
24002422
recordMutableVarParent(load, load->getSubExpr());
24012423

24022424
if (auto lookup = dyn_cast<LookupExpr>(expr)) {
2425+
applyStack.push_back(lookup);
24032426
checkReference(lookup->getBase(), lookup->getMember(), lookup->getLoc(),
24042427
/*partialApply*/ llvm::None, lookup);
24052428
return Action::Continue(expr);
@@ -2442,7 +2465,7 @@ namespace {
24422465
// Self applications are checked as part of the outer call.
24432466
// However, we look for inout issues here.
24442467
if (applyStack.size() >= 2) {
2445-
ApplyExpr *outerCall = applyStack[applyStack.size() - 2];
2468+
auto outerCall = applyStack[applyStack.size() - 2];
24462469
if (isAsyncCall(outerCall)) {
24472470
// This call is a partial application within an async call.
24482471
// If the partial application take a value inout, it is bad.
@@ -2500,17 +2523,21 @@ namespace {
25002523
}
25012524

25022525
if (auto *apply = dyn_cast<ApplyExpr>(expr)) {
2503-
assert(applyStack.back() == apply);
2526+
assert(applyStack.back().get<ApplyExpr *>() == apply);
25042527
applyStack.pop_back();
25052528
}
25062529

25072530
// Clear out the mutable local variable parent map on the way out.
2508-
if (auto *declRefExpr = dyn_cast<DeclRefExpr>(expr))
2531+
if (auto *declRefExpr = dyn_cast<DeclRefExpr>(expr)) {
25092532
mutableLocalVarParent.erase(declRefExpr);
2510-
else if (auto *lookupExpr = dyn_cast<LookupExpr>(expr))
2533+
} else if (auto *lookupExpr = dyn_cast<LookupExpr>(expr)) {
25112534
mutableLocalVarParent.erase(lookupExpr);
2512-
else if (auto *inoutExpr = dyn_cast<InOutExpr>(expr))
2535+
2536+
assert(applyStack.back().dyn_cast<LookupExpr *>() == lookupExpr);
2537+
applyStack.pop_back();
2538+
} else if (auto *inoutExpr = dyn_cast<InOutExpr>(expr)) {
25132539
mutableLocalVarParent.erase(inoutExpr);
2540+
}
25142541

25152542
// Remove the tracked capture contexts.
25162543
if (auto captureList = dyn_cast<CaptureListExpr>(expr)) {
@@ -2725,28 +2752,31 @@ namespace {
27252752
/// Diagnose an inout argument passed into an async call
27262753
///
27272754
/// \returns true if we diagnosed the entity, \c false otherwise.
2728-
bool diagnoseInOutArg(const ApplyExpr *call, const InOutExpr *arg,
2729-
bool isPartialApply) {
2755+
bool diagnoseInOutArg(
2756+
llvm::PointerUnion<ApplyExpr *, LookupExpr *> call,
2757+
const InOutExpr *arg,
2758+
bool isPartialApply) {
27302759
// check that the call is actually async
27312760
if (!isAsyncCall(call))
27322761
return false;
27332762

27342763
bool result = false;
2735-
auto checkDiagnostic = [this, call, isPartialApply, &result](
2764+
auto diagnoseIsolatedInoutState = [this, call, isPartialApply, &result](
27362765
ConcreteDeclRef declRef, SourceLoc argLoc) {
27372766
auto decl = declRef.getDecl();
27382767
auto isolation = getActorIsolationForReference(decl, getDeclContext());
27392768
if (!isolation.isActorIsolated())
27402769
return;
27412770

27422771
if (isPartialApply) {
2772+
auto *apply = call.get<ApplyExpr *>();
27432773
// The partially applied InoutArg is a property of actor. This
27442774
// can really only happen when the property is a struct with a
27452775
// mutating async method.
2746-
if (auto partialApply = dyn_cast<ApplyExpr>(call->getFn())) {
2776+
if (auto partialApply = dyn_cast<ApplyExpr>(apply->getFn())) {
27472777
if (auto declRef = dyn_cast<DeclRefExpr>(partialApply->getFn())) {
27482778
ValueDecl *fnDecl = declRef->getDecl();
2749-
ctx.Diags.diagnose(call->getLoc(),
2779+
ctx.Diags.diagnose(apply->getLoc(),
27502780
diag::actor_isolated_mutating_func,
27512781
fnDecl->getName(), decl);
27522782
result = true;
@@ -2755,29 +2785,36 @@ namespace {
27552785
}
27562786
}
27572787

2788+
bool isImplicitlyAsync;
2789+
if (auto *apply = call.dyn_cast<ApplyExpr *>()) {
2790+
isImplicitlyAsync = apply->isImplicitlyAsync().has_value();
2791+
} else {
2792+
auto *lookup = call.get<LookupExpr *>();
2793+
isImplicitlyAsync = lookup->isImplicitlyAsync().has_value();
2794+
}
2795+
27582796
ctx.Diags.diagnose(argLoc, diag::actor_isolated_inout_state,
2759-
decl, call->isImplicitlyAsync().has_value());
2797+
decl, isImplicitlyAsync);
27602798
decl->diagnose(diag::kind_declared_here, decl->getDescriptiveKind());
27612799
result = true;
27622800
return;
27632801
};
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
2802+
2803+
auto findIsolatedState = [&](Expr *expr) -> Expr * {
27682804
if (LookupExpr *lookup = dyn_cast<LookupExpr>(expr)) {
27692805
if (isa<DeclRefExpr>(lookup->getBase())) {
2770-
checkDiagnostic(lookup->getMember().getDecl(), baseArg->getLoc());
2806+
diagnoseIsolatedInoutState(lookup->getMember().getDecl(),
2807+
expr->getLoc());
27712808
return nullptr; // Diagnosed. Don't keep walking
27722809
}
27732810
}
27742811
if (DeclRefExpr *declRef = dyn_cast<DeclRefExpr>(expr)) {
2775-
checkDiagnostic(declRef->getDecl(), baseArg->getLoc());
2812+
diagnoseIsolatedInoutState(declRef->getDecl(), expr->getLoc());
27762813
return nullptr; // Diagnosed. Don't keep walking
27772814
}
27782815
return expr;
27792816
};
2780-
arg->getSubExpr()->forEachChildExpr(expressionWalker);
2817+
arg->getSubExpr()->forEachChildExpr(findIsolatedState);
27812818
return result;
27822819
}
27832820

@@ -3284,9 +3321,9 @@ namespace {
32843321

32853322
// If this declaration is a callee from the enclosing application,
32863323
// it's already been checked via the call.
3287-
if (!applyStack.empty()) {
3324+
if (auto *apply = getImmediateApply()) {
32883325
auto immediateCallee =
3289-
applyStack.back()->getCalledValue(/*skipFunctionConversions=*/true);
3326+
apply->getCalledValue(/*skipFunctionConversions=*/true);
32903327
if (decl == immediateCallee)
32913328
return false;
32923329
}

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)