Skip to content

Commit 5c2122a

Browse files
committed
[Serialization] Restrict resilient swiftmodules to the compiler that built them
Introduce a new loading restriction that is more strict than the serialization version check on swiftmodules. Tagged compilers will only load library-evolution enabled swiftmodules that are produced by a compiler with the exact same revision id. This will be more reliable in production environments than using the serialization version which we forgot to update from time to time. This shouldn't affect development compilers that will still load any module with a compatible serialization version. rdar://83105234
1 parent 2cd0d5e commit 5c2122a

File tree

7 files changed

+121
-3
lines changed

7 files changed

+121
-3
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,10 @@ ERROR(serialization_module_too_old,Fatal,
766766
"compiled module was created by an older version of the compiler; "
767767
"rebuild %0 and try again: %1",
768768
(Identifier, StringRef))
769+
ERROR(serialization_module_incompatible_revision,Fatal,
770+
"compiled module was created by a different version of the compiler; "
771+
"rebuild %0 and try again: %1",
772+
(Identifier, StringRef))
769773
ERROR(serialization_missing_single_dependency,Fatal,
770774
"missing required module '%0'", (StringRef))
771775
ERROR(serialization_missing_dependencies,Fatal,

include/swift/Serialization/Validation.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ enum class Status {
3939
/// compiler.
4040
FormatTooNew,
4141

42+
/// The precise revision version doesn't match.
43+
RevisionIncompatible,
44+
4245
/// The module file depends on another module that can't be loaded.
4346
MissingDependency,
4447

lib/Serialization/ModuleFileSharedCore.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,35 @@ validateControlBlock(llvm::BitstreamCursor &cursor,
303303
result.sdkName = blobData;
304304
break;
305305
}
306+
case control_block::REVISION: {
307+
// Tagged compilers should load only resilient modules if they were
308+
// produced by the exact same version.
309+
310+
// Disable this restriction for compiler testing by setting this
311+
// env var to any value.
312+
static const char* ignoreRevision =
313+
::getenv("SWIFT_DEBUG_IGNORE_SWIFTMODULE_REVISION");
314+
if (ignoreRevision)
315+
break;
316+
317+
// Override this env var for testing, forcing the behavior of a tagged
318+
// compiler and using the env var value to override this compiler's
319+
// revision.
320+
static const char* forcedDebugRevision =
321+
::getenv("SWIFT_DEBUG_FORCE_SWIFTMODULE_REVISION");
322+
323+
bool isCompilerTagged = forcedDebugRevision ||
324+
- !version::Version::getCurrentCompilerVersion().empty();
325+
326+
StringRef moduleRevision = blobData;
327+
if (isCompilerTagged && !moduleRevision.empty()) {
328+
std::string compilerRevision = forcedDebugRevision ?
329+
forcedDebugRevision : version::getSwiftRevision();
330+
if (moduleRevision != compilerRevision)
331+
result.status = Status::RevisionIncompatible;
332+
}
333+
break;
334+
}
306335
default:
307336
// Unknown metadata record, possibly for use by a future version of the
308337
// module format.

lib/Serialization/ModuleFormat.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
5656
/// describe what change you made. The content of this comment isn't important;
5757
/// it just ensures a conflict if two people change the module format.
5858
/// Don't worry about adhering to the 80-column limit for this line.
59-
const uint16_t SWIFTMODULE_VERSION_MINOR = 629; // BuilderSDK
59+
const uint16_t SWIFTMODULE_VERSION_MINOR = 630; // Precise
6060

6161
/// A standard hash seed used for all string hashes in a serialized module.
6262
///
@@ -755,7 +755,8 @@ namespace control_block {
755755
METADATA = 1,
756756
MODULE_NAME,
757757
TARGET,
758-
SDK_NAME
758+
SDK_NAME,
759+
REVISION,
759760
};
760761

761762
using MetadataLayout = BCRecordLayout<
@@ -785,6 +786,11 @@ namespace control_block {
785786
SDK_NAME,
786787
BCBlob
787788
>;
789+
790+
using RevisionLayout = BCRecordLayout<
791+
REVISION,
792+
BCBlob
793+
>;
788794
}
789795

790796
/// The record types within the options block (a sub-block of the control

lib/Serialization/Serialization.cpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,7 @@ void Serializer::writeBlockInfoBlock() {
803803
BLOCK_RECORD(control_block, MODULE_NAME);
804804
BLOCK_RECORD(control_block, TARGET);
805805
BLOCK_RECORD(control_block, SDK_NAME);
806+
BLOCK_RECORD(control_block, REVISION);
806807

807808
BLOCK(OPTIONS_BLOCK);
808809
BLOCK_RECORD(options_block, SDK_PATH);
@@ -949,11 +950,12 @@ void Serializer::writeBlockInfoBlock() {
949950

950951
void Serializer::writeHeader(const SerializationOptions &options) {
951952
{
952-
BCBlockRAII restoreBlock(Out, CONTROL_BLOCK_ID, 3);
953+
BCBlockRAII restoreBlock(Out, CONTROL_BLOCK_ID, 4);
953954
control_block::ModuleNameLayout ModuleName(Out);
954955
control_block::MetadataLayout Metadata(Out);
955956
control_block::TargetLayout Target(Out);
956957
control_block::SDKNameLayout SDKName(Out);
958+
control_block::RevisionLayout Revision(Out);
957959

958960
ModuleName.emit(ScratchRecord, M->getName().str());
959961

@@ -992,6 +994,18 @@ void Serializer::writeHeader(const SerializationOptions &options) {
992994

993995
Target.emit(ScratchRecord, M->getASTContext().LangOpts.Target.str());
994996

997+
// Write the producer's Swift revision only for resilient modules.
998+
if (M->getResilienceStrategy() != ResilienceStrategy::Default) {
999+
auto revision = version::getSwiftRevision();
1000+
1001+
static const char* forcedDebugRevision =
1002+
::getenv("SWIFT_DEBUG_FORCE_SWIFTMODULE_REVISION");
1003+
if (forcedDebugRevision)
1004+
revision = forcedDebugRevision;
1005+
1006+
Revision.emit(ScratchRecord, revision);
1007+
}
1008+
9951009
{
9961010
llvm::BCBlockRAII restoreBlock(Out, OPTIONS_BLOCK_ID, 4);
9971011

lib/Serialization/SerializedModuleLoader.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,10 @@ void swift::serialization::diagnoseSerializedASTLoadFailure(
839839
Ctx.Diags.diagnose(diagLoc, diag::serialization_module_too_old, ModuleName,
840840
moduleBufferID);
841841
break;
842+
case serialization::Status::RevisionIncompatible:
843+
Ctx.Diags.diagnose(diagLoc, diag::serialization_module_incompatible_revision,
844+
ModuleName, moduleBufferID);
845+
break;
842846
case serialization::Status::Malformed:
843847
Ctx.Diags.diagnose(diagLoc, diag::serialization_malformed_module,
844848
moduleBufferID);
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// RUN: %empty-directory(%t/cache)
2+
// RUN: %empty-directory(%t/build)
3+
// RUN: %{python} %utils/split_file.py -o %t %s
4+
5+
/// 1. Compile Lib.swift as non-resilient untagged, resilient untagged, and resilient tagged
6+
// BEGIN Lib.swift
7+
public func foo() {}
8+
9+
/// Build Lib as a resilient and non-resilient swiftmodule
10+
// RUN: %target-swift-frontend -emit-module %t/Lib.swift -swift-version 5 -o %t/build -parse-stdlib -module-cache-path %t/cache -module-name ResilientLib -enable-library-evolution
11+
// RUN: %target-swift-frontend -emit-module %t/Lib.swift -swift-version 5 -o %t/build -parse-stdlib -module-cache-path %t/cache -module-name NonresilientLib
12+
// RUN: SWIFT_DEBUG_FORCE_SWIFTMODULE_REVISION=my-revision \
13+
// RUN: %target-swift-frontend -emit-module %t/Lib.swift -swift-version 5 -o %t/build -parse-stdlib -module-cache-path %t/cache -module-name TaggedLib -enable-library-evolution
14+
15+
16+
/// 2. Test importing the non-resilient untagged library
17+
// BEGIN NonresilientClient.swift
18+
import NonresilientLib
19+
foo()
20+
21+
/// Building a NonresilientLib client should always succeed
22+
// RUN: %target-swift-frontend -typecheck %t/NonresilientClient.swift -swift-version 5 -I %t/build -parse-stdlib -module-cache-path %t/cache
23+
// RUN: SWIFT_DEBUG_FORCE_SWIFTMODULE_REVISION=my-revision \
24+
// RUN: %target-swift-frontend -typecheck %t/NonresilientClient.swift -swift-version 5 -I %t/build -parse-stdlib -module-cache-path %t/cache
25+
26+
27+
/// 3. Test importing the resilient untagged library
28+
// BEGIN ResilientClient.swift
29+
import ResilientLib
30+
foo()
31+
32+
/// Building a ResilientLib client should succeed in non-revision/dev mode
33+
// RUN: %target-swift-frontend -typecheck %t/ResilientClient.swift -swift-version 5 -I %t/build -parse-stdlib -module-cache-path %t/cache
34+
35+
/// Building a ResilientLib client should reject the import for a tagged compiler
36+
// RUN: SWIFT_DEBUG_FORCE_SWIFTMODULE_REVISION=not-a-revision \
37+
// RUN: not %target-swift-frontend -typecheck %t/ResilientClient.swift -swift-version 5 -I %t/build -parse-stdlib -module-cache-path %t/cache 2>&1 | %FileCheck %s
38+
// CHECK: compiled module was created by a different version of the compiler; rebuild 'ResilientLib' and try again: {{.*}}ResilientLib.swiftmodule
39+
40+
/// Building a ResilientLib client should succeed for a tagged compiler with SWIFT_DEBUG_IGNORE_SWIFTMODULE_REVISION
41+
// RUN: SWIFT_DEBUG_FORCE_SWIFTMODULE_REVISION=not-a-revision SWIFT_DEBUG_IGNORE_SWIFTMODULE_REVISION=true \
42+
// RUN: %target-swift-frontend -typecheck %t/ResilientClient.swift -swift-version 5 -I %t/build -parse-stdlib -module-cache-path %t/cache
43+
44+
45+
/// 4. Test importing the resilient tagged library
46+
// BEGIN TaggedClient.swift
47+
import TaggedLib
48+
foo()
49+
50+
/// Importing TaggedLib should success with the same tag or a dev compiler
51+
// RUN: SWIFT_DEBUG_FORCE_SWIFTMODULE_REVISION=my-revision \
52+
// RUN: %target-swift-frontend -typecheck %t/TaggedClient.swift -swift-version 5 -I %t/build -parse-stdlib -module-cache-path %t/cache
53+
// RUN: %target-swift-frontend -typecheck %t/TaggedClient.swift -swift-version 5 -I %t/build -parse-stdlib -module-cache-path %t/cache
54+
55+
/// Building a TaggedLib client should reject the import for a different tagged compiler
56+
// RUN: SWIFT_DEBUG_FORCE_SWIFTMODULE_REVISION=not-a-revision \
57+
// RUN: not %target-swift-frontend -typecheck %t/TaggedClient.swift -swift-version 5 -I %t/build -parse-stdlib -module-cache-path %t/cache 2>&1 | %FileCheck %s --check-prefix=CHECK-TAGGED
58+
// CHECK-TAGGED: compiled module was created by a different version of the compiler; rebuild 'TaggedLib' and try again: {{.*}}TaggedLib.swiftmodule

0 commit comments

Comments
 (0)