Skip to content

Commit e032b31

Browse files
authored
Merge pull request #63096 from xymus/deser-safety-writing
[Serialization] Introduce main deserialization safety logic, on the writer side
2 parents 13ff3fe + a91bb49 commit e032b31

File tree

7 files changed

+487
-8
lines changed

7 files changed

+487
-8
lines changed

lib/Serialization/ModuleFile.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -885,8 +885,13 @@ void ModuleFile::lookupObjCMethods(
885885
auto found = *known;
886886
for (const auto &result : found) {
887887
// Deserialize the method and add it to the list.
888-
if (auto func = dyn_cast_or_null<AbstractFunctionDecl>(
889-
getDecl(std::get<2>(result))))
888+
auto declOrError = getDeclChecked(std::get<2>(result));
889+
if (!declOrError) {
890+
consumeError(declOrError.takeError());
891+
continue;
892+
}
893+
894+
if (auto func = dyn_cast_or_null<AbstractFunctionDecl>(declOrError.get()))
890895
results.push_back(func);
891896
}
892897
}

lib/Serialization/Serialization.cpp

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@
7070

7171
#include <vector>
7272

73+
#define DEBUG_TYPE "Serialization"
74+
7375
using namespace swift;
7476
using namespace swift::serialization;
7577
using namespace llvm::support;
@@ -3093,6 +3095,170 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
30933095
}
30943096
}
30953097

3098+
/// Determine if \p decl is safe to deserialize when it's public
3099+
/// or otherwise needed by the client in normal builds, this should usually
3100+
/// correspond to logic in type-checking ensuring these safe decls don't
3101+
/// refer to implementation details. We have to be careful not to mark
3102+
/// anything needed by a client as unsafe as the client will reject reading
3103+
/// it, but at the same time keep the safety checks precise to avoid
3104+
/// XRef errors and such.
3105+
///
3106+
/// \p decl should be either an \c ExtensionDecl or a \c ValueDecl.
3107+
static bool isDeserializationSafe(const Decl *decl) {
3108+
if (auto ext = dyn_cast<ExtensionDecl>(decl)) {
3109+
// Consider extensions as safe as their extended type.
3110+
auto nominalType = ext->getExtendedNominal();
3111+
if (!nominalType ||
3112+
!isDeserializationSafe(nominalType))
3113+
return false;
3114+
3115+
// We can mark the extension unsafe only if it has no public members.
3116+
auto members = ext->getMembers();
3117+
auto hasSafeMembers = std::any_of(members.begin(), members.end(),
3118+
[](const Decl *D) -> bool {
3119+
if (auto VD = dyn_cast<ValueDecl>(D))
3120+
return isDeserializationSafe(VD);
3121+
return true;
3122+
});
3123+
if (hasSafeMembers)
3124+
return true;
3125+
3126+
// We can mark the extension unsafe only if it has no public
3127+
// conformances.
3128+
auto protocols = ext->getLocalProtocols(
3129+
ConformanceLookupKind::OnlyExplicit);
3130+
bool hasSafeConformances = std::any_of(protocols.begin(),
3131+
protocols.end(),
3132+
isDeserializationSafe);
3133+
if (hasSafeConformances)
3134+
return true;
3135+
3136+
// Truly empty extensions are safe, it may happen in swiftinterfaces.
3137+
if (members.empty() && protocols.size() == 0)
3138+
return true;
3139+
3140+
return false;
3141+
}
3142+
3143+
auto value = cast<ValueDecl>(decl);
3144+
3145+
// A decl is safe if formally accessible publicly.
3146+
auto accessScope = value->getFormalAccessScope(/*useDC=*/nullptr,
3147+
/*treatUsableFromInlineAsPublic=*/true);
3148+
if (accessScope.isPublic())
3149+
return true;
3150+
3151+
// Testable allows access to internal details.
3152+
if (value->getDeclContext()->getParentModule()->isTestingEnabled() &&
3153+
accessScope.isInternal())
3154+
return true;
3155+
3156+
if (auto accessor = dyn_cast<AccessorDecl>(value))
3157+
// Accessors are as safe as their storage.
3158+
if (isDeserializationSafe(accessor->getStorage()))
3159+
return true;
3160+
3161+
// Frozen fields are always safe.
3162+
if (auto var = dyn_cast<VarDecl>(value)) {
3163+
if (var->isLayoutExposedToClients())
3164+
return true;
3165+
3166+
// Consider all lazy var storage as "safe".
3167+
// FIXME: We should keep track of what lazy var is associated to the
3168+
// storage for them to preserve the same safeness.
3169+
if (var->isLazyStorageProperty())
3170+
return true;
3171+
3172+
// Property wrappers storage is as safe as the wrapped property.
3173+
if (VarDecl *wrapped = var->getOriginalWrappedProperty())
3174+
if (isDeserializationSafe(wrapped))
3175+
return true;
3176+
}
3177+
3178+
return false;
3179+
}
3180+
3181+
/// Write a \c DeserializationSafetyLayout record only when \p decl is unsafe
3182+
/// to deserialize.
3183+
///
3184+
/// \sa isDeserializationSafe
3185+
void writeDeserializationSafety(const Decl *decl) {
3186+
using namespace decls_block;
3187+
3188+
auto DC = decl->getDeclContext();
3189+
if (!DC->getParentModule()->isResilient())
3190+
return;
3191+
3192+
// Everything should be safe in a swiftinterface. So, don't emit any safety
3193+
// record when building a swiftinterface in release builds. Debug builds
3194+
// instead print inconsistencies.
3195+
auto parentSF = DC->getParentSourceFile();
3196+
bool fromModuleInterface = parentSF &&
3197+
parentSF->Kind == SourceFileKind::Interface;
3198+
#if NDEBUG
3199+
if (fromModuleInterface)
3200+
return;
3201+
#endif
3202+
3203+
// Private imports allow safe access to everything.
3204+
if (DC->getParentModule()->arePrivateImportsEnabled())
3205+
return;
3206+
3207+
// Ignore things with no access level.
3208+
// Note: There's likely room to report some of these as unsafe to prevent
3209+
// failures.
3210+
if (isa<GenericTypeParamDecl>(decl) ||
3211+
isa<OpaqueTypeDecl>(decl) ||
3212+
isa<ParamDecl>(decl) ||
3213+
isa<EnumCaseDecl>(decl) ||
3214+
isa<EnumElementDecl>(decl))
3215+
return;
3216+
3217+
if (!isa<ValueDecl>(decl) && !isa<ExtensionDecl>(decl))
3218+
return;
3219+
3220+
// Don't look at decls inside functions and
3221+
// check the ValueDecls themselves.
3222+
auto declIsSafe = DC->isLocalContext() ||
3223+
isDeserializationSafe(decl);
3224+
#ifdef NDEBUG
3225+
// In release builds, bail right away if the decl is safe.
3226+
// In debug builds, wait to bail after the debug prints and asserts.
3227+
if (declIsSafe)
3228+
return;
3229+
#endif
3230+
3231+
// Write a human readable name to an identifier.
3232+
SmallString<64> out;
3233+
llvm::raw_svector_ostream outStream(out);
3234+
if (auto val = dyn_cast<ValueDecl>(decl)) {
3235+
outStream << val->getName();
3236+
} else if (auto ext = dyn_cast<ExtensionDecl>(decl)) {
3237+
outStream << "extension ";
3238+
if (auto nominalType = ext->getExtendedNominal())
3239+
outStream << nominalType->getName();
3240+
}
3241+
auto name = S.getASTContext().getIdentifier(out);
3242+
3243+
LLVM_DEBUG(
3244+
llvm::dbgs() << "Serialization safety, "
3245+
<< (declIsSafe? "safe" : "unsafe")
3246+
<< ": '" << name << "'\n";
3247+
assert((declIsSafe || !fromModuleInterface) &&
3248+
"All swiftinterface decls should be deserialization safe");
3249+
);
3250+
3251+
#ifndef NDEBUG
3252+
// Bail out here in debug builds, release builds would bailed out earlier.
3253+
if (declIsSafe)
3254+
return;
3255+
#endif
3256+
3257+
auto abbrCode = S.DeclTypeAbbrCodes[DeserializationSafetyLayout::Code];
3258+
DeserializationSafetyLayout::emitRecord(S.Out, S.ScratchRecord, abbrCode,
3259+
S.addDeclBaseNameRef(name));
3260+
}
3261+
30963262
void writeForeignErrorConvention(const ForeignErrorConvention &fec) {
30973263
using namespace decls_block;
30983264

@@ -3410,6 +3576,8 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
34103576
if (D->isInvalid())
34113577
writeDeclErrorFlag();
34123578

3579+
writeDeserializationSafety(D);
3580+
34133581
// Emit attributes (if any).
34143582
for (auto Attr : D->getAttrs())
34153583
writeDeclAttribute(D, Attr);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// RUN: %target-swift-frontend -typecheck -verify %s \
2+
// RUN: -enable-deserialization-safety \
3+
// RUN: -Xllvm -debug-only=Serialization 2>&1 | %FileCheck %s
4+
5+
// REQUIRES: objc_interop
6+
// REQUIRES: asserts
7+
8+
import Foundation
9+
10+
// Fails at reading __SwiftNativeNSEnumerator.init() on macOS.
11+
NSString.instancesRespond(to: "init") // expected-warning {{use of string literal for Objective-C selectors is deprecated; use '#selector' instead}}
12+
// CHECK: Skipping unsafe deserialization: 'init()'
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: split-file %s %t
3+
4+
// REQUIRES: asserts
5+
// REQUIRES: VENDOR=apple
6+
7+
/// Build library.
8+
// RUN: %target-swift-frontend -emit-module %t/Lib.swift \
9+
// RUN: -enable-library-evolution -swift-version 5 \
10+
// RUN: -emit-module-path %t/Lib.swiftmodule
11+
12+
/// Build client.
13+
// RUN: %target-swift-frontend -typecheck %t/Client.swift -I %t \
14+
// RUN: -verify -Xllvm -debug-only=Serialization \
15+
// RUN: -enable-deserialization-safety 2>&1 \
16+
// RUN: | %FileCheck --check-prefixes=SAFE %s
17+
18+
/// Decls skips by the deserialization safety logic.
19+
// SAFE-NOT: InternalClass
20+
// SAFE: Deserialized: 'PublicClass'
21+
// SAFE: Deserialized: 'publicFunc()'
22+
// SAFE: Skipping unsafe deserialization: 'privateFunc()'
23+
// SAFE: Skipping unsafe deserialization: 'fileprivateFunc()'
24+
// SAFE: Skipping unsafe deserialization: 'internalFunc()'
25+
26+
//--- Lib.swift
27+
28+
import Foundation
29+
30+
@objc
31+
public class PublicClass : NSObject {
32+
public func publicFunc() {}
33+
}
34+
35+
@objc
36+
internal class InternalClass : NSObject {
37+
private func privateFunc() {}
38+
fileprivate func fileprivateFunc() {}
39+
internal func internalFunc() {}
40+
}
41+
42+
//--- Client.swift
43+
44+
import Lib
45+
46+
var x: AnyObject
47+
48+
// Trigger a typo correction to read all members of all subtypes to NSObject.
49+
x.notAMember() // expected-error {{value of type 'AnyObject' has no member 'notAMember'}}

test/Serialization/Safety/skip-internal-details.swift renamed to test/Serialization/Safety/skip-reading-internal-details.swift

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,31 @@
33

44
// REQUIRES: asserts
55

6+
/// Build libraries.
67
// RUN: %target-swift-frontend -emit-module %t/HiddenLib.swift \
7-
// RUN: -enable-library-evolution \
8+
// RUN: -enable-library-evolution -swift-version 5 \
89
// RUN: -emit-module-path %t/HiddenLib.swiftmodule
9-
1010
// RUN: %target-swift-frontend -emit-module %t/Lib.swift -I %t \
1111
// RUN: -enable-library-evolution \
1212
// RUN: -emit-module-path %t/Lib.swiftmodule \
1313
// RUN: -emit-module-interface-path %t/Lib.swiftinterface
1414

15+
/// Build clients, with and without safety.
1516
// RUN: %target-swift-frontend -typecheck %t/Client.swift -I %t \
16-
// RUN: -verify -Xllvm -debug-only=Serialization 2>&1 \
17+
// RUN: -verify -Xllvm -debug-only=Serialization \
18+
// RUN: -disable-deserialization-safety 2>&1 \
1719
// RUN: | %FileCheck --check-prefixes=NEEDED,UNSAFE %s
1820

19-
// RUN: rm %t/Lib.swiftmodule
21+
// RUN: %target-swift-frontend -typecheck %t/Client.swift -I %t \
22+
// RUN: -verify -Xllvm -debug-only=Serialization \
23+
// RUN: -enable-deserialization-safety 2>&1 \
24+
// RUN: | %FileCheck --check-prefixes=NEEDED,CLEAN,SAFE %s
2025

26+
/// Build against the swiftinterface.
27+
// RUN: rm %t/Lib.swiftmodule
2128
// RUN: %target-swift-frontend -typecheck %t/Client.swift -I %t \
22-
// RUN: -verify -Xllvm -debug-only=Serialization 2>&1 \
29+
// RUN: -verify -Xllvm -debug-only=Serialization \
30+
// RUN: -disable-deserialization-safety 2>&1 \
2331
// RUN: | %FileCheck --check-prefixes=NEEDED,CLEAN %s
2432

2533
/// Decls part of the API needed by the client.
@@ -37,6 +45,12 @@
3745
// CLEAN-NOT: Deserialized: 'privateFunc()'
3846
// CLEAN-NOT: Deserialized: 'fileprivateFunc()'
3947

48+
/// Decls skips by the deserialization safety logic.
49+
// SAFE: Skipping unsafe deserialization: 'internalFunc()'
50+
// SAFE: Skipping unsafe deserialization: 'privateFunc()'
51+
// SAFE: Skipping unsafe deserialization: 'fileprivateFunc()'
52+
// SAFE: Skipping unsafe deserialization: 'refToIOI()'
53+
4054
//--- HiddenLib.swift
4155

4256
public struct HiddenStruct {
@@ -47,7 +61,7 @@ public struct HiddenStruct {
4761

4862
@_implementationOnly import HiddenLib
4963

50-
public class PublicStruct {
64+
public struct PublicStruct {
5165
public init() {}
5266

5367
public func publicFunc() {}

0 commit comments

Comments
 (0)