Skip to content

Commit c03abed

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 provides a way to prevent resilience bypassing when the deserialized decl's layout is incorrect. Resolves rdar://132411524
1 parent 26b2f7b commit c03abed

12 files changed

+431
-18
lines changed

include/swift/AST/DeclContext.h

+34
Original file line numberDiff line numberDiff line change
@@ -815,6 +815,22 @@ class IterableDeclContext {
815815
/// while skipping the body of this context.
816816
unsigned HasDerivativeDeclarations : 1;
817817

818+
/// Members of a decl are deserialized lazily. This is set when
819+
/// deserialization of all members is done, regardless of errors.
820+
unsigned DeserializedMembers : 1;
821+
822+
/// Deserialization errors are attempted to be recovered later or
823+
/// silently dropped due to `EnableDeserializationRecovery` being
824+
/// on by default. The following flag is set when deserializing
825+
/// members fails regardless of the `EnableDeserializationRecovery`
826+
/// value and is used to prevent decl containing such members from
827+
/// being accessed non-resiliently.
828+
unsigned HasDeserializeMemberError : 1;
829+
830+
/// Used to track whether members of this decl and their respective
831+
/// members were checked for deserialization errors recursively.
832+
unsigned CheckedForDeserializeMemberError : 1;
833+
818834
template<class A, class B, class C>
819835
friend struct ::llvm::CastInfo;
820836

@@ -825,6 +841,9 @@ class IterableDeclContext {
825841
/// Retrieve the \c ASTContext in which this iterable context occurs.
826842
ASTContext &getASTContext() const;
827843

844+
void setCheckedForDeserializeMemberError(bool checked) { CheckedForDeserializeMemberError = checked; }
845+
bool checkedForDeserializeMemberError() const { return CheckedForDeserializeMemberError; }
846+
828847
public:
829848
IterableDeclContext(IterableDeclContextKind kind)
830849
: LastDeclAndKind(nullptr, kind) {
@@ -833,6 +852,9 @@ class IterableDeclContext {
833852
HasDerivativeDeclarations = 0;
834853
HasNestedClassDeclarations = 0;
835854
InFreestandingMacroArgument = 0;
855+
DeserializedMembers = 0;
856+
HasDeserializeMemberError = 0;
857+
CheckedForDeserializeMemberError = 0;
836858
}
837859

838860
/// Determine the kind of iterable context we have.
@@ -842,6 +864,18 @@ class IterableDeclContext {
842864

843865
bool hasUnparsedMembers() const;
844866

867+
void setDeserializedMembers(bool deserialized) { DeserializedMembers = deserialized; }
868+
bool didDeserializeMembers() const { return DeserializedMembers; }
869+
870+
void setHasDeserializeMemberError(bool hasError) { HasDeserializeMemberError = hasError; }
871+
bool hasDeserializeMemberError() const { return HasDeserializeMemberError; }
872+
873+
/// This recursively checks whether members of this decl and their respective
874+
/// members were deserialized correctly and emits a diagnostic in case of an error.
875+
/// Requires accessing module and this decl's module are in the same package,
876+
/// and this decl's module has package optimization enabled.
877+
void checkDeserializeMemberErrorInPackage(ModuleDecl *accessingModule);
878+
845879
bool maybeHasOperatorDeclarations() const {
846880
return HasOperatorDeclarations;
847881
}

include/swift/AST/DiagnosticsSema.def

+5
Original file line numberDiff line numberDiff line change
@@ -4733,6 +4733,11 @@ NOTE(ambiguous_because_of_trailing_closure,none,
47334733
"avoid using a trailing closure}0 to call %1",
47344734
(bool, const ValueDecl *))
47354735

4736+
// In-package resilience bypassing
4737+
ERROR(cannot_bypass_resilience_due_to_missing_member,none,
4738+
"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",
4739+
(Identifier, bool, Identifier, Identifier, Identifier))
4740+
47364741
// Cannot capture inout-ness of a parameter
47374742
// Partial application of foreign functions not supported
47384743
ERROR(partial_application_of_function_invalid,none,

include/swift/Basic/LangOptions.h

+7
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

+4
Original file line numberDiff line numberDiff line change
@@ -1079,6 +1079,10 @@ def Oplayground : Flag<["-"], "Oplayground">, Group<O_Group>,
10791079
Flags<[HelpHidden, FrontendOption, ModuleInterfaceOption]>,
10801080
HelpText<"Compile with optimizations appropriate for a playground">;
10811081

1082+
def ExperimentalSkipDeserializationChecksForPackageCMO : Flag<["-"], "experimental-skip-deserialization-checks-for-package-cmo">,
1083+
Flags<[FrontendOption]>,
1084+
HelpText<"Skip deserialization checks for package-cmo; use only for experimental purposes">;
1085+
10821086
def ExperimentalPackageCMO : Flag<["-"], "experimental-package-cmo">,
10831087
Flags<[FrontendOption]>,
10841088
HelpText<"Deprecated; use -package-cmo instead">;

lib/AST/Decl.cpp

+44-7
Original file line numberDiff line numberDiff line change
@@ -4723,14 +4723,51 @@ bool ValueDecl::hasOpenAccess(const DeclContext *useDC) const {
47234723
}
47244724

47254725
bool ValueDecl::bypassResilienceInPackage(ModuleDecl *accessingModule) const {
4726-
// If the defining module is built with package-cmo, bypass
4727-
// resilient access from the use site that belongs to a module
4728-
// in the same package.
4726+
// To allow bypassing resilience when accessing this decl from another
4727+
// module, it should be in the same package as this decl's module.
47294728
auto declModule = getModuleContext();
4730-
return declModule->inSamePackage(accessingModule) &&
4731-
declModule->isResilient() &&
4732-
declModule->allowNonResilientAccess() &&
4733-
declModule->serializePackageEnabled();
4729+
if (!declModule->inSamePackage(accessingModule))
4730+
return false;
4731+
// Package optimization allows bypassing resilience, but it assumes the
4732+
// memory layout of the decl being accessed is correct. When this assumption
4733+
// fails due to a deserialization error of its members, the use site incorrectly
4734+
// accesses the layout of the decl with a wrong field offset, resulting in UB
4735+
// or a crash.
4736+
// The deserialization error is currently not caught at compile time due to
4737+
// LangOpts.EnableDeserializationRecovery being enabled by default (to allow
4738+
// for recovery of some of the deserialization errors at a later time). In case
4739+
// of member deserialization, however, it's not necessarily recovered later on
4740+
// and is silently dropped, causing UB or a crash at runtime.
4741+
// The following tracks errors in member deserialization by recursively loading
4742+
// members of a type (if not done already) and checking whether the type's
4743+
// members, and their respective types (recursively), encountered deserialization
4744+
// failures.
4745+
// If any such type is found, it fails and emits a diagnostic at compile time.
4746+
// Simply disallowing resilience bypassing for this decl here is insufficient
4747+
// because it would cause a type requirement mismatch later during SIL
4748+
// deserialiaztion; e.g. generated SIL in the imported module might contain
4749+
// an instruction that allows a direct access, while the caller in client module
4750+
// might require a non-direct access due to a deserialization error.
4751+
if (declModule->isResilient() &&
4752+
declModule->allowNonResilientAccess() &&
4753+
declModule->serializePackageEnabled()) {
4754+
// Fail and diagnose if there is a member deserialization error,
4755+
// with an option to skip for temporary/migration purposes.
4756+
if (!getASTContext().LangOpts.SkipDeserializationChecksForPackageCMO) {
4757+
// Since we're checking for deserialization errors, make sure the
4758+
// accessing module is different from this decl's module.
4759+
if (accessingModule &&
4760+
accessingModule != declModule) {
4761+
if (auto IDC = dyn_cast<IterableDeclContext>(this)) {
4762+
// Recursively check if members and their members have failing
4763+
// deserialization, and emit a diagnostic.
4764+
IDC->checkDeserializeMemberErrorInPackage(accessingModule);
4765+
}
4766+
}
4767+
}
4768+
return true;
4769+
}
4770+
return false;
47344771
}
47354772

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

lib/AST/DeclContext.cpp

+140
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "swift/AST/SourceFile.h"
2727
#include "swift/AST/TypeCheckRequests.h"
2828
#include "swift/AST/Types.h"
29+
#include "swift/AST/DiagnosticsSema.h"
2930
#include "swift/Basic/Assertions.h"
3031
#include "swift/Basic/SourceManager.h"
3132
#include "swift/Basic/Statistic.h"
@@ -1174,6 +1175,145 @@ void IterableDeclContext::loadAllMembers() const {
11741175
--s->getFrontendCounters().NumUnloadedLazyIterableDeclContexts;
11751176
}
11761177

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

lib/AST/TypeSubstitution.cpp

+1-2
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

+7
Original file line numberDiff line numberDiff line change
@@ -9835,17 +9835,22 @@ ClangImporter::Implementation::loadAllMembers(Decl *D, uint64_t extra) {
98359835
// Check whether we're importing an Objective-C container of some sort.
98369836
auto objcContainer =
98379837
dyn_cast_or_null<clang::ObjCContainerDecl>(D->getClangDecl());
9838+
auto *IDC = dyn_cast<IterableDeclContext>(D);
98389839

98399840
// If not, we're importing globals-as-members into an extension.
98409841
if (objcContainer) {
98419842
loadAllMembersOfSuperclassIfNeeded(dyn_cast<ClassDecl>(D));
98429843
loadAllMembersOfObjcContainer(D, objcContainer);
9844+
if (IDC) // Set member deserialization status
9845+
IDC->setDeserializedMembers(true);
98439846
return;
98449847
}
98459848

98469849
if (isa_and_nonnull<clang::RecordDecl>(D->getClangDecl())) {
98479850
loadAllMembersOfRecordDecl(cast<NominalTypeDecl>(D),
98489851
cast<clang::RecordDecl>(D->getClangDecl()));
9852+
if (IDC) // Set member deserialization status
9853+
IDC->setDeserializedMembers(true);
98499854
return;
98509855
}
98519856

@@ -9856,6 +9861,8 @@ ClangImporter::Implementation::loadAllMembers(Decl *D, uint64_t extra) {
98569861
}
98579862

98589863
loadAllMembersIntoExtension(D, extra);
9864+
if (IDC) // Set member deserialization status
9865+
IDC->setDeserializedMembers(true);
98599866
}
98609867

98619868
void ClangImporter::Implementation::loadAllMembersIntoExtension(

lib/Frontend/CompilerInvocation.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -1354,6 +1354,7 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
13541354
}
13551355
}
13561356

1357+
Opts.SkipDeserializationChecksForPackageCMO = Args.hasArg(OPT_ExperimentalSkipDeserializationChecksForPackageCMO);
13571358
Opts.AllowNonResilientAccess =
13581359
Args.hasArg(OPT_experimental_allow_non_resilient_access) ||
13591360
Args.hasArg(OPT_allow_non_resilient_access) ||

lib/SIL/IR/TypeLowering.cpp

+2-7
Original file line numberDiff line numberDiff line change
@@ -2449,13 +2449,8 @@ namespace {
24492449
// The same should happen if the type was resilient and serialized in
24502450
// another module in the same package with package-cmo enabled, which
24512451
// treats those modules to be in the same resilience domain.
2452-
auto declModule = D->getModuleContext();
2453-
bool sameModule = (declModule == &TC.M);
2454-
bool serializedPackage = declModule != &TC.M &&
2455-
declModule->inSamePackage(&TC.M) &&
2456-
declModule->isResilient() &&
2457-
declModule->serializePackageEnabled();
2458-
auto inSameResilienceDomain = sameModule || serializedPackage;
2452+
auto inSameResilienceDomain = D->getModuleContext() == &TC.M ||
2453+
D->bypassResilienceInPackage(&TC.M);
24592454
if (inSameResilienceDomain)
24602455
properties.addSubobject(RecursiveProperties::forResilient());
24612456

0 commit comments

Comments
 (0)