Skip to content

Commit ba8b900

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 (cherry picked from commit 6fe7496)
1 parent bec55c3 commit ba8b900

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
@@ -939,6 +939,9 @@ NOTE(sil_referencebinding_inout_binding_here, none,
939939

940940
NOTE(regionbasedisolation_maybe_race, none,
941941
"access can happen concurrently", ())
942+
NOTE(regionbasedisolation_inout_sending_must_be_reinitialized, none,
943+
"'inout sending' parameter must be reinitialized before function exit with a non-actor isolated value",
944+
())
942945
ERROR(regionbasedisolation_unknown_pattern, none,
943946
"pattern that the region based isolation checker does not understand how to check. Please file a bug",
944947
())
@@ -968,6 +971,9 @@ ERROR(regionbasedisolation_arg_passed_to_strongly_transferred_param, none,
968971
NOTE(regionbasedisolation_isolated_since_in_same_region_basename, none,
969972
"value is %0 since it is in the same region as %1",
970973
(StringRef, DeclBaseName))
974+
NOTE(regionbasedisolation_isolated_since_in_same_region_string, none,
975+
"%0 is %1since it is in the same region as %2",
976+
(Identifier, StringRef, Identifier))
971977

972978
//===---
973979
// New Transfer Non Sendable Diagnostics
@@ -977,6 +983,13 @@ ERROR(regionbasedisolation_named_transfer_yields_race, none,
977983
"sending %0 risks causing data races",
978984
(Identifier))
979985

986+
ERROR(regionbasedisolation_inout_sending_cannot_be_actor_isolated, none,
987+
"'inout sending' parameter %0 cannot be %1at end of function",
988+
(Identifier, StringRef))
989+
NOTE(regionbasedisolation_inout_sending_cannot_be_actor_isolated_note, none,
990+
"%1%0 risks causing races in between %1uses and caller uses since caller assumes value is not actor isolated",
991+
(Identifier, StringRef))
992+
980993
NOTE(regionbasedisolation_named_info_transfer_yields_race, none,
981994
"sending %1%0 to %2 callee risks causing data races between %2 and local %3 uses",
982995
(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 {
@@ -1150,6 +1182,41 @@ struct PartitionOpEvaluator {
11501182
}
11511183
}
11521184
return;
1185+
case PartitionOpKind::RequireInOutSendingAtFunctionExit: {
1186+
assert(op.getOpArgs().size() == 1 &&
1187+
"Require PartitionOp should be passed 1 argument");
1188+
assert(p.isTrackingElement(op.getOpArgs()[0]) &&
1189+
"Require PartitionOp's argument should already be tracked");
1190+
1191+
// First check if the region of our 'inout sending' element has been
1192+
// transferred. In that case, we emit a special use after free error.
1193+
if (auto *transferredOperandSet = p.getTransferred(op.getOpArgs()[0])) {
1194+
for (auto transferredOperand : transferredOperandSet->data()) {
1195+
handleInOutSendingNotInitializedAtExitError(op, op.getOpArgs()[0],
1196+
transferredOperand);
1197+
}
1198+
return;
1199+
}
1200+
1201+
// If we were not transferred, check if our region is actor isolated. If
1202+
// so, error since we need a disconnected value in the inout parameter.
1203+
Region inoutSendingRegion = p.getRegion(op.getOpArgs()[0]);
1204+
auto dynamicRegionIsolation = getIsolationRegionInfo(inoutSendingRegion);
1205+
1206+
// If we failed to merge emit an unknown pattern error so we fail.
1207+
if (!dynamicRegionIsolation) {
1208+
handleUnknownCodePattern(op);
1209+
return;
1210+
}
1211+
1212+
// Otherwise, emit the error if the dynamic region isolation is not
1213+
// disconnected.
1214+
if (!dynamicRegionIsolation.isDisconnected()) {
1215+
handleInOutSendingNotDisconnectedAtExitError(op, op.getOpArgs()[0],
1216+
dynamicRegionIsolation);
1217+
}
1218+
return;
1219+
}
11531220
case PartitionOpKind::UnknownPatternError:
11541221
// Begin tracking the specified element in case we have a later use.
11551222
p.trackNewElement(op.getOpArgs()[0]);
@@ -1337,6 +1404,16 @@ struct PartitionOpEvaluatorBaseImpl : PartitionOpEvaluator<Subclass> {
13371404
/// to our user so that we can emit that error as we process.
13381405
void handleUnknownCodePattern(const PartitionOp &op) const {}
13391406

1407+
/// Called if we find an 'inout sending' parameter that is not live at exit.
1408+
void handleInOutSendingNotInitializedAtExitError(
1409+
const PartitionOp &op, Element elt, Operand *transferringOp) const {}
1410+
1411+
/// Called if we find an 'inout sending' parameter that is live at excit but
1412+
/// is actor isolated instead of disconnected.
1413+
void handleInOutSendingNotDisconnectedAtExitError(
1414+
const PartitionOp &op, Element elt,
1415+
SILDynamicMergedIsolationInfo actorIsolation) const {}
1416+
13401417
/// This is used to determine if an element is actor derived. If we determine
13411418
/// that a region containing such an element is transferred, we emit an error
13421419
/// 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
@@ -1196,6 +1196,14 @@ struct PartitionOpBuilder {
11961196
PartitionOp::Require(lookupValueID(value), currentInst));
11971197
}
11981198

1199+
void addRequireInOutSendingAtFunctionExit(SILValue value) {
1200+
assert(valueHasID(value, /*dumpIfHasNoID=*/true) &&
1201+
"required value should already have been encountered");
1202+
currentInstPartitionOps.emplace_back(
1203+
PartitionOp::RequireInOutSendingAtFunctionExit(lookupValueID(value),
1204+
currentInst));
1205+
}
1206+
11991207
void addUnknownPatternError(SILValue value) {
12001208
if (AbortOnUnknownPatternMatchError) {
12011209
llvm::report_fatal_error(
@@ -1594,9 +1602,9 @@ class PartitionOpTranslator {
15941602
// compiler exits successfully, actor merge errors could not have happened.
15951603
std::optional<SILDynamicMergedIsolationInfo> mergedInfo;
15961604
if (resultIsolationInfoOverride) {
1597-
mergedInfo = resultIsolationInfoOverride;
1605+
mergedInfo = SILDynamicMergedIsolationInfo(resultIsolationInfoOverride);
15981606
} else {
1599-
mergedInfo = SILIsolationInfo::getDisconnected(false);
1607+
mergedInfo = SILDynamicMergedIsolationInfo::getDisconnected(false);
16001608
}
16011609

16021610
for (SILValue src : sourceValues) {
@@ -2296,6 +2304,22 @@ class PartitionOpTranslator {
22962304
#define INST(INST, PARENT) TranslationSemantics visit##INST(INST *inst);
22972305
#include "swift/SIL/SILNodes.def"
22982306

2307+
/// Adds requires for all sending inout parameters to make sure that they are
2308+
/// properly updated before the end of the function.
2309+
void addRequiresForInOutParameters(TermInst *inst) {
2310+
assert(inst->isFunctionExiting() && "Must be function exiting term inst?!");
2311+
for (auto *arg : inst->getFunction()->getArguments()) {
2312+
auto *fArg = cast<SILFunctionArgument>(arg);
2313+
if (fArg->getArgumentConvention().isInoutConvention() &&
2314+
fArg->getKnownParameterInfo().hasOption(SILParameterInfo::Sending)) {
2315+
if (auto ns = tryToTrackValue(arg)) {
2316+
auto rep = ns->getRepresentative().getValue();
2317+
builder.addRequireInOutSendingAtFunctionExit(rep);
2318+
}
2319+
}
2320+
}
2321+
}
2322+
22992323
/// Top level switch that translates SIL instructions.
23002324
void translateSILInstruction(SILInstruction *inst) {
23012325
builder.reset(inst);
@@ -2684,12 +2708,8 @@ CONSTANT_TRANSLATION(CondFailInst, Ignored)
26842708
// function_ref/class_method which are considered sendable.
26852709
CONSTANT_TRANSLATION(SwitchValueInst, Ignored)
26862710
CONSTANT_TRANSLATION(UnreachableInst, Ignored)
2687-
CONSTANT_TRANSLATION(UnwindInst, Ignored)
2688-
// Doesn't take a parameter.
2689-
CONSTANT_TRANSLATION(ThrowAddrInst, Ignored)
26902711

26912712
// Terminators that only need require.
2692-
CONSTANT_TRANSLATION(ThrowInst, Require)
26932713
CONSTANT_TRANSLATION(SwitchEnumAddrInst, Require)
26942714
CONSTANT_TRANSLATION(YieldInst, Require)
26952715

@@ -2699,6 +2719,33 @@ CONSTANT_TRANSLATION(CondBranchInst, TerminatorPhi)
26992719
CONSTANT_TRANSLATION(CheckedCastBranchInst, TerminatorPhi)
27002720
CONSTANT_TRANSLATION(DynamicMethodBranchInst, TerminatorPhi)
27012721

2722+
// Function exiting terminators.
2723+
//
2724+
// We handle these especially since we want to make sure that inout parameters
2725+
// that are transferred are forced to be reinitialized.
2726+
//
2727+
// There is an assert in TermInst::isFunctionExiting that makes sure we do this
2728+
// correctly.
2729+
//
2730+
// NOTE: We purposely do not require reinitialization along paths that end in
2731+
// unreachable.
2732+
#ifdef FUNCTION_EXITING_TERMINATOR_TRANSLATION
2733+
#error "FUNCTION_EXITING_TERMINATOR_TRANSLATION already defined?!"
2734+
#endif
2735+
2736+
#define FUNCTION_EXITING_TERMINATOR_CONSTANT(INST, Kind) \
2737+
TranslationSemantics PartitionOpTranslator::visit##INST(INST *inst) { \
2738+
assert(inst->isFunctionExiting() && "Must be function exiting?!"); \
2739+
addRequiresForInOutParameters(inst); \
2740+
return TranslationSemantics::Kind; \
2741+
}
2742+
2743+
FUNCTION_EXITING_TERMINATOR_CONSTANT(UnwindInst, Ignored)
2744+
FUNCTION_EXITING_TERMINATOR_CONSTANT(ThrowAddrInst, Ignored)
2745+
FUNCTION_EXITING_TERMINATOR_CONSTANT(ThrowInst, Require)
2746+
2747+
#undef FUNCTION_EXITING_TERMINATOR_CONSTANT
2748+
27022749
// Today, await_async_continuation just takes Sendable values
27032750
// (UnsafeContinuation and UnsafeThrowingContinuation).
27042751
CONSTANT_TRANSLATION(AwaitAsyncContinuationInst, AssertingIfNonSendable)
@@ -2897,6 +2944,7 @@ PartitionOpTranslator::visitLoadBorrowInst(LoadBorrowInst *lbi) {
28972944
}
28982945

28992946
TranslationSemantics PartitionOpTranslator::visitReturnInst(ReturnInst *ri) {
2947+
addRequiresForInOutParameters(ri);
29002948
if (ri->getFunction()->getLoweredFunctionType()->hasSendingResult()) {
29012949
return TranslationSemantics::TransferringNoResult;
29022950
}

0 commit comments

Comments
 (0)