Skip to content

Commit 0633894

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 57657c6 commit 0633894

File tree

12 files changed

+422
-18
lines changed

12 files changed

+422
-18
lines changed

include/swift/AST/DeclContext.h

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

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

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

843+
void setCheckedForDeserializeMemberError(bool checked) { CheckedForDeserializeMemberError = checked; }
844+
bool checkedForDeserializeMemberError() const { return CheckedForDeserializeMemberError; }
845+
827846
public:
828847
IterableDeclContext(IterableDeclContextKind kind)
829848
: LastDeclAndKind(nullptr, kind) {
@@ -832,6 +851,9 @@ class IterableDeclContext {
832851
HasDerivativeDeclarations = 0;
833852
HasNestedClassDeclarations = 0;
834853
InFreestandingMacroArgument = 0;
854+
DeserializedMembers = 0;
855+
HasDeserializeMemberError = 0;
856+
CheckedForDeserializeMemberError = 0;
835857
}
836858

837859
/// Determine the kind of iterable context we have.
@@ -841,6 +863,18 @@ class IterableDeclContext {
841863

842864
bool hasUnparsedMembers() const;
843865

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

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 0 deletions
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

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

613+
/// If true, fails and emits a diag on deserialization error and prevents
614+
/// resilience bypass within a package for modules with package-cmo
615+
/// enabled.
616+
bool PackageResilienceBypassOnDeserializationFail = false;
617+
613618
/// Enables dumping type witness systems from associated type inference.
614619
bool DumpTypeWitnessSystems = false;
615620

include/swift/Option/Options.td

Lines changed: 4 additions & 0 deletions
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 ExperimentalPackageResilienceBypassOnDeserializationFail : Flag<["-"], "experimental-disallow-package-resilience-bypass-on-deserialization-fail">,
1083+
Flags<[FrontendOption]>,
1084+
HelpText<"Fail on deserialization errors if package-cmo is enabled">;
1085+
10821086
def ExperimentalPackageCMO : Flag<["-"], "experimental-package-cmo">,
10831087
Flags<[FrontendOption]>,
10841088
HelpText<"Deprecated; use -package-cmo instead">;

lib/AST/Decl.cpp

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

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

47334769
/// 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
@@ -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

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

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

1357+
Opts.PackageResilienceBypassOnDeserializationFail = Args.hasArg(OPT_ExperimentalPackageResilienceBypassOnDeserializationFail);
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

Lines changed: 2 additions & 7 deletions
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)