Skip to content

Commit 864105c

Browse files
committed
Add OSSA invariants for forwarding terminators
Enforce OSSA invariants for blocks terminators such as switch_enum and checked_cast_br, along with any other block terminator that forwards an operand to its results. Failing to fully enforce can result in leaks, use-after-free and other verifier errors after CFG transformation. For switch_enum sanity, consistently handle cases with no payload. Remove special case behavior for the default block.
1 parent a5b1b5c commit 864105c

File tree

1 file changed

+112
-78
lines changed

1 file changed

+112
-78
lines changed

lib/SIL/Verifier/SILVerifier.cpp

Lines changed: 112 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1186,11 +1186,49 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
11861186
}
11871187
}
11881188

1189-
if (OwnershipForwardingMixin::isa(I)) {
1189+
if (I->getFunction()->hasOwnership() && OwnershipForwardingMixin::isa(I)) {
11901190
checkOwnershipForwardingInst(I);
11911191
}
11921192
}
11931193

1194+
// Verify the result of any forwarding terminator, including switch_enum.
1195+
void checkForwardedTermResult(OwnershipForwardingTermInst *term,
1196+
SILBasicBlock *destBB) {
1197+
require(destBB->getNumArguments() == 1,
1198+
"OwnershipForwardingTermInst needs a single result");
1199+
auto *arg = destBB->getArgument(0);
1200+
auto argKind = arg->getOwnershipKind();
1201+
// A terminator may cast a nontrivial to a trivial type. e.g. a trivial
1202+
// payload in a nontrivial enum. If the result is trivial, it no longer
1203+
// requires ownership.
1204+
if (arg->getType().isTrivial(F) && argKind == OwnershipKind::None)
1205+
return;
1206+
1207+
require(argKind == term->getForwardingOwnershipKind(),
1208+
"OwnershipForwardingTermInst nontrivial result "
1209+
"must have the same ownership");
1210+
}
1211+
1212+
/// A terminator result is forwarded from the terminator's operand. Forwarding
1213+
/// a nontrivial value to another nontrivial value can never gain or lose
1214+
/// ownership information. If the terminator's operand has ownership and the
1215+
/// result is nontrivial, then the result must have identical ownership.
1216+
///
1217+
/// The terminator result can only "lose" ownership if the operand is
1218+
/// nontrivial but the result is trivial. It is still valid for the trivial
1219+
/// result to retain the operand's ownership, but unnecessary and produces
1220+
/// less efficient SIL.
1221+
void checkOwnershipForwardingTermInst(OwnershipForwardingTermInst *term) {
1222+
// Verifying switch_enum ownership requires evaluating the payload
1223+
// types. These are fully verified by checkSwitchEnumInst.
1224+
if (isa<SwitchEnumInst>(term))
1225+
return;
1226+
1227+
for (auto &succ : term->getSuccessors()) {
1228+
checkForwardedTermResult(term, succ.getBB());
1229+
}
1230+
}
1231+
11941232
/// We are given an instruction \p fInst that forwards ownership from \p
11951233
/// operand to one of \p fInst's results, make sure that if we have a
11961234
/// forwarding instruction that can only accept owned or guaranteed ownership
@@ -1209,6 +1247,10 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
12091247
"GuaranteedFirstArgForwardingSingleValueInst's ownership kind "
12101248
"must be compatible with guaranteed");
12111249
}
1250+
1251+
if (auto *term = dyn_cast<OwnershipForwardingTermInst>(i)) {
1252+
checkOwnershipForwardingTermInst(term);
1253+
}
12121254
}
12131255

12141256
void checkDebugVariable(SILInstruction *inst) {
@@ -4503,104 +4545,96 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
45034545
checkSelectValueCases(SVI);
45044546
}
45054547

4506-
void checkSwitchEnumInst(SwitchEnumInst *SOI) {
4507-
require(SOI->getOperand()->getType().isObject(),
4548+
void checkSwitchEnumInst(SwitchEnumInst *switchEnum) {
4549+
require(switchEnum->getOperand()->getType().isObject(),
45084550
"switch_enum operand must be an object");
45094551

4510-
SILType uTy = SOI->getOperand()->getType();
4552+
SILType uTy = switchEnum->getOperand()->getType();
45114553
EnumDecl *uDecl = uTy.getEnumOrBoundGenericEnum();
45124554
require(uDecl, "switch_enum operand is not an enum");
45134555

45144556
// Find the set of enum elements for the type so we can verify
45154557
// exhaustiveness.
45164558
llvm::DenseSet<EnumElementDecl*> unswitchedElts;
4517-
uDecl->getAllElements(unswitchedElts);
4518-
4519-
// Verify the set of enum cases we dispatch on.
4520-
for (unsigned i = 0, e = SOI->getNumCases(); i < e; ++i) {
4521-
EnumElementDecl *elt;
4522-
SILBasicBlock *dest;
4523-
std::tie(elt, dest) = SOI->getCase(i);
45244559

4560+
auto checkSwitchCase = [&](EnumElementDecl *elt, SILBasicBlock *dest) {
45254561
require(elt->getDeclContext() == uDecl,
45264562
"switch_enum dispatches on enum element that is not part of "
45274563
"its type");
4528-
require(unswitchedElts.count(elt),
4564+
require(unswitchedElts.insert(elt).second,
45294565
"switch_enum dispatches on same enum element more than once");
4530-
unswitchedElts.erase(elt);
4531-
4532-
// The destination BB can take the argument payload, if any, as a BB
4533-
// arguments, or it can ignore it and take no arguments.
4534-
if (elt->hasAssociatedValues()) {
4535-
if (isSILOwnershipEnabled() && F.hasOwnership()) {
4536-
require(dest->getArguments().size() == 1,
4537-
"switch_enum destination for case w/ args must take 1 "
4538-
"argument");
4539-
if (!dest->getArgument(0)->getType().isTrivial(*SOI->getFunction())) {
4540-
require(
4541-
dest->getArgument(0)->getOwnershipKind().isCompatibleWith(
4542-
SOI->getForwardingOwnershipKind()),
4543-
"Switch enum non-trivial destination arg must have ownership "
4544-
"kind that is compatible with the switch_enum's operand");
4545-
}
4546-
} else {
4547-
require(dest->getArguments().empty() ||
4548-
dest->getArguments().size() == 1,
4549-
"switch_enum destination for case w/ args must take 0 or 1 "
4550-
"arguments");
4551-
}
4552-
4553-
if (dest->getArguments().size() == 1) {
4554-
SILType eltArgTy = uTy.getEnumElementType(elt, F.getModule(),
4555-
F.getTypeExpansionContext());
4556-
SILType bbArgTy = dest->getArguments()[0]->getType();
4557-
if (F.getModule().getStage() != SILStage::Lowered) {
4558-
// During the lowered stage, a function type might have different
4559-
// signature
4560-
require(eltArgTy == bbArgTy,
4561-
"switch_enum destination bbarg must match case arg type");
4562-
}
4563-
require(!dest->getArguments()[0]->getType().isAddress(),
4564-
"switch_enum destination bbarg type must not be an address");
4565-
}
45664566

4567-
} else {
4567+
if (!elt->hasAssociatedValues()) {
45684568
require(dest->getArguments().empty(),
45694569
"switch_enum destination for no-argument case must take no "
45704570
"arguments");
4571+
return;
4572+
}
4573+
// Check for a valid switch result type.
4574+
if (dest->getArguments().size() == 1) {
4575+
SILType eltArgTy = uTy.getEnumElementType(elt, F.getModule(),
4576+
F.getTypeExpansionContext());
4577+
SILType bbArgTy = dest->getArguments()[0]->getType();
4578+
if (F.getModule().getStage() != SILStage::Lowered) {
4579+
// During the lowered stage, a function type might have different
4580+
// signature
4581+
require(eltArgTy == bbArgTy,
4582+
"switch_enum destination bbarg must match case arg type");
4583+
}
4584+
require(!dest->getArguments()[0]->getType().isAddress(),
4585+
"switch_enum destination bbarg type must not be an address");
4586+
}
4587+
if (!isSILOwnershipEnabled() || !F.hasOwnership()) {
4588+
// In non-OSSA, the destBB can optionally ignore the payload.
4589+
require(dest->getArguments().empty()
4590+
|| dest->getArguments().size() == 1,
4591+
"switch_enum destination for case w/ args must take 0 or 1 "
4592+
"arguments");
4593+
return;
45714594
}
4595+
checkForwardedTermResult(switchEnum, dest);
4596+
};
4597+
// Verify the set of enum cases we dispatch on.
4598+
for (unsigned i = 0, e = switchEnum->getNumCases(); i < e; ++i) {
4599+
EnumElementDecl *elt;
4600+
SILBasicBlock *dest;
4601+
std::tie(elt, dest) = switchEnum->getCase(i);
4602+
checkSwitchCase(elt, dest);
45724603
}
4573-
45744604
// If the switch is non-exhaustive, we require a default.
4575-
bool isExhaustive =
4576-
uDecl->isEffectivelyExhaustive(F.getModule().getSwiftModule(),
4577-
F.getResilienceExpansion());
4578-
require((isExhaustive && unswitchedElts.empty()) || SOI->hasDefault(),
4579-
"nonexhaustive switch_enum must have a default destination");
4580-
if (SOI->hasDefault()) {
4581-
// When SIL ownership is enabled, we require all default branches to take
4582-
// an @owned original version of the enum.
4583-
//
4584-
// When SIL ownership is disabled, we no longer support this.
4585-
if (isSILOwnershipEnabled() && F.hasOwnership()) {
4586-
require(SOI->getDefaultBB()->getNumArguments() == 1,
4587-
"Switch enum default block should have one argument");
4588-
requireSameType(
4589-
SOI->getDefaultBB()->getArgument(0)->getType(),
4590-
SOI->getOperand()->getType(),
4591-
"Switch enum default block should have one argument that is "
4592-
"the same as the input type");
4593-
auto defaultKind =
4594-
SOI->getDefaultBB()->getArgument(0)->getOwnershipKind();
4595-
require(
4596-
defaultKind.isCompatibleWith(SOI->getOperand().getOwnershipKind()),
4597-
"Switch enum default block arg must have same ownership kind "
4598-
"as operand");
4599-
} else if (!F.hasOwnership()) {
4600-
require(SOI->getDefaultBB()->args_empty(),
4601-
"switch_enum default destination must take no arguments");
4602-
}
4605+
if (!switchEnum->hasDefault()) {
4606+
bool isExhaustive = uDecl->isEffectivelyExhaustive(
4607+
F.getModule().getSwiftModule(), F.getResilienceExpansion());
4608+
require(isExhaustive
4609+
&& (unswitchedElts.size() == uDecl->getNumElements()),
4610+
"nonexhaustive switch_enum must have a default destination");
4611+
return;
4612+
}
4613+
auto *defaultBB = switchEnum->getDefaultBB();
4614+
if (!isSILOwnershipEnabled() || !F.hasOwnership()) {
4615+
require(switchEnum->getDefaultBB()->args_empty(),
4616+
"switch_enum default destination must take no arguments");
4617+
return;
46034618
}
4619+
// When the switch has a unique default case, the OSSA result has the same
4620+
// requirements as a matched result.
4621+
if (NullablePtr<EnumElementDecl> uniqueCase =
4622+
switchEnum->getUniqueCaseForDefault()) {
4623+
checkSwitchCase(uniqueCase.get(), defaultBB);
4624+
return;
4625+
}
4626+
// With no unique case, the switch_enum operand is simply forwarded.
4627+
require(defaultBB->getNumArguments() == 1,
4628+
"Switch enum default block should have one argument");
4629+
requireSameType(
4630+
defaultBB->getArgument(0)->getType(),
4631+
switchEnum->getOperand()->getType(),
4632+
"Switch enum default block should have one argument that is "
4633+
"the same as the input type");
4634+
require(defaultBB->getArgument(0)->getOwnershipKind()
4635+
== switchEnum->getForwardingOwnershipKind(),
4636+
"switch_enum non-trivial destination arg must have the same "
4637+
"ownership as switch_enum's operand");
46044638
}
46054639

46064640
void checkSwitchEnumAddrInst(SwitchEnumAddrInst *SOI) {

0 commit comments

Comments
 (0)