[Serialization] Restrict loading swiftmodule files to the builder's SDK

Serialize the canonical name of the SDK used when building a swiftmodule
file and use it to ensure that the swiftmodule file is loaded only with
the same SDK. The SDK name must be passed down from the frontend.

This will report unsupported configurations like:

- Installing roots between incompatible SDKs without deleting the
swiftmodule files.
- Having multiple targets in the same project using different SDKs.
- Loading a swiftmodule created with a newer SDK (and stdlib) with an
older SDK.

All of these lead to hard to investigate deserialization failures and
this change should detect them early, before reaching a deserialization
failure.

rdar://78048939
This commit is contained in:
Alexis Laferrière
2021-06-01 16:57:40 -07:00
parent 7c653e4fec
commit c38d1773d2
16 changed files with 85 additions and 3 deletions

View File

@@ -782,6 +782,9 @@ ERROR(serialization_name_mismatch_repl,none,
ERROR(serialization_target_incompatible,Fatal,
"module %0 was created for incompatible target %1: %2",
(Identifier, StringRef, StringRef))
ERROR(serialization_sdk_mismatch,Fatal,
"cannot load module %0 built with SDK '%1' when using SDK '%2': %3",
(Identifier, StringRef, StringRef, StringRef))
ERROR(serialization_target_incompatible_repl,none,
"module %0 was created for incompatible target %1: %2",
(Identifier, StringRef, StringRef))

View File

@@ -82,6 +82,10 @@ public:
/// would for a non-system header.
bool DisableModulesValidateSystemDependencies = false;
/// Enforce loading only serialized modules built with the same SDK
/// as the context loading it.
bool EnableSameSDKCheck = true;
/// A set of compiled modules that may be ready to use.
std::vector<std::string> CandidateCompiledModules;

View File

@@ -127,6 +127,9 @@ namespace swift {
/// The target variant SDK version, if known.
Optional<llvm::VersionTuple> VariantSDKVersion;
/// The SDK canonical name, if known.
std::string SDKName;
/// The alternate name to use for the entry point instead of main.
std::string entryPointFunctionName = "main";

View File

@@ -823,6 +823,9 @@ def target_sdk_version : Separate<["-"], "target-sdk-version">,
def target_variant_sdk_version : Separate<["-"], "target-variant-sdk-version">,
HelpText<"The version of target variant SDK used for compilation">;
def target_sdk_name : Separate<["-"], "target-sdk-name">,
HelpText<"Canonical name of the target SDK used for compilation">;
def extra_clang_options_only : Flag<["-"], "only-use-extra-clang-opts">,
HelpText<"Options passed via -Xcc are sufficient for Clang configuration">;

View File

@@ -35,6 +35,7 @@ namespace swift {
bool SkipSymbolGraphInheritedDocs = true;
bool IncludeSPISymbolsInSymbolGraph = false;
llvm::VersionTuple UserModuleVersion;
std::string SDKName;
StringRef GroupInfoPath;
StringRef ImportedHeader;

View File

@@ -66,7 +66,11 @@ enum class Status {
TargetIncompatible,
/// The module file was built for a target newer than the current target.
TargetTooNew
TargetTooNew,
/// The module file was built with a different SDK than the one in use
/// to build the client.
SDKMismatch
};
/// Returns true if the data looks like it contains a serialized AST.
@@ -80,6 +84,7 @@ struct ValidationInfo {
StringRef miscVersion = {};
version::Version compatibilityVersion = {};
llvm::VersionTuple userModuleVersion;
StringRef sdkName = {};
size_t bytes = 0;
Status status = Status::Malformed;
};

View File

@@ -774,6 +774,11 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
}
}
// Get the SDK name.
if (Arg *A = Args.getLastArg(options::OPT_target_sdk_name)) {
Opts.SDKName = A->getValue();
}
if (const Arg *A = Args.getLastArg(OPT_entry_point_function_name)) {
Opts.entryPointFunctionName = A->getValue();
}

View File

@@ -150,6 +150,7 @@ SerializationOptions CompilerInvocation::computeSerializationOptions(
serializationOpts.ExtraClangOptions = getClangImporterOptions().ExtraArgs;
serializationOpts.PublicDependentLibraries =
getIRGenOptions().PublicLinkLibraries;
serializationOpts.SDKName = getLangOptions().SDKName;
if (opts.EmitSymbolGraph) {
if (!opts.SymbolGraphOutputDir.empty()) {

View File

@@ -257,6 +257,8 @@ bool ModuleInterfaceBuilder::buildSwiftModuleInternal(
if (!getRelativeDepPath(InPath, SDKPath))
SerializationOpts.ModuleInterface = InPath;
SerializationOpts.SDKName = SubInstance.getASTContext().LangOpts.SDKName;
SmallVector<FileDependency, 16> Deps;
bool serializeHashes = FEOpts.SerializeModuleInterfaceDependencyHashes;
if (collectDepsForSerialization(SubInstance, Deps, serializeHashes)) {

View File

@@ -149,6 +149,15 @@ Status ModuleFile::associateWithFileContext(FileUnit *file, SourceLoc diagLoc,
return error(status);
}
auto clientSDK = ctx.LangOpts.SDKName;
StringRef moduleSDK = Core->SDKName;
if (ctx.SearchPathOpts.EnableSameSDKCheck &&
!moduleSDK.empty() && !clientSDK.empty() &&
moduleSDK != clientSDK) {
status = Status::SDKMismatch;
return error(status);
}
for (const auto &searchPath : Core->SearchPaths)
ctx.addSearchPath(searchPath.Path, searchPath.IsFramework,
searchPath.IsSystem);

View File

@@ -299,6 +299,10 @@ validateControlBlock(llvm::BitstreamCursor &cursor,
case control_block::TARGET:
result.targetTriple = blobData;
break;
case control_block::SDK_NAME: {
result.sdkName = blobData;
break;
}
default:
// Unknown metadata record, possibly for use by a future version of the
// module format.
@@ -1210,6 +1214,7 @@ ModuleFileSharedCore::ModuleFileSharedCore(
}
Name = info.name;
TargetTriple = info.targetTriple;
SDKName = info.sdkName;
CompatibilityVersion = info.compatibilityVersion;
UserModuleVersion = info.userModuleVersion;
Bits.ArePrivateImportsEnabled = extInfo.arePrivateImportsEnabled();

View File

@@ -58,6 +58,9 @@ class ModuleFileSharedCore {
/// The target the module was built for.
StringRef TargetTriple;
/// The canonical name of the SDK the module was built with.
StringRef SDKName;
/// The name of the module interface this module was compiled from.
///
/// Empty if this module didn't come from an interface file.

View File

@@ -56,7 +56,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
/// describe what change you made. The content of this comment isn't important;
/// it just ensures a conflict if two people change the module format.
/// Don't worry about adhering to the 80-column limit for this line.
const uint16_t SWIFTMODULE_VERSION_MINOR = 628; // unaligned pointer
const uint16_t SWIFTMODULE_VERSION_MINOR = 629; // BuilderSDK
/// A standard hash seed used for all string hashes in a serialized module.
///
@@ -754,7 +754,8 @@ namespace control_block {
enum {
METADATA = 1,
MODULE_NAME,
TARGET
TARGET,
SDK_NAME
};
using MetadataLayout = BCRecordLayout<
@@ -779,6 +780,11 @@ namespace control_block {
TARGET,
BCBlob // LLVM triple
>;
using SDKNameLayout = BCRecordLayout<
SDK_NAME,
BCBlob
>;
}
/// The record types within the options block (a sub-block of the control

View File

@@ -802,6 +802,7 @@ void Serializer::writeBlockInfoBlock() {
BLOCK_RECORD(control_block, METADATA);
BLOCK_RECORD(control_block, MODULE_NAME);
BLOCK_RECORD(control_block, TARGET);
BLOCK_RECORD(control_block, SDK_NAME);
BLOCK(OPTIONS_BLOCK);
BLOCK_RECORD(options_block, SDK_PATH);
@@ -952,6 +953,7 @@ void Serializer::writeHeader(const SerializationOptions &options) {
control_block::ModuleNameLayout ModuleName(Out);
control_block::MetadataLayout Metadata(Out);
control_block::TargetLayout Target(Out);
control_block::SDKNameLayout SDKName(Out);
ModuleName.emit(ScratchRecord, M->getName().str());
@@ -985,6 +987,9 @@ void Serializer::writeHeader(const SerializationOptions &options) {
userModuleSubminor, userModuleBuild,
versionString.str());
if (!options.SDKName.empty())
SDKName.emit(ScratchRecord, options.SDKName);
Target.emit(ScratchRecord, M->getASTContext().LangOpts.Target.str());
{

View File

@@ -976,6 +976,13 @@ void swift::serialization::diagnoseSerializedASTLoadFailure(
moduleOSInfo.second, moduleBufferID);
break;
}
case serialization::Status::SDKMismatch:
auto currentSDK = Ctx.LangOpts.SDKName;
auto moduleSDK = loadInfo.sdkName;
Ctx.Diags.diagnose(diagLoc, diag::serialization_sdk_mismatch,
ModuleName, moduleSDK, currentSDK, moduleBufferID);
break;
}
}

View File

@@ -0,0 +1,20 @@
// RUN: %empty-directory(%t/cache)
// RUN: %empty-directory(%t/build)
// RUN: %{python} %utils/split_file.py -o %t %s
/// Build Lib against SDK A.
// RUN: %target-swift-frontend -emit-module %t/Lib.swift -swift-version 5 -target-sdk-name A -o %t/build -parse-stdlib -module-cache-path %t/cache
/// Building Client against SDK A should work fine as expected.
// RUN: %target-swift-frontend -typecheck %t/Client.swift -swift-version 5 -target-sdk-name A -I %t/build -parse-stdlib -module-cache-path %t/cache
/// Build Client against SDK B, this should fail at loading Lib against a different SDK than A.
// RUN: not %target-swift-frontend -typecheck %t/Client.swift -swift-version 5 -target-sdk-name B -I %t/build -parse-stdlib -module-cache-path %t/cache 2>&1 | %FileCheck %s
// CHECK: cannot load module 'Lib' built with SDK 'A' when using SDK 'B': {{.*}}Lib.swiftmodule
// BEGIN Lib.swift
public func foo() {}
// BEGIN Client.swift
import Lib
foo()