Skip to content

Commit ecdbbd5

Browse files
authored
Merge pull request #78700 from swiftlang/elsh/rel/pcmo-deserialization-diags
[6.1][Package CMO] Add deserialization checks to ensure correct memory layout
2 parents 8eaad45 + 32232c6 commit ecdbbd5

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
@@ -9680,17 +9680,22 @@ ClangImporter::Implementation::loadAllMembers(Decl *D, uint64_t extra) {
96809680
// Check whether we're importing an Objective-C container of some sort.
96819681
auto objcContainer =
96829682
dyn_cast_or_null<clang::ObjCContainerDecl>(D->getClangDecl());
9683+
auto *IDC = dyn_cast<IterableDeclContext>(D);
96839684

96849685
// If not, we're importing globals-as-members into an extension.
96859686
if (objcContainer) {
96869687
loadAllMembersOfSuperclassIfNeeded(dyn_cast<ClassDecl>(D));
96879688
loadAllMembersOfObjcContainer(D, objcContainer);
9689+
if (IDC) // Set member deserialization status
9690+
IDC->setDeserializedMembers(true);
96889691
return;
96899692
}
96909693

96919694
if (isa_and_nonnull<clang::RecordDecl>(D->getClangDecl())) {
96929695
loadAllMembersOfRecordDecl(cast<NominalTypeDecl>(D),
96939696
cast<clang::RecordDecl>(D->getClangDecl()));
9697+
if (IDC) // Set member deserialization status
9698+
IDC->setDeserializedMembers(true);
96949699
return;
96959700
}
96969701

@@ -9701,6 +9706,8 @@ ClangImporter::Implementation::loadAllMembers(Decl *D, uint64_t extra) {
97019706
}
97029707

97039708
loadAllMembersIntoExtension(D, extra);
9709+
if (IDC) // Set member deserialization status
9710+
IDC->setDeserializedMembers(true);
97049711
}
97059712

97069713
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)