Serialization: restrict swiftmodules to distribution channels

There are scenarios where different compilers are distributed with
compatible serialization format versions and the same tag. Distinguish
swiftmodules in such a case by assigning them to different distribution
channels. A compiler expecting a specific channel will only read
swiftmodules from the same channel. The channels should be defined by
downstream code as it is by definition vendor specific.

For development, a no-channel compiler loads or defining the env var
SWIFT_IGNORE_SWIFTMODULE_REVISION skips this new check.

rdar://123731777
This commit is contained in:
Alexis Laferrière
2024-02-29 17:48:34 -08:00
parent ffc2d9f9a7
commit 1e4fe67f40
10 changed files with 149 additions and 1 deletions

View File

@@ -850,6 +850,11 @@ ERROR(serialization_module_incompatible_revision,Fatal,
"compiled module was created by a different version of the compiler '%0'; " "compiled module was created by a different version of the compiler '%0'; "
"rebuild %1 and try again: %2", "rebuild %1 and try again: %2",
(StringRef, Identifier, StringRef)) (StringRef, Identifier, StringRef))
ERROR(serialization_module_incompatible_channel,Fatal,
"compiled module was created for a different distribution channel '%0' "
"than the local compiler '%1', "
"please ensure %2 is found from the expected path: %3",
(StringRef, StringRef, Identifier, StringRef))
ERROR(serialization_missing_single_dependency,Fatal, ERROR(serialization_missing_single_dependency,Fatal,
"missing required module '%0'", (StringRef)) "missing required module '%0'", (StringRef))
ERROR(serialization_missing_dependencies,Fatal, ERROR(serialization_missing_dependencies,Fatal,

View File

@@ -177,6 +177,11 @@ StringRef getCurrentCompilerTag();
/// depending on the vendor. /// depending on the vendor.
StringRef getCurrentCompilerSerializationTag(); StringRef getCurrentCompilerSerializationTag();
/// Distribution channel of the running compiler for distributed swiftmodules.
/// Helps to distinguish swiftmodules between different compilers using the
/// same serialization tag.
StringRef getCurrentCompilerChannel();
/// Retrieves the value of the upcoming C++ interoperability compatibility /// Retrieves the value of the upcoming C++ interoperability compatibility
/// version that's going to be presented as some new concrete version to the /// version that's going to be presented as some new concrete version to the
/// users. /// users.

View File

@@ -48,6 +48,9 @@ enum class Status {
/// The precise revision version doesn't match. /// The precise revision version doesn't match.
RevisionIncompatible, RevisionIncompatible,
/// The distribution channel doesn't match.
ChannelIncompatible,
/// The module is required to be in OSSA, but is not. /// The module is required to be in OSSA, but is not.
NotInOSSA, NotInOSSA,
@@ -105,6 +108,7 @@ struct ValidationInfo {
llvm::VersionTuple userModuleVersion; llvm::VersionTuple userModuleVersion;
StringRef sdkName = {}; StringRef sdkName = {};
StringRef problematicRevision = {}; StringRef problematicRevision = {};
StringRef problematicChannel = {};
size_t bytes = 0; size_t bytes = 0;
Status status = Status::Malformed; Status status = Status::Malformed;
std::vector<StringRef> allowableClients; std::vector<StringRef> allowableClients;

View File

@@ -313,6 +313,16 @@ StringRef getCurrentCompilerSerializationTag() {
#endif #endif
} }
StringRef getCurrentCompilerChannel() {
static const char* forceDebugChannel =
::getenv("SWIFT_FORCE_SWIFTMODULE_CHANNEL");
if (forceDebugChannel)
return forceDebugChannel;
// Leave it to downstream compilers to define the different channels.
return StringRef();
}
unsigned getUpcomingCxxInteropCompatVersion() { unsigned getUpcomingCxxInteropCompatVersion() {
return SWIFT_VERSION_MAJOR + 1; return SWIFT_VERSION_MAJOR + 1;
} }

View File

@@ -316,6 +316,8 @@ struct ModuleRebuildInfo {
return "compiled with a newer version of the compiler"; return "compiled with a newer version of the compiler";
case Status::RevisionIncompatible: case Status::RevisionIncompatible:
return "compiled with a different version of the compiler"; return "compiled with a different version of the compiler";
case Status::ChannelIncompatible:
return "compiled for a different distribution channel";
case Status::NotInOSSA: case Status::NotInOSSA:
return "module was not built with OSSA"; return "module was not built with OSSA";
case Status::NoncopyableGenericsMismatch: case Status::NoncopyableGenericsMismatch:

View File

@@ -418,6 +418,21 @@ static ValidationInfo validateControlBlock(
} }
break; break;
} }
case control_block::CHANNEL: {
static const char* ignoreRevision =
::getenv("SWIFT_IGNORE_SWIFTMODULE_REVISION");
if (ignoreRevision)
break;
StringRef moduleChannel = blobData,
compilerChannel = version::getCurrentCompilerChannel();
if (requiresRevisionMatch && !compilerChannel.empty() &&
moduleChannel != compilerChannel) {
result.problematicChannel = moduleChannel;
result.status = Status::ChannelIncompatible;
}
break;
}
case control_block::IS_OSSA: { case control_block::IS_OSSA: {
auto isModuleInOSSA = scratch[0]; auto isModuleInOSSA = scratch[0];
if (requiresOSSAModules && !isModuleInOSSA) if (requiresOSSAModules && !isModuleInOSSA)
@@ -532,6 +547,7 @@ std::string serialization::StatusToString(Status S) {
case Status::FormatTooOld: return "FormatTooOld"; case Status::FormatTooOld: return "FormatTooOld";
case Status::FormatTooNew: return "FormatTooNew"; case Status::FormatTooNew: return "FormatTooNew";
case Status::RevisionIncompatible: return "RevisionIncompatible"; case Status::RevisionIncompatible: return "RevisionIncompatible";
case Status::ChannelIncompatible: return "ChannelIncompatible";
case Status::NotInOSSA: return "NotInOSSA"; case Status::NotInOSSA: return "NotInOSSA";
case Status::NoncopyableGenericsMismatch: case Status::NoncopyableGenericsMismatch:
return "NoncopyableGenericsMismatch"; return "NoncopyableGenericsMismatch";

View File

@@ -58,7 +58,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
/// describe what change you made. The content of this comment isn't important; /// 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. /// 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. /// Don't worry about adhering to the 80-column limit for this line.
const uint16_t SWIFTMODULE_VERSION_MINOR = 858; // Replace inherited types with protocols in protocol layout const uint16_t SWIFTMODULE_VERSION_MINOR = 859; // channel check
/// A standard hash seed used for all string hashes in a serialized module. /// A standard hash seed used for all string hashes in a serialized module.
/// ///
@@ -860,6 +860,7 @@ namespace control_block {
TARGET, TARGET,
SDK_NAME, SDK_NAME,
REVISION, REVISION,
CHANNEL,
IS_OSSA, IS_OSSA,
ALLOWABLE_CLIENT_NAME, ALLOWABLE_CLIENT_NAME,
HAS_NONCOPYABLE_GENERICS, HAS_NONCOPYABLE_GENERICS,
@@ -898,6 +899,11 @@ namespace control_block {
BCBlob BCBlob
>; >;
using ChannelLayout = BCRecordLayout<
CHANNEL,
BCBlob
>;
using IsOSSALayout = BCRecordLayout< using IsOSSALayout = BCRecordLayout<
IS_OSSA, IS_OSSA,
BCFixed<1> BCFixed<1>

View File

@@ -834,6 +834,7 @@ void Serializer::writeBlockInfoBlock() {
BLOCK_RECORD(control_block, TARGET); BLOCK_RECORD(control_block, TARGET);
BLOCK_RECORD(control_block, SDK_NAME); BLOCK_RECORD(control_block, SDK_NAME);
BLOCK_RECORD(control_block, REVISION); BLOCK_RECORD(control_block, REVISION);
BLOCK_RECORD(control_block, CHANNEL);
BLOCK_RECORD(control_block, IS_OSSA); BLOCK_RECORD(control_block, IS_OSSA);
BLOCK_RECORD(control_block, ALLOWABLE_CLIENT_NAME); BLOCK_RECORD(control_block, ALLOWABLE_CLIENT_NAME);
BLOCK_RECORD(control_block, HAS_NONCOPYABLE_GENERICS); BLOCK_RECORD(control_block, HAS_NONCOPYABLE_GENERICS);
@@ -982,6 +983,7 @@ void Serializer::writeHeader() {
control_block::TargetLayout Target(Out); control_block::TargetLayout Target(Out);
control_block::SDKNameLayout SDKName(Out); control_block::SDKNameLayout SDKName(Out);
control_block::RevisionLayout Revision(Out); control_block::RevisionLayout Revision(Out);
control_block::ChannelLayout Channel(Out);
control_block::IsOSSALayout IsOSSA(Out); control_block::IsOSSALayout IsOSSA(Out);
control_block::AllowableClientLayout Allowable(Out); control_block::AllowableClientLayout Allowable(Out);
control_block::HasNoncopyableGenerics HasNoncopyableGenerics(Out); control_block::HasNoncopyableGenerics HasNoncopyableGenerics(Out);
@@ -1035,6 +1037,8 @@ void Serializer::writeHeader() {
forcedDebugRevision : version::getCurrentCompilerSerializationTag(); forcedDebugRevision : version::getCurrentCompilerSerializationTag();
Revision.emit(ScratchRecord, revision); Revision.emit(ScratchRecord, revision);
Channel.emit(ScratchRecord, version::getCurrentCompilerChannel());
IsOSSA.emit(ScratchRecord, Options.IsOSSA); IsOSSA.emit(ScratchRecord, Options.IsOSSA);
HasNoncopyableGenerics.emit(ScratchRecord, HasNoncopyableGenerics.emit(ScratchRecord,

View File

@@ -1081,6 +1081,12 @@ void swift::serialization::diagnoseSerializedASTLoadFailure(
Ctx.Diags.diagnose(diagLoc, diag::serialization_module_incompatible_revision, Ctx.Diags.diagnose(diagLoc, diag::serialization_module_incompatible_revision,
loadInfo.problematicRevision, ModuleName, moduleBufferID); loadInfo.problematicRevision, ModuleName, moduleBufferID);
break; break;
case serialization::Status::ChannelIncompatible:
Ctx.Diags.diagnose(diagLoc, diag::serialization_module_incompatible_channel,
loadInfo.problematicChannel,
version::getCurrentCompilerChannel(),
ModuleName, moduleBufferID);
break;
case serialization::Status::Malformed: case serialization::Status::Malformed:
Ctx.Diags.diagnose(diagLoc, diag::serialization_malformed_module, Ctx.Diags.diagnose(diagLoc, diag::serialization_malformed_module,
moduleBufferID); moduleBufferID);
@@ -1168,6 +1174,7 @@ void swift::serialization::diagnoseSerializedASTLoadFailureTransitive(
case serialization::Status::NotInOSSA: case serialization::Status::NotInOSSA:
case serialization::Status::NoncopyableGenericsMismatch: case serialization::Status::NoncopyableGenericsMismatch:
case serialization::Status::RevisionIncompatible: case serialization::Status::RevisionIncompatible:
case serialization::Status::ChannelIncompatible:
case serialization::Status::Malformed: case serialization::Status::Malformed:
case serialization::Status::MalformedDocumentation: case serialization::Status::MalformedDocumentation:
case serialization::Status::FailedToLoadBridgingHeader: case serialization::Status::FailedToLoadBridgingHeader:

View File

@@ -0,0 +1,89 @@
// Binary swiftmodules are restricted to a channel when set in the compiler.
// RUN: %empty-directory(%t/cache)
// RUN: %empty-directory(%t/build)
// RUN: split-file %s %t --leading-lines
//--- Lib.swift
public func foo() {}
/// Build Lib as a resilient and non-resilient swiftmodule
// RUN: %target-swift-frontend -emit-module %t/Lib.swift -swift-version 5 \
// RUN: -o %t/build -parse-stdlib -module-cache-path %t/cache \
// RUN: -module-name ResilientLib -enable-library-evolution \
// RUN: -emit-module-interface-path %t/build/ResilientLib.swiftinterface
// RUN: %target-swift-frontend -emit-module %t/Lib.swift -swift-version 5 \
// RUN: -o %t/build -parse-stdlib -module-cache-path %t/cache \
// RUN: -module-name NonResilientLib
/// Build a channel restricted Lib.
// RUN: env SWIFT_FORCE_SWIFTMODULE_CHANNEL=restricted-channel \
// RUN: %target-swift-frontend -emit-module %t/Lib.swift -swift-version 5 \
// RUN: -o %t/build -parse-stdlib -module-cache-path %t/cache \
// RUN: -module-name ChannelLib -enable-library-evolution
/// 2. Test importing the non-resilient no-channel library from a channel compiler.
//--- NonResilientClient.swift
import NonResilientLib // expected-error {{compiled module was created for a different distribution channel '' than the local compiler 'restricted-channel', please ensure 'NonResilientLib' is found from the expected path:}}
foo()
/// Building a NonResilientLib client should reject the import for a tagged compiler
// RUN: env SWIFT_FORCE_SWIFTMODULE_CHANNEL=restricted-channel \
// RUN: %target-swift-frontend -typecheck %t/NonResilientClient.swift \
// RUN: -swift-version 5 -I %t/build -parse-stdlib -module-cache-path %t/cache \
// RUN: -verify -verify-ignore-unknown
/// 3. Test importing the resilient no-channel library.
//--- ResilientClient.swift
import ResilientLib // expected-reject-error {{compiled module was created for a different distribution channel '' than the local compiler 'restricted-channel', please ensure 'ResilientLib' is found from the expected path:}}
// expected-rebuild-remark @-1 {{rebuilding module 'ResilientLib' from interface}}
// expected-rebuild-note @-2 {{compiled module is out of date}}
// expected-rebuild-note @-3 {{compiled for a different distribution channel}}
foo()
/// ResilientLib client should rebuild from swiftinterface when available.
// RUN: env SWIFT_FORCE_SWIFTMODULE_CHANNEL=restricted-channel \
// RUN: %target-swift-frontend -typecheck %t/ResilientClient.swift \
// RUN: -swift-version 5 -I %t/build -parse-stdlib -module-cache-path %t/cache \
// RUN: -verify -verify-additional-prefix rebuild- -Rmodule-interface-rebuild
// RUN: rm %t/build/ResilientLib.swiftinterface
// RUN: %empty-directory(%t/cache)
/// Building a ResilientLib client should succeed in no-channel / dev mode.
// RUN: %target-swift-frontend -typecheck %t/ResilientClient.swift \
// RUN: -swift-version 5 -I %t/build -parse-stdlib -module-cache-path %t/cache
/// Building a ResilientLib client should reject the import for a channel compiler.
// RUN: env SWIFT_FORCE_SWIFTMODULE_CHANNEL=restricted-channel \
// RUN: %target-swift-frontend -typecheck %t/ResilientClient.swift \
// RUN: -swift-version 5 -I %t/build -parse-stdlib -module-cache-path %t/cache \
// RUN: -verify -verify-ignore-unknown -verify-additional-prefix reject-
/// Building a ResilientLib client should succeed for a channel compiler with SWIFT_IGNORE_SWIFTMODULE_REVISION
// RUN: env SWIFT_FORCE_SWIFTMODULE_CHANNEL=restricted-channel SWIFT_IGNORE_SWIFTMODULE_REVISION=true \
// RUN: %target-swift-frontend -typecheck %t/ResilientClient.swift \
// RUN: -swift-version 5 -I %t/build -parse-stdlib -module-cache-path %t/cache
/// 4. Test importing the channel restricted library
//--- ChannelClient.swift
import ChannelLib // expected-reject-error {{compiled module was created for a different distribution channel 'restricted-channel' than the local compiler 'other-channel', please ensure 'ChannelLib' is found from the expected path}}
foo()
/// Importing ChannelLib should succeed with the same channel.
// RUN: env SWIFT_FORCE_SWIFTMODULE_CHANNEL=restricted-channel \
// RUN: %target-swift-frontend -typecheck %t/ChannelClient.swift \
// RUN: -swift-version 5 -I %t/build -parse-stdlib -module-cache-path %t/cache
/// Importing ChannelLib should succeed with a dev compiler.
// RUN: %target-swift-frontend -typecheck %t/ChannelClient.swift \
// RUN: -swift-version 5 -I %t/build -parse-stdlib -module-cache-path %t/cache
/// Importing ChannelLib from a different channel should be rejected.
// RUN: env SWIFT_FORCE_SWIFTMODULE_CHANNEL=other-channel \
// RUN: %target-swift-frontend -typecheck %t/ChannelClient.swift \
// RUN: -swift-version 5 -I %t/build -parse-stdlib -module-cache-path %t/cache \
// RUN: -verify -verify-ignore-unknown -verify-additional-prefix reject-