Skip to content

Commit 32232c6

Browse files
committed
Package optimization allows bypassing resilience, but that assumes the memory layout of the
decl being accessed is correct. When this assumption fails due to a deserialization error of its members, the use site accesses the layout with a wrong field offset, resulting in UB or a crash. The deserialization error is currently not caught at compile time due to LangOpts.EnableDeserializationRecovery being enabled by default to allow for recovery of some of the deserialization errors at a later time. In case of member deserialization, however, it's not necessarily recovered later on. This PR tracks whether member deserialization had an error by recursively loading members and checking for deserialization error, and fails and emits a diagnostic. It prevents resilience bypassing when the deserialized decl's layout is incorrect. It also provides an opt-out flag for deserialization checks for temporary migration purposes. Resolves rdar://132411524
1 parent eedae18 commit 32232c6

File tree

12 files changed

+431
-18
lines changed

12 files changed

+431
-18
lines changed

include/swift/AST/DeclContext.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -806,6 +806,22 @@ class IterableDeclContext {
806806
/// while skipping the body of this context.
807807
unsigned HasDerivativeDeclarations : 1;
808808

809+
/// Members of a decl are deserialized lazily. This is set when
810+
/// deserialization of all members is done, regardless of errors.
811+
unsigned DeserializedMembers : 1;
812+
813+
/// Deserialization errors are attempted to be recovered later or
814+
/// silently dropped due to `EnableDeserializationRecovery` being
815+
/// on by default. The following flag is set when deserializing
816+
/// members fails regardless of the `EnableDeserializationRecovery`
817+
/// value and is used to prevent decl containing such members from
818+
/// being accessed non-resiliently.
819+
unsigned HasDeserializeMemberError : 1;
820+
821+
/// Used to track whether members of this decl and their respective
822+
/// members were checked for deserialization errors recursively.
823+
unsigned CheckedForDeserializeMemberError : 1;
824+
809825
template<class A, class B, class C>
810826
friend struct ::llvm::CastInfo;
811827

@@ -816,6 +832,9 @@ class IterableDeclContext {
816832
/// Retrieve the \c ASTContext in which this iterable context occurs.
817833
ASTContext &getASTContext() const;
818834

835+
void setCheckedForDeserializeMemberError(bool checked) { CheckedForDeserializeMemberError = checked; }
836+
bool checkedForDeserializeMemberError() const { return CheckedForDeserializeMemberError; }
837+
819838
public:
820839
IterableDeclContext(IterableDeclContextKind kind)
821840
: LastDeclAndKind(nullptr, kind) {
@@ -824,6 +843,9 @@ class IterableDeclContext {
824843
HasDerivativeDeclarations = 0;
825844
HasNestedClassDeclarations = 0;
826845
InFreestandingMacroArgument = 0;
846+
DeserializedMembers = 0;
847+
HasDeserializeMemberError = 0;
848+
CheckedForDeserializeMemberError = 0;
827849
}
828850

829851
/// Determine the kind of iterable context we have.
@@ -833,6 +855,18 @@ class IterableDeclContext {
833855

834856
bool hasUnparsedMembers() const;
835857

858+
void setDeserializedMembers(bool deserialized) { DeserializedMembers = deserialized; }
859+
bool didDeserializeMembers() const { return DeserializedMembers; }
860+
861+
void setHasDeserializeMemberError(bool hasError) { HasDeserializeMemberError = hasError; }
862+
bool hasDeserializeMemberError() const { return HasDeserializeMemberError; }
863+
864+
/// This recursively checks whether members of this decl and their respective
865+
/// members were deserialized correctly and emits a diagnostic in case of an error.
866+
/// Requires accessing module and this decl's module are in the same package,
867+
/// and this decl's module has package optimization enabled.
868+
void checkDeserializeMemberErrorInPackage(ModuleDecl *accessingModule);
869+
836870
bool maybeHasOperatorDeclarations() const {
837871
return HasOperatorDeclarations;
838872
}

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4722,6 +4722,11 @@ NOTE(ambiguous_because_of_trailing_closure,none,
47224722
"avoid using a trailing closure}0 to call %1",
47234723
(bool, const ValueDecl *))
47244724

4725+
// In-package resilience bypassing
4726+
ERROR(cannot_bypass_resilience_due_to_missing_member,none,
4727+
"cannot bypass resilience due to member deserialization failure while attempting to access %select{member %0|missing member}1 of %2 in module %3 from module %4",
4728+
(Identifier, bool, Identifier, Identifier, Identifier))
4729+
47254730
// Cannot capture inout-ness of a parameter
47264731
// Partial application of foreign functions not supported
47274732
ERROR(partial_application_of_function_invalid,none,

include/swift/Basic/LangOptions.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,13 @@ namespace swift {
610610
/// from source.
611611
bool AllowNonResilientAccess = false;
612612

613+
/// When Package CMO is enabled, deserialization checks are done to
614+
/// ensure that the members of a decl are correctly deserialized to maintain
615+
/// proper layout. This ensures that bypassing resilience is safe. Accessing
616+
/// an incorrectly laid-out decl directly can lead to runtime crashes. This flag
617+
/// should only be used temporarily during migration to enable Package CMO.
618+
bool SkipDeserializationChecksForPackageCMO = false;
619+
613620
/// Enables dumping type witness systems from associated type inference.
614621
bool DumpTypeWitnessSystems = false;
615622

include/swift/Option/Options.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,6 +1074,10 @@ def Oplayground : Flag<["-"], "Oplayground">, Group<O_Group>,
10741074
Flags<[HelpHidden, FrontendOption, ModuleInterfaceOption]>,
10751075
HelpText<"Compile with optimizations appropriate for a playground">;
10761076

1077+
def ExperimentalSkipDeserializationChecksForPackageCMO : Flag<["-"], "experimental-skip-deserialization-checks-for-package-cmo">,
1078+
Flags<[FrontendOption]>,
1079+
HelpText<"Skip deserialization checks for package-cmo; use only for experimental purposes">;
1080+
10771081
def ExperimentalPackageCMO : Flag<["-"], "experimental-package-cmo">,
10781082
Flags<[FrontendOption]>,
10791083
HelpText<"Deprecated; use -package-cmo instead">;

lib/AST/Decl.cpp

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4560,14 +4560,51 @@ bool ValueDecl::hasOpenAccess(const DeclContext *useDC) const {
45604560
}
45614561

45624562
bool ValueDecl::bypassResilienceInPackage(ModuleDecl *accessingModule) const {
4563-
// If the defining module is built with package-cmo, bypass
4564-
// resilient access from the use site that belongs to a module
4565-
// in the same package.
4563+
// To allow bypassing resilience when accessing this decl from another
4564+
// module, it should be in the same package as this decl's module.
45664565
auto declModule = getModuleContext();
4567-
return declModule->inSamePackage(accessingModule) &&
4568-
declModule->isResilient() &&
4569-
declModule->allowNonResilientAccess() &&
4570-
declModule->serializePackageEnabled();
4566+
if (!declModule->inSamePackage(accessingModule))
4567+
return false;
4568+
// Package optimization allows bypassing resilience, but it assumes the
4569+
// memory layout of the decl being accessed is correct. When this assumption
4570+
// fails due to a deserialization error of its members, the use site incorrectly
4571+
// accesses the layout of the decl with a wrong field offset, resulting in UB
4572+
// or a crash.
4573+
// The deserialization error is currently not caught at compile time due to
4574+
// LangOpts.EnableDeserializationRecovery being enabled by default (to allow
4575+
// for recovery of some of the deserialization errors at a later time). In case
4576+
// of member deserialization, however, it's not necessarily recovered later on
4577+
// and is silently dropped, causing UB or a crash at runtime.
4578+
// The following tracks errors in member deserialization by recursively loading
4579+
// members of a type (if not done already) and checking whether the type's
4580+
// members, and their respective types (recursively), encountered deserialization
4581+
// failures.
4582+
// If any such type is found, it fails and emits a diagnostic at compile time.
4583+
// Simply disallowing resilience bypassing for this decl here is insufficient
4584+
// because it would cause a type requirement mismatch later during SIL
4585+
// deserialiaztion; e.g. generated SIL in the imported module might contain
4586+
// an instruction that allows a direct access, while the caller in client module
4587+
// might require a non-direct access due to a deserialization error.
4588+
if (declModule->isResilient() &&
4589+
declModule->allowNonResilientAccess() &&
4590+
declModule->serializePackageEnabled()) {
4591+
// Fail and diagnose if there is a member deserialization error,
4592+
// with an option to skip for temporary/migration purposes.
4593+
if (!getASTContext().LangOpts.SkipDeserializationChecksForPackageCMO) {
4594+
// Since we're checking for deserialization errors, make sure the
4595+
// accessing module is different from this decl's module.
4596+
if (accessingModule &&
4597+
accessingModule != declModule) {
4598+
if (auto IDC = dyn_cast<IterableDeclContext>(this)) {
4599+
// Recursively check if members and their members have failing
4600+
// deserialization, and emit a diagnostic.
4601+
IDC->checkDeserializeMemberErrorInPackage(accessingModule);
4602+
}
4603+
}
4604+
}
4605+
return true;
4606+
}
4607+
return false;
45714608
}
45724609

45734610
/// Given the formal access level for using \p VD, compute the scope where

lib/AST/DeclContext.cpp

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "swift/AST/SourceFile.h"
2626
#include "swift/AST/Types.h"
2727
#include "swift/AST/TypeCheckRequests.h"
28+
#include "swift/AST/DiagnosticsSema.h"
2829
#include "swift/Basic/Assertions.h"
2930
#include "swift/Basic/SourceManager.h"
3031
#include "swift/Basic/Statistic.h"
@@ -1173,6 +1174,145 @@ void IterableDeclContext::loadAllMembers() const {
11731174
--s->getFrontendCounters().NumUnloadedLazyIterableDeclContexts;
11741175
}
11751176

1177+
// Checks whether members of this decl and their respective members
1178+
// (recursively) were deserialized correctly and emits a diagnostic
1179+
// if deserialization failed. Requires accessing module and this decl's
1180+
// module are in the same package, and this decl's module has package
1181+
// optimization enabled.
1182+
void IterableDeclContext::checkDeserializeMemberErrorInPackage(ModuleDecl *accessingModule) {
1183+
// Only check if accessing module is in the same package as this
1184+
// decl's module, which has package optimization enabled.
1185+
if (!getDecl()->getModuleContext()->inSamePackage(accessingModule) ||
1186+
!getDecl()->getModuleContext()->isResilient() ||
1187+
!getDecl()->getModuleContext()->serializePackageEnabled())
1188+
return;
1189+
// Bail if already checked for an error.
1190+
if (checkedForDeserializeMemberError())
1191+
return;
1192+
// If members were not deserialized, force load here.
1193+
if (!didDeserializeMembers()) {
1194+
// This needs to be set to force load all members if not done already.
1195+
setHasLazyMembers(true);
1196+
// Calling getMembers actually loads the members.
1197+
auto members = getMembers();
1198+
assert(!hasLazyMembers());
1199+
assert(didDeserializeMembers());
1200+
}
1201+
// Members could have been deserialized from other flows. Check
1202+
// for an error here. First mark this decl 'checked' to prevent
1203+
// infinite recursion in case of self-referencing members.
1204+
setCheckedForDeserializeMemberError(true);
1205+
1206+
// If members are already loaded above or by other flows,
1207+
// calling getMembers here should be inexpensive.
1208+
auto memberList = getMembers();
1209+
1210+
// This decl contains a member deserialization error; emit a diag.
1211+
if (hasDeserializeMemberError()) {
1212+
auto containerID = Identifier();
1213+
if (auto container = dyn_cast<NominalTypeDecl>(getDecl())) {
1214+
containerID = container->getBaseIdentifier();
1215+
}
1216+
1217+
auto foundMissing = false;
1218+
for (auto member: memberList) {
1219+
// Only storage vars can affect memory layout so
1220+
// look up pattern binding decl or var decl.
1221+
if (auto *PBD = dyn_cast<PatternBindingDecl>(member)) {
1222+
// If this pattern binding decl is empty, we have
1223+
// a missing member.
1224+
if (PBD->getNumPatternEntries() == 0)
1225+
foundMissing = true;
1226+
}
1227+
// Check if a member can be cast to MissingMemberDecl.
1228+
if (auto missingMember = dyn_cast<MissingMemberDecl>(member)) {
1229+
if (!missingMember->getName().getBaseName().isSpecial() &&
1230+
foundMissing) {
1231+
foundMissing = false;
1232+
auto missingMemberID = missingMember->getName().getBaseIdentifier();
1233+
getASTContext().Diags.diagnose(member->getLoc(),
1234+
diag::cannot_bypass_resilience_due_to_missing_member,
1235+
missingMemberID,
1236+
missingMemberID.empty(),
1237+
containerID,
1238+
getDecl()->getModuleContext()->getBaseIdentifier(),
1239+
accessingModule->getBaseIdentifier());
1240+
continue;
1241+
}
1242+
}
1243+
// If not handled above, emit a diag here.
1244+
if (foundMissing) {
1245+
getASTContext().Diags.diagnose(getDecl()->getLoc(),
1246+
diag::cannot_bypass_resilience_due_to_missing_member,
1247+
Identifier(),
1248+
true,
1249+
containerID,
1250+
getDecl()->getModuleContext()->getBaseIdentifier(),
1251+
accessingModule->getBaseIdentifier());
1252+
}
1253+
}
1254+
} else {
1255+
// This decl does not contain a member deserialization error.
1256+
// Check for members of this decl's members recursively to
1257+
// see if a member deserialization failed.
1258+
for (auto member: memberList) {
1259+
// Only storage vars can affect memory layout so
1260+
// look up pattern binding decl or var decl.
1261+
if (auto *PBD = dyn_cast<PatternBindingDecl>(member)) {
1262+
for (auto i : range(PBD->getNumPatternEntries())) {
1263+
auto pattern = PBD->getPattern(i);
1264+
pattern->forEachVariable([&](const VarDecl *VD) {
1265+
// Bail if the var is static or has no storage
1266+
if (VD->isStatic() ||
1267+
!VD->hasStorageOrWrapsStorage())
1268+
return;
1269+
// Unwrap in case this var is optional.
1270+
auto varType = VD->getInterfaceType()->getCanonicalType();
1271+
if (auto unwrapped = varType->getCanonicalType()->getOptionalObjectType()) {
1272+
varType = unwrapped->getCanonicalType();
1273+
}
1274+
// Handle BoundGenericType, e.g. [Foo: Bar], Dictionary<Foo, Bar>.
1275+
// Check for its arguments types, i.e. Foo, Bar.
1276+
if (auto boundGeneric = varType->getAs<BoundGenericType>()) {
1277+
for (auto arg : boundGeneric->getGenericArgs()) {
1278+
if (auto argNominal = arg->getNominalOrBoundGenericNominal()) {
1279+
if (auto argIDC = dyn_cast<IterableDeclContext>(argNominal)) {
1280+
argIDC->checkDeserializeMemberErrorInPackage(getDecl()->getModuleContext());
1281+
if (argIDC->hasDeserializeMemberError()) {
1282+
setHasDeserializeMemberError(true);
1283+
break;
1284+
}
1285+
}
1286+
}
1287+
}
1288+
} else if (auto tupleType = varType->getAs<TupleType>()) {
1289+
// Handle TupleType, e.g. (Foo, Var).
1290+
for (auto element : tupleType->getElements()) {
1291+
if (auto elementNominal = element.getType()->getNominalOrBoundGenericNominal()) {
1292+
if (auto elementIDC = dyn_cast<IterableDeclContext>(elementNominal)) {
1293+
elementIDC->checkDeserializeMemberErrorInPackage(getDecl()->getModuleContext());
1294+
if (elementIDC->hasDeserializeMemberError()) {
1295+
setHasDeserializeMemberError(true);
1296+
break;
1297+
}
1298+
}
1299+
}
1300+
}
1301+
} else if (auto varNominal = varType->getNominalOrBoundGenericNominal()) {
1302+
if (auto varIDC = dyn_cast<IterableDeclContext>(varNominal)) {
1303+
varIDC->checkDeserializeMemberErrorInPackage(getDecl()->getModuleContext());
1304+
if (varIDC->hasDeserializeMemberError()) {
1305+
setHasDeserializeMemberError(true);
1306+
}
1307+
}
1308+
}
1309+
});
1310+
}
1311+
}
1312+
}
1313+
}
1314+
}
1315+
11761316
bool IterableDeclContext::wasDeserialized() const {
11771317
const DeclContext *DC = getAsGenericContext();
11781318
if (auto F = dyn_cast<FileUnit>(DC->getModuleScopeContext())) {

lib/AST/TypeSubstitution.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -997,8 +997,7 @@ ReplaceOpaqueTypesWithUnderlyingTypes::shouldPerformSubstitution(
997997
// resilient expansion if the context's and the opaque type's module are in
998998
// the same package.
999999
if (contextExpansion == ResilienceExpansion::Maximal &&
1000-
module->isResilient() && module->serializePackageEnabled() &&
1001-
module->inSamePackage(contextModule))
1000+
namingDecl->bypassResilienceInPackage(contextModule))
10021001
return OpaqueSubstitutionKind::SubstituteSamePackageMaximalResilience;
10031002

10041003
// Allow general replacement from non resilient modules. Otherwise, disallow.

lib/ClangImporter/ImportDecl.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9672,17 +9672,22 @@ ClangImporter::Implementation::loadAllMembers(Decl *D, uint64_t extra) {
96729672
// Check whether we're importing an Objective-C container of some sort.
96739673
auto objcContainer =
96749674
dyn_cast_or_null<clang::ObjCContainerDecl>(D->getClangDecl());
9675+
auto *IDC = dyn_cast<IterableDeclContext>(D);
96759676

96769677
// If not, we're importing globals-as-members into an extension.
96779678
if (objcContainer) {
96789679
loadAllMembersOfSuperclassIfNeeded(dyn_cast<ClassDecl>(D));
96799680
loadAllMembersOfObjcContainer(D, objcContainer);
9681+
if (IDC) // Set member deserialization status
9682+
IDC->setDeserializedMembers(true);
96809683
return;
96819684
}
96829685

96839686
if (isa_and_nonnull<clang::RecordDecl>(D->getClangDecl())) {
96849687
loadAllMembersOfRecordDecl(cast<NominalTypeDecl>(D),
96859688
cast<clang::RecordDecl>(D->getClangDecl()));
9689+
if (IDC) // Set member deserialization status
9690+
IDC->setDeserializedMembers(true);
96869691
return;
96879692
}
96889693

@@ -9693,6 +9698,8 @@ ClangImporter::Implementation::loadAllMembers(Decl *D, uint64_t extra) {
96939698
}
96949699

96959700
loadAllMembersIntoExtension(D, extra);
9701+
if (IDC) // Set member deserialization status
9702+
IDC->setDeserializedMembers(true);
96969703
}
96979704

96989705
void ClangImporter::Implementation::loadAllMembersIntoExtension(

lib/Frontend/CompilerInvocation.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1356,6 +1356,7 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
13561356
}
13571357
}
13581358

1359+
Opts.SkipDeserializationChecksForPackageCMO = Args.hasArg(OPT_ExperimentalSkipDeserializationChecksForPackageCMO);
13591360
Opts.AllowNonResilientAccess =
13601361
Args.hasArg(OPT_experimental_allow_non_resilient_access) ||
13611362
Args.hasArg(OPT_allow_non_resilient_access) ||

lib/SIL/IR/TypeLowering.cpp

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2443,13 +2443,8 @@ namespace {
24432443
// The same should happen if the type was resilient and serialized in
24442444
// another module in the same package with package-cmo enabled, which
24452445
// treats those modules to be in the same resilience domain.
2446-
auto declModule = D->getModuleContext();
2447-
bool sameModule = (declModule == &TC.M);
2448-
bool serializedPackage = declModule != &TC.M &&
2449-
declModule->inSamePackage(&TC.M) &&
2450-
declModule->isResilient() &&
2451-
declModule->serializePackageEnabled();
2452-
auto inSameResilienceDomain = sameModule || serializedPackage;
2446+
auto inSameResilienceDomain = D->getModuleContext() == &TC.M ||
2447+
D->bypassResilienceInPackage(&TC.M);
24532448
if (inSameResilienceDomain)
24542449
properties.addSubobject(RecursiveProperties::forResilient());
24552450

0 commit comments

Comments
 (0)