Skip to content

Commit 6fe7496

Browse files
committed
[region-isolation] Add 'inout sending' diagnostics.
Specifically: 1. We error now if one transfers an 'inout sending' parameter and does not reinitialize it before the end of the function. 2. We error now if one merges an 'inout sending' parameter into an actor isolated region and do not reinitialize it with a non-actor isolated value before the end of the function. rdar://126303739
1 parent bd06c82 commit 6fe7496

File tree

8 files changed

+464
-14
lines changed

8 files changed

+464
-14
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -943,6 +943,9 @@ NOTE(sil_referencebinding_inout_binding_here, none,
943943

944944
NOTE(regionbasedisolation_maybe_race, none,
945945
"access can happen concurrently", ())
946+
NOTE(regionbasedisolation_inout_sending_must_be_reinitialized, none,
947+
"'inout sending' parameter must be reinitialized before function exit with a non-actor isolated value",
948+
())
946949
ERROR(regionbasedisolation_unknown_pattern, none,
947950
"pattern that the region based isolation checker does not understand how to check. Please file a bug",
948951
())
@@ -972,6 +975,9 @@ ERROR(regionbasedisolation_arg_passed_to_strongly_transferred_param, none,
972975
NOTE(regionbasedisolation_isolated_since_in_same_region_basename, none,
973976
"value is %0 since it is in the same region as %1",
974977
(StringRef, DeclBaseName))
978+
NOTE(regionbasedisolation_isolated_since_in_same_region_string, none,
979+
"%0 is %1since it is in the same region as %2",
980+
(Identifier, StringRef, Identifier))
975981

976982
//===---
977983
// New Transfer Non Sendable Diagnostics
@@ -981,6 +987,13 @@ ERROR(regionbasedisolation_named_transfer_yields_race, none,
981987
"sending %0 risks causing data races",
982988
(Identifier))
983989

990+
ERROR(regionbasedisolation_inout_sending_cannot_be_actor_isolated, none,
991+
"'inout sending' parameter %0 cannot be %1at end of function",
992+
(Identifier, StringRef))
993+
NOTE(regionbasedisolation_inout_sending_cannot_be_actor_isolated_note, none,
994+
"%1%0 risks causing races in between %1uses and caller uses since caller assumes value is not actor isolated",
995+
(Identifier, StringRef))
996+
984997
NOTE(regionbasedisolation_named_info_transfer_yields_race, none,
985998
"sending %1%0 to %2 callee risks causing data races between %2 and local %3 uses",
986999
(Identifier, StringRef, ActorIsolation, ActorIsolation))

include/swift/SILOptimizer/Utils/PartitionUtils.h

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,15 @@ enum class PartitionOpKind : uint8_t {
410410
///
411411
/// This is used if we need to reject the program and do not want to assert.
412412
UnknownPatternError,
413+
414+
/// Require that a 'inout sending' parameter's region is not transferred and
415+
/// disconnected at a specific function exiting term inst.
416+
///
417+
/// This ensures that if users transfer away an inout sending parameter, the
418+
/// parameter is reinitialized with a disconnected value.
419+
///
420+
/// Takes one parameter, the inout parameter that we need to check.
421+
RequireInOutSendingAtFunctionExit,
413422
};
414423

415424
/// PartitionOp represents a primitive operation that can be performed on
@@ -494,6 +503,12 @@ class PartitionOp {
494503
return PartitionOp(PartitionOpKind::UnknownPatternError, elt, sourceInst);
495504
}
496505

506+
static PartitionOp
507+
RequireInOutSendingAtFunctionExit(Element elt, SILInstruction *sourceInst) {
508+
return PartitionOp(PartitionOpKind::RequireInOutSendingAtFunctionExit, elt,
509+
sourceInst);
510+
}
511+
497512
bool operator==(const PartitionOp &other) const {
498513
return opKind == other.opKind && opArgs == other.opArgs &&
499514
source == other.source;
@@ -925,6 +940,21 @@ struct PartitionOpEvaluator {
925940
isolationRegionInfo);
926941
}
927942

943+
/// Call our CRTP subclass.
944+
void handleInOutSendingNotInitializedAtExitError(
945+
const PartitionOp &op, Element elt, Operand *transferringOp) const {
946+
return asImpl().handleInOutSendingNotInitializedAtExitError(op, elt,
947+
transferringOp);
948+
}
949+
950+
/// Call our CRTP subclass.
951+
void handleInOutSendingNotDisconnectedAtExitError(
952+
const PartitionOp &op, Element elt,
953+
SILDynamicMergedIsolationInfo isolation) const {
954+
return asImpl().handleInOutSendingNotDisconnectedAtExitError(op, elt,
955+
isolation);
956+
}
957+
928958
/// Call isActorDerived on our CRTP subclass.
929959
bool isActorDerived(Element elt) const {
930960
return asImpl().isActorDerived(elt);
@@ -959,8 +989,10 @@ struct PartitionOpEvaluator {
959989
}
960990

961991
/// Overload of \p getIsolationRegionInfo without an Operand.
962-
SILIsolationInfo getIsolationRegionInfo(Region region) const {
963-
return getIsolationRegionInfo(region, nullptr).first;
992+
SILDynamicMergedIsolationInfo getIsolationRegionInfo(Region region) const {
993+
if (auto opt = getIsolationRegionInfo(region, nullptr))
994+
return opt->first;
995+
return SILDynamicMergedIsolationInfo();
964996
}
965997

966998
bool isTaskIsolatedDerived(Element elt) const {
@@ -1128,6 +1160,41 @@ struct PartitionOpEvaluator {
11281160
}
11291161
}
11301162
return;
1163+
case PartitionOpKind::RequireInOutSendingAtFunctionExit: {
1164+
assert(op.getOpArgs().size() == 1 &&
1165+
"Require PartitionOp should be passed 1 argument");
1166+
assert(p.isTrackingElement(op.getOpArgs()[0]) &&
1167+
"Require PartitionOp's argument should already be tracked");
1168+
1169+
// First check if the region of our 'inout sending' element has been
1170+
// transferred. In that case, we emit a special use after free error.
1171+
if (auto *transferredOperandSet = p.getTransferred(op.getOpArgs()[0])) {
1172+
for (auto transferredOperand : transferredOperandSet->data()) {
1173+
handleInOutSendingNotInitializedAtExitError(op, op.getOpArgs()[0],
1174+
transferredOperand);
1175+
}
1176+
return;
1177+
}
1178+
1179+
// If we were not transferred, check if our region is actor isolated. If
1180+
// so, error since we need a disconnected value in the inout parameter.
1181+
Region inoutSendingRegion = p.getRegion(op.getOpArgs()[0]);
1182+
auto dynamicRegionIsolation = getIsolationRegionInfo(inoutSendingRegion);
1183+
1184+
// If we failed to merge emit an unknown pattern error so we fail.
1185+
if (!dynamicRegionIsolation) {
1186+
handleUnknownCodePattern(op);
1187+
return;
1188+
}
1189+
1190+
// Otherwise, emit the error if the dynamic region isolation is not
1191+
// disconnected.
1192+
if (!dynamicRegionIsolation.isDisconnected()) {
1193+
handleInOutSendingNotDisconnectedAtExitError(op, op.getOpArgs()[0],
1194+
dynamicRegionIsolation);
1195+
}
1196+
return;
1197+
}
11311198
case PartitionOpKind::UnknownPatternError:
11321199
// Begin tracking the specified element in case we have a later use.
11331200
p.trackNewElement(op.getOpArgs()[0]);
@@ -1315,6 +1382,16 @@ struct PartitionOpEvaluatorBaseImpl : PartitionOpEvaluator<Subclass> {
13151382
/// to our user so that we can emit that error as we process.
13161383
void handleUnknownCodePattern(const PartitionOp &op) const {}
13171384

1385+
/// Called if we find an 'inout sending' parameter that is not live at exit.
1386+
void handleInOutSendingNotInitializedAtExitError(
1387+
const PartitionOp &op, Element elt, Operand *transferringOp) const {}
1388+
1389+
/// Called if we find an 'inout sending' parameter that is live at excit but
1390+
/// is actor isolated instead of disconnected.
1391+
void handleInOutSendingNotDisconnectedAtExitError(
1392+
const PartitionOp &op, Element elt,
1393+
SILDynamicMergedIsolationInfo actorIsolation) const {}
1394+
13181395
/// This is used to determine if an element is actor derived. If we determine
13191396
/// that a region containing such an element is transferred, we emit an error
13201397
/// since actor regions cannot be transferred.

include/swift/SILOptimizer/Utils/SILIsolationInfo.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ class ActorInstance {
8181
return value.getPointer();
8282
}
8383

84+
SILValue maybeGetValue() const {
85+
if (getKind() != Kind::Value)
86+
return SILValue();
87+
return getValue();
88+
}
89+
8490
bool isValue() const { return getKind() == Kind::Value; }
8591

8692
bool isAccessorInit() const { return getKind() == Kind::ActorAccessorInit; }
@@ -389,7 +395,7 @@ class SILDynamicMergedIsolationInfo {
389395

390396
public:
391397
SILDynamicMergedIsolationInfo() : innerInfo() {}
392-
SILDynamicMergedIsolationInfo(SILIsolationInfo innerInfo)
398+
explicit SILDynamicMergedIsolationInfo(SILIsolationInfo innerInfo)
393399
: innerInfo(innerInfo) {}
394400

395401
/// Returns nullptr only if both this isolation info and \p other are actor
@@ -404,6 +410,8 @@ class SILDynamicMergedIsolationInfo {
404410

405411
operator bool() const { return bool(innerInfo); }
406412

413+
SILIsolationInfo *operator->() { return &innerInfo; }
414+
407415
SILIsolationInfo getIsolationInfo() const { return innerInfo; }
408416

409417
bool isDisconnected() const { return innerInfo.isDisconnected(); }
@@ -412,6 +420,12 @@ class SILDynamicMergedIsolationInfo {
412420
return innerInfo.hasSameIsolation(other);
413421
}
414422

423+
static SILDynamicMergedIsolationInfo
424+
getDisconnected(bool isUnsafeNonIsolated) {
425+
return SILDynamicMergedIsolationInfo(
426+
SILIsolationInfo::getDisconnected(isUnsafeNonIsolated));
427+
}
428+
415429
SWIFT_DEBUG_DUMP { innerInfo.dump(); }
416430

417431
void printForDiagnostics(llvm::raw_ostream &os) const {

lib/SILOptimizer/Analysis/RegionAnalysis.cpp

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,6 +1205,14 @@ struct PartitionOpBuilder {
12051205
PartitionOp::Require(lookupValueID(value), currentInst));
12061206
}
12071207

1208+
void addRequireInOutSendingAtFunctionExit(SILValue value) {
1209+
assert(valueHasID(value, /*dumpIfHasNoID=*/true) &&
1210+
"required value should already have been encountered");
1211+
currentInstPartitionOps.emplace_back(
1212+
PartitionOp::RequireInOutSendingAtFunctionExit(lookupValueID(value),
1213+
currentInst));
1214+
}
1215+
12081216
void addUnknownPatternError(SILValue value) {
12091217
if (AbortOnUnknownPatternMatchError) {
12101218
llvm::report_fatal_error(
@@ -1604,9 +1612,9 @@ class PartitionOpTranslator {
16041612
// compiler exits successfully, actor merge errors could not have happened.
16051613
std::optional<SILDynamicMergedIsolationInfo> mergedInfo;
16061614
if (resultIsolationInfoOverride) {
1607-
mergedInfo = resultIsolationInfoOverride;
1615+
mergedInfo = SILDynamicMergedIsolationInfo(resultIsolationInfoOverride);
16081616
} else {
1609-
mergedInfo = SILIsolationInfo::getDisconnected(false);
1617+
mergedInfo = SILDynamicMergedIsolationInfo::getDisconnected(false);
16101618
}
16111619

16121620
for (SILValue src : sourceValues) {
@@ -2321,6 +2329,22 @@ class PartitionOpTranslator {
23212329
#define INST(INST, PARENT) TranslationSemantics visit##INST(INST *inst);
23222330
#include "swift/SIL/SILNodes.def"
23232331

2332+
/// Adds requires for all sending inout parameters to make sure that they are
2333+
/// properly updated before the end of the function.
2334+
void addRequiresForInOutParameters(TermInst *inst) {
2335+
assert(inst->isFunctionExiting() && "Must be function exiting term inst?!");
2336+
for (auto *arg : inst->getFunction()->getArguments()) {
2337+
auto *fArg = cast<SILFunctionArgument>(arg);
2338+
if (fArg->getArgumentConvention().isInoutConvention() &&
2339+
fArg->getKnownParameterInfo().hasOption(SILParameterInfo::Sending)) {
2340+
if (auto ns = tryToTrackValue(arg)) {
2341+
auto rep = ns->getRepresentative().getValue();
2342+
builder.addRequireInOutSendingAtFunctionExit(rep);
2343+
}
2344+
}
2345+
}
2346+
}
2347+
23242348
/// Top level switch that translates SIL instructions.
23252349
void translateSILInstruction(SILInstruction *inst) {
23262350
builder.reset(inst);
@@ -2710,12 +2734,8 @@ CONSTANT_TRANSLATION(CondFailInst, Ignored)
27102734
// function_ref/class_method which are considered sendable.
27112735
CONSTANT_TRANSLATION(SwitchValueInst, Ignored)
27122736
CONSTANT_TRANSLATION(UnreachableInst, Ignored)
2713-
CONSTANT_TRANSLATION(UnwindInst, Ignored)
2714-
// Doesn't take a parameter.
2715-
CONSTANT_TRANSLATION(ThrowAddrInst, Ignored)
27162737

27172738
// Terminators that only need require.
2718-
CONSTANT_TRANSLATION(ThrowInst, Require)
27192739
CONSTANT_TRANSLATION(SwitchEnumAddrInst, Require)
27202740
CONSTANT_TRANSLATION(YieldInst, Require)
27212741

@@ -2725,6 +2745,33 @@ CONSTANT_TRANSLATION(CondBranchInst, TerminatorPhi)
27252745
CONSTANT_TRANSLATION(CheckedCastBranchInst, TerminatorPhi)
27262746
CONSTANT_TRANSLATION(DynamicMethodBranchInst, TerminatorPhi)
27272747

2748+
// Function exiting terminators.
2749+
//
2750+
// We handle these especially since we want to make sure that inout parameters
2751+
// that are transferred are forced to be reinitialized.
2752+
//
2753+
// There is an assert in TermInst::isFunctionExiting that makes sure we do this
2754+
// correctly.
2755+
//
2756+
// NOTE: We purposely do not require reinitialization along paths that end in
2757+
// unreachable.
2758+
#ifdef FUNCTION_EXITING_TERMINATOR_TRANSLATION
2759+
#error "FUNCTION_EXITING_TERMINATOR_TRANSLATION already defined?!"
2760+
#endif
2761+
2762+
#define FUNCTION_EXITING_TERMINATOR_CONSTANT(INST, Kind) \
2763+
TranslationSemantics PartitionOpTranslator::visit##INST(INST *inst) { \
2764+
assert(inst->isFunctionExiting() && "Must be function exiting?!"); \
2765+
addRequiresForInOutParameters(inst); \
2766+
return TranslationSemantics::Kind; \
2767+
}
2768+
2769+
FUNCTION_EXITING_TERMINATOR_CONSTANT(UnwindInst, Ignored)
2770+
FUNCTION_EXITING_TERMINATOR_CONSTANT(ThrowAddrInst, Ignored)
2771+
FUNCTION_EXITING_TERMINATOR_CONSTANT(ThrowInst, Require)
2772+
2773+
#undef FUNCTION_EXITING_TERMINATOR_CONSTANT
2774+
27282775
// Today, await_async_continuation just takes Sendable values
27292776
// (UnsafeContinuation and UnsafeThrowingContinuation).
27302777
CONSTANT_TRANSLATION(AwaitAsyncContinuationInst, AssertingIfNonSendable)
@@ -2961,6 +3008,7 @@ PartitionOpTranslator::visitLoadBorrowInst(LoadBorrowInst *lbi) {
29613008
}
29623009

29633010
TranslationSemantics PartitionOpTranslator::visitReturnInst(ReturnInst *ri) {
3011+
addRequiresForInOutParameters(ri);
29643012
if (ri->getFunction()->getLoweredFunctionType()->hasSendingResult()) {
29653013
return TranslationSemantics::TransferringNoResult;
29663014
}

0 commit comments

Comments
 (0)