Skip to content

Commit be6e16a

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 if opted-in. It provides a way to prevent resilience bypassing when the deserialized decl's layout is incorrect. Resolves rdar://132411524
1 parent f7d1c59 commit be6e16a

File tree

12 files changed

+417
-18
lines changed

12 files changed

+417
-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
@@ -4726,6 +4726,11 @@ NOTE(ambiguous_because_of_trailing_closure,none,
47264726
"avoid using a trailing closure}0 to call %1",
47274727
(bool, const ValueDecl *))
47284728

4729+
// In-package resilience bypassing
4730+
ERROR(cannot_bypass_resilience_due_to_missing_member,none,
4731+
"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",
4732+
(Identifier, bool, Identifier, Identifier, Identifier))
4733+
47294734
// Cannot capture inout-ness of a parameter
47304735
// Partial application of foreign functions not supported
47314736
ERROR(partial_application_of_function_invalid,none,

include/swift/Basic/LangOptions.h

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

613+
/// If true, fails and emits a diag on member deserialization error to prevent
614+
/// direct access to modules with package-cmo enabled.
615+
bool FailOnDeserializationErrorForPackageCMO = false;
616+
613617
/// Enables dumping type witness systems from associated type inference.
614618
bool DumpTypeWitnessSystems = false;
615619

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 ExperimentalPackageCMOFailOnDeserializationError : Flag<["-"], "experimental-package-cmo-fail-on-deserialization-error">,
1078+
Flags<[FrontendOption]>,
1079+
HelpText<"Fail on deserialization errors if package-cmo is enabled">;
1080+
10771081
def ExperimentalPackageCMO : Flag<["-"], "experimental-package-cmo">,
10781082
Flags<[FrontendOption]>,
10791083
HelpText<"Deprecated; use -package-cmo instead">;

lib/AST/Decl.cpp

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4573,14 +4573,50 @@ bool ValueDecl::hasOpenAccess(const DeclContext *useDC) const {
45734573
}
45744574

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

45864622
/// 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
@@ -9713,17 +9713,22 @@ ClangImporter::Implementation::loadAllMembers(Decl *D, uint64_t extra) {
97139713
// Check whether we're importing an Objective-C container of some sort.
97149714
auto objcContainer =
97159715
dyn_cast_or_null<clang::ObjCContainerDecl>(D->getClangDecl());
9716+
auto *IDC = dyn_cast<IterableDeclContext>(D);
97169717

97179718
// If not, we're importing globals-as-members into an extension.
97189719
if (objcContainer) {
97199720
loadAllMembersOfSuperclassIfNeeded(dyn_cast<ClassDecl>(D));
97209721
loadAllMembersOfObjcContainer(D, objcContainer);
9722+
if (IDC) // Set member deserialization status
9723+
IDC->setDeserializedMembers(true);
97219724
return;
97229725
}
97239726

97249727
if (isa_and_nonnull<clang::RecordDecl>(D->getClangDecl())) {
97259728
loadAllMembersOfRecordDecl(cast<NominalTypeDecl>(D),
97269729
cast<clang::RecordDecl>(D->getClangDecl()));
9730+
if (IDC) // Set member deserialization status
9731+
IDC->setDeserializedMembers(true);
97279732
return;
97289733
}
97299734

@@ -9734,6 +9739,8 @@ ClangImporter::Implementation::loadAllMembers(Decl *D, uint64_t extra) {
97349739
}
97359740

97369741
loadAllMembersIntoExtension(D, extra);
9742+
if (IDC) // Set member deserialization status
9743+
IDC->setDeserializedMembers(true);
97379744
}
97389745

97399746
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.FailOnDeserializationErrorForPackageCMO = Args.hasArg(OPT_ExperimentalPackageCMOFailOnDeserializationError);
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
@@ -2442,13 +2442,8 @@ namespace {
24422442
// The same should happen if the type was resilient and serialized in
24432443
// another module in the same package with package-cmo enabled, which
24442444
// treats those modules to be in the same resilience domain.
2445-
auto declModule = D->getModuleContext();
2446-
bool sameModule = (declModule == &TC.M);
2447-
bool serializedPackage = declModule != &TC.M &&
2448-
declModule->inSamePackage(&TC.M) &&
2449-
declModule->isResilient() &&
2450-
declModule->serializePackageEnabled();
2451-
auto inSameResilienceDomain = sameModule || serializedPackage;
2445+
auto inSameResilienceDomain = D->getModuleContext() == &TC.M ||
2446+
D->bypassResilienceInPackage(&TC.M);
24522447
if (inSameResilienceDomain)
24532448
properties.addSubobject(RecursiveProperties::forResilient());
24542449

0 commit comments

Comments
 (0)