Skip to content

Commit 4e69160

Browse files
authored
Merge pull request #62886 from xymus/deser-safety-infra
[Serialization] Prepare the infrastructure for the deserialization safety feature
2 parents f237fba + 294b5d0 commit 4e69160

File tree

13 files changed

+151
-3
lines changed

13 files changed

+151
-3
lines changed

include/swift/Basic/LangOptions.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -357,11 +357,15 @@ namespace swift {
357357
bool EnableTargetOSChecking = true;
358358

359359
/// Whether to attempt to recover from missing cross-references and other
360-
/// errors when deserializing from a Swift module.
360+
/// errors when deserializing from a binary swiftmodule file.
361361
///
362-
/// This is a staging flag; eventually it will be removed.
362+
/// This flag should only be used in testing.
363363
bool EnableDeserializationRecovery = true;
364364

365+
/// Enable early skipping deserialization of decls that are marked as
366+
/// unsafe to read.
367+
bool EnableDeserializationSafety = false;
368+
365369
/// Whether to enable the new operator decl and precedencegroup lookup
366370
/// behavior. This is a staging flag, and will be removed in the future.
367371
bool EnableNewOperatorLookup = false;

include/swift/Option/FrontendOptions.td

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,13 @@ def disable_deserialization_recovery :
564564
Flag<["-"], "disable-deserialization-recovery">,
565565
HelpText<"Don't attempt to recover from missing xrefs (etc) in swiftmodules">;
566566

567+
def enable_deserialization_safety :
568+
Flag<["-"], "enable-deserialization-safety">,
569+
HelpText<"Avoid reading potentially unsafe decls in swiftmodules">;
570+
def disable_deserialization_safety :
571+
Flag<["-"], "disable-deserialization-safety">,
572+
HelpText<"Don't avoid reading potentially unsafe decls in swiftmodules">;
573+
567574
def enable_experimental_string_processing :
568575
Flag<["-"], "enable-experimental-string-processing">,
569576
HelpText<"Enable experimental string processing">;

lib/Frontend/CompilerInvocation.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,12 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
500500
= A->getOption().matches(OPT_enable_deserialization_recovery);
501501
}
502502

503+
if (auto A = Args.getLastArg(OPT_enable_deserialization_safety,
504+
OPT_disable_deserialization_safety)) {
505+
Opts.EnableDeserializationSafety
506+
= A->getOption().matches(OPT_enable_deserialization_safety);
507+
}
508+
503509
// Whether '/.../' regex literals are enabled. This implies experimental
504510
// string processing.
505511
if (Args.hasArg(OPT_enable_bare_slash_regex)) {

lib/Frontend/Frontend.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,11 @@ bool CompilerInstance::setUpASTContextIfNeeded() {
274274

275275
registerIRGenSILTransforms(*Context);
276276

277+
if (Invocation.getFrontendOptions().RequestedAction ==
278+
FrontendOptions::ActionType::MergeModules ||
279+
Invocation.getLangOptions().DebuggerSupport)
280+
Invocation.getLangOptions().EnableDeserializationSafety = false;
281+
277282
if (setUpModuleLoaders())
278283
return true;
279284

lib/Serialization/DeclTypeRecordNodes.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@ OTHER(LOCAL_DISCRIMINATOR, 127)
182182
OTHER(PRIVATE_DISCRIMINATOR, 128)
183183
OTHER(FILENAME_FOR_PRIVATE, 129)
184184

185+
OTHER(DESERIALIZATION_SAFETY, 130)
186+
185187
// 140 is unused; was ABSTRACT_PROTOCOL_CONFORMANCE
186188
OTHER(NORMAL_PROTOCOL_CONFORMANCE, 141)
187189
OTHER(SPECIALIZED_PROTOCOL_CONFORMANCE, 142)

lib/Serialization/Deserialization.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ const char DeclAttributesDidNotMatch::ID = '\0';
155155
void DeclAttributesDidNotMatch::anchor() {}
156156
const char InvalidRecordKindError::ID = '\0';
157157
void InvalidRecordKindError::anchor() {}
158+
const char UnsafeDeserializationError::ID = '\0';
159+
void UnsafeDeserializationError::anchor() {}
158160

159161
/// Skips a single record in the bitstream.
160162
///
@@ -4750,6 +4752,14 @@ ModuleFile::getDeclChecked(
47504752
IDC->setDeclID(DID);
47514753
}
47524754
}
4755+
4756+
LLVM_DEBUG(
4757+
if (auto *VD = dyn_cast_or_null<ValueDecl>(declOrOffset.get())) {
4758+
llvm::dbgs() << "Deserialized: '";
4759+
llvm::dbgs() << VD->getName();
4760+
llvm::dbgs() << "'\n";
4761+
});
4762+
47534763
return declOrOffset;
47544764
}
47554765

@@ -5403,6 +5413,17 @@ llvm::Error DeclDeserializer::deserializeDeclCommon() {
54035413
IdentifierID filenameID;
54045414
decls_block::FilenameForPrivateLayout::readRecord(scratch, filenameID);
54055415
filenameForPrivate = MF.getIdentifierText(filenameID);
5416+
} else if (recordID == decls_block::DESERIALIZATION_SAFETY) {
5417+
IdentifierID declID;
5418+
decls_block::DeserializationSafetyLayout::readRecord(scratch, declID);
5419+
5420+
if (MF.getResilienceStrategy() == ResilienceStrategy::Resilient &&
5421+
MF.getContext().LangOpts.EnableDeserializationSafety) {
5422+
auto name = MF.getIdentifier(declID);
5423+
LLVM_DEBUG(llvm::dbgs() << "Skipping unsafe deserialization: '"
5424+
<< name << "'\n");
5425+
return llvm::make_error<UnsafeDeserializationError>(name);
5426+
}
54065427
} else {
54075428
return llvm::Error::success();
54085429
}

lib/Serialization/DeserializationErrors.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,27 @@ class InvalidRecordKindError :
494494
}
495495
};
496496

497+
// Decl was not deserialized because it's an internal detail maked as unsafe
498+
// at serialization.
499+
class UnsafeDeserializationError : public llvm::ErrorInfo<UnsafeDeserializationError, DeclDeserializationError> {
500+
friend ErrorInfo;
501+
static const char ID;
502+
void anchor() override;
503+
504+
public:
505+
UnsafeDeserializationError(Identifier name) {
506+
this->name = name;
507+
}
508+
509+
void log(raw_ostream &OS) const override {
510+
OS << "Decl '" << name << "' is unsafe to deserialize";
511+
}
512+
513+
std::error_code convertToErrorCode() const override {
514+
return llvm::inconvertibleErrorCode();
515+
}
516+
};
517+
497518
LLVM_NODISCARD
498519
static inline std::unique_ptr<llvm::ErrorInfoBase>
499520
takeErrorInfo(llvm::Error error) {

lib/Serialization/ModuleFormat.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
5858
/// describe what change you made. The content of this comment isn't important;
5959
/// it just ensures a conflict if two people change the module format.
6060
/// Don't worry about adhering to the 80-column limit for this line.
61-
const uint16_t SWIFTMODULE_VERSION_MINOR = 731; // macro definition
61+
const uint16_t SWIFTMODULE_VERSION_MINOR = 732; // deserialization safety
6262

6363
/// A standard hash seed used for all string hashes in a serialized module.
6464
///
@@ -1809,6 +1809,11 @@ namespace decls_block {
18091809
IdentifierIDField // the file name, as an identifier
18101810
>;
18111811

1812+
using DeserializationSafetyLayout = BCRecordLayout<
1813+
DESERIALIZATION_SAFETY,
1814+
IdentifierIDField // name to debug access to unsafe decl
1815+
>;
1816+
18121817
using NormalProtocolConformanceLayout = BCRecordLayout<
18131818
NORMAL_PROTOCOL_CONFORMANCE,
18141819
DeclIDField, // the protocol

lib/Serialization/Serialization.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5336,6 +5336,7 @@ void Serializer::writeAllDeclsAndTypes() {
53365336
registerDeclTypeAbbr<LocalDiscriminatorLayout>();
53375337
registerDeclTypeAbbr<PrivateDiscriminatorLayout>();
53385338
registerDeclTypeAbbr<FilenameForPrivateLayout>();
5339+
registerDeclTypeAbbr<DeserializationSafetyLayout>();
53395340
registerDeclTypeAbbr<MembersLayout>();
53405341
registerDeclTypeAbbr<XRefLayout>();
53415342

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: split-file %s %t
3+
4+
// REQUIRES: asserts
5+
6+
// RUN: %target-swift-frontend -emit-module %t/HiddenLib.swift \
7+
// RUN: -enable-library-evolution \
8+
// RUN: -emit-module-path %t/HiddenLib.swiftmodule
9+
10+
// RUN: %target-swift-frontend -emit-module %t/Lib.swift -I %t \
11+
// RUN: -enable-library-evolution \
12+
// RUN: -emit-module-path %t/Lib.swiftmodule \
13+
// RUN: -emit-module-interface-path %t/Lib.swiftinterface
14+
15+
// RUN: %target-swift-frontend -typecheck %t/Client.swift -I %t \
16+
// RUN: -verify -Xllvm -debug-only=Serialization 2>&1 \
17+
// RUN: | %FileCheck --check-prefixes=NEEDED,UNSAFE %s
18+
19+
// RUN: rm %t/Lib.swiftmodule
20+
21+
// RUN: %target-swift-frontend -typecheck %t/Client.swift -I %t \
22+
// RUN: -verify -Xllvm -debug-only=Serialization 2>&1 \
23+
// RUN: | %FileCheck --check-prefixes=NEEDED,CLEAN %s
24+
25+
/// Decls part of the API needed by the client.
26+
// NEEDED-NOT: Deserialized: 'refToIOI()'
27+
// NEEDED: Deserialized: 'PublicStruct'
28+
// NEEDED: Deserialized: 'publicFunc()'
29+
30+
/// Internal details dangerous to load.
31+
// UNSAFE: Deserialized: 'internalFunc()'
32+
// UNSAFE: Deserialized: 'privateFunc()'
33+
// UNSAFE: Deserialized: 'fileprivateFunc()'
34+
35+
/// Decls removed by rebuilding from the swiftinterface.
36+
// CLEAN-NOT: Deserialized: 'internalFunc()'
37+
// CLEAN-NOT: Deserialized: 'privateFunc()'
38+
// CLEAN-NOT: Deserialized: 'fileprivateFunc()'
39+
40+
//--- HiddenLib.swift
41+
42+
public struct HiddenStruct {
43+
public init() {}
44+
}
45+
46+
//--- Lib.swift
47+
48+
@_implementationOnly import HiddenLib
49+
50+
public class PublicStruct {
51+
public init() {}
52+
53+
public func publicFunc() {}
54+
internal func internalFunc() {}
55+
private func privateFunc() {}
56+
fileprivate func fileprivateFunc() {}
57+
58+
internal func refToIOI() -> HiddenStruct {
59+
return HiddenStruct();
60+
}
61+
}
62+
63+
//--- Client.swift
64+
65+
import Lib
66+
67+
var x = PublicStruct()
68+
69+
// Trigger a typo correction that reads all members.
70+
x.notAMember() // expected-error {{value of type 'PublicStruct' has no member 'notAMember'}}

tools/lldb-moduleimport-test/lldb-moduleimport-test.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ static bool validateModule(
6363
llvm::outs() << "error: loadFromSerializedAST() failed\n";
6464
return false;
6565
}
66+
CI.getLangOptions().EnableDeserializationSafety = false;
6667

6768
if (Verbose) {
6869
if (!info.shortVersion.empty())

tools/sil-opt/SILOpt.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,7 @@ int main(int argc, char **argv) {
562562
Invocation.getLangOptions().DisableAvailabilityChecking = true;
563563
Invocation.getLangOptions().EnableAccessControl = false;
564564
Invocation.getLangOptions().EnableObjCAttrRequiresFoundation = false;
565+
Invocation.getLangOptions().EnableDeserializationSafety = false;
565566
if (auto overrideKind = getASTOverrideKind()) {
566567
Invocation.getLangOptions().ASTVerifierOverride = *overrideKind;
567568
}

tools/swift-ide-test/swift-ide-test.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3002,6 +3002,10 @@ static int doPrintModules(const CompilerInvocation &InitInvok,
30023002
bool SynthesizeExtensions) {
30033003
CompilerInvocation Invocation(InitInvok);
30043004

3005+
// Read everything from loaded modules, including internal details to
3006+
// test the behavior around non-public access levels.
3007+
Invocation.getLangOptions().EnableDeserializationSafety = false;
3008+
30053009
CompilerInstance CI;
30063010
// Display diagnostics to stderr.
30073011
PrintingDiagnosticConsumer PrintDiags;

0 commit comments

Comments
 (0)