Skip to content

Commit d755e90

Browse files
committed
NCGenerics: limit field metadata heuristic
We have had a heuristic that lets you reflect fields of types, where the field's type is conditionally Copyable, despite the reflection infrastructure always copying a field. That heuristic was added to allow ubiquitous types like Optional in the stdlib to continue to be reflected, even if the Optional at runtime really isn't Copyable. As a consequence, we were also allowing reflection of fields containing user-defined conditionally copyable types, when that's unsafe for no real benefit, yielding runtime crashes! I think in that case it's better to fall-back on the non-crashy case of reflection seeing it as the EmptyTupleType, which isn't inhabited, so it won't try to copy the field and instead basically skip-over it until a future runtime supports the reflection safely. So, this patch limits the dangerous reflection to only stdlib-defined types, until Mirror and friends are updated.
1 parent 76bb266 commit d755e90

File tree

3 files changed

+97
-13
lines changed

3 files changed

+97
-13
lines changed

lib/IRGen/GenReflection.cpp

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -437,21 +437,31 @@ getTypeRefImpl(IRGenModule &IGM,
437437

438438
bool isAlwaysNoncopyable = false;
439439
if (contextualTy->isNoncopyable()) {
440+
isAlwaysNoncopyable = true;
441+
440442
// If the contextual type has any archetypes in it, it's plausible that
441-
// we could end up with a copyable type in some instances. Look for those.
443+
// we could end up with a copyable type in some instances. Look for those
444+
// so we can permit unsafe reflection of the field, by assuming it could
445+
// be Copyable.
442446
if (contextualTy->hasArchetype()) {
443447
// If this is a nominal type, check whether it can ever be copyable.
444448
if (auto nominal = contextualTy->getAnyNominal()) {
445-
if (!nominal->canBeCopyable())
446-
isAlwaysNoncopyable = true;
449+
// If it's a nominal that can ever be Copyable _and_ it's defined in
450+
// the stdlib, assume that we could end up with a Copyable type.
451+
if (nominal->canBeCopyable()
452+
&& nominal->getModuleContext()->isStdlibModule())
453+
isAlwaysNoncopyable = false;
447454
} else {
448-
// Assume that we could end up with a copyable type somehow.
455+
// Assume that we could end up with a Copyable type somehow.
456+
// This allows you to reflect a 'T: ~Copyable' stored in a type.
457+
isAlwaysNoncopyable = false;
449458
}
450-
} else {
451-
isAlwaysNoncopyable = true;
452459
}
453460
}
454461

462+
// The getTypeRefByFunction strategy will emit a forward-compatible runtime
463+
// check to see if the runtime can safely reflect such fields. Otherwise,
464+
// the field will be artificially hidden to reflectors.
455465
if (isAlwaysNoncopyable) {
456466
IGM.IRGen.noteUseOfTypeMetadata(type);
457467
return getTypeRefByFunction(IGM, sig, type);

test/IRGen/noncopyable_field_descriptors.swift

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ public struct NonCopyable: ~Copyable { }
4141
// 'MF' constant as a separator that precedes each field descriptor.
4242

4343
// NEW: @"$s4test8CC_TestsCMF" =
44-
// NEW-SAME: @"symbolic _____yxG 4test21ConditionallyCopyableOAARi_zrlE"
44+
// NEW-SAME: @"get_type_metadata Ri_zr0_l4test21ConditionallyCopyableOyxG.3"
4545
// NEW-SAME: @"symbolic _____yq_G 4test21ConditionallyCopyableOAARi_zrlE"
46-
// NEW-SAME: @"get_type_metadata Ri_zr0_l4test21ConditionallyCopyableOyAA03NonC0VG.3"
46+
// NEW-SAME: @"get_type_metadata Ri_zr0_l4test21ConditionallyCopyableOyAA03NonC0VG.4"
4747
// NEW-SAME: @"symbolic _____ySSG 4test21ConditionallyCopyableOAARi_zrlE"
4848

4949
// OLD: @"$s4test8CC_TestsCMF" =
@@ -64,10 +64,10 @@ public class CC_Tests<NCG: ~Copyable, T> {
6464
/// fields until a future runtime says they're safe to reflect.
6565

6666
// NEW: @"$s4test8NC_TestsCMF" =
67-
// NEW-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOyxG.4"
68-
// NEW-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOyq_G.5"
69-
// NEW-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOyAA03NonC0VG.6"
70-
// NEW-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOySSG.7"
67+
// NEW-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOyxG.5"
68+
// NEW-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOyq_G.6"
69+
// NEW-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOyAA03NonC0VG.7"
70+
// NEW-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOySSG.8"
7171

7272
// OLD: @"$s4test8NC_TestsCMF" =
7373
// OLD-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOyxG.7"
@@ -85,7 +85,7 @@ public class NC_Tests<NCG: ~Copyable, T> {
8585
// NEW: @"$s4test17StdlibTypes_TestsCMF" =
8686
// NEW-SAME: @"symbolic xSg"
8787
// NEW-SAME: @"symbolic q_Sg"
88-
// NEW-SAME: @"get_type_metadata Ri_zr0_l4test11NonCopyableVSg.8"
88+
// NEW-SAME: @"get_type_metadata Ri_zr0_l4test11NonCopyableVSg.9"
8989
// NEW-SAME: @"symbolic SSSg"
9090
// NEW-SAME: @"symbolic SPyxG"
9191
// NEW-SAME: @"symbolic SPyq_G"
@@ -112,3 +112,29 @@ public class StdlibTypes_Tests<NCG: ~Copyable, T> {
112112
var upNC: UnsafePointer<NonCopyable> = .init(bitPattern: 64)!
113113
var upC: UnsafePointer<String> = .init(bitPattern: 128)!
114114
}
115+
116+
117+
// NEW: @"$s4test19PlainlyStored_TestsCMF" =
118+
// NEW-SAME: @"symbolic x"
119+
// NEW-SAME: @"symbolic q_"
120+
// NEW-SAME: @"get_type_metadata Ri_zr0_l4test11NonCopyableV.10"
121+
// NEW-SAME: @"symbolic SS"
122+
123+
// OLD: @"$s4test19PlainlyStored_TestsCMF" =
124+
// OLD-SAME: @"symbolic x"
125+
// OLD-SAME: @"symbolic q_"
126+
// OLD-SAME: @"get_type_metadata Ri_zr0_l4test11NonCopyableV.12"
127+
// OLD-SAME: @"symbolic SS"
128+
public class PlainlyStored_Tests<NCG: ~Copyable, T> {
129+
var ncg: NCG
130+
var t: T
131+
var concreteNC: NonCopyable
132+
var str: String
133+
134+
public init(_ ncg: consuming NCG, _ t: T) {
135+
self.ncg = ncg
136+
self.t = t
137+
self.concreteNC = NonCopyable()
138+
self.str = ""
139+
}
140+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-build-swift -D CRASH %s -o %t/crash
3+
// RUN: %target-build-swift %s -o %t/exe
4+
5+
// RUN: %target-run %t/exe | %FileCheck %s
6+
// RUN: %target-run not --crash %t/crash
7+
8+
// REQUIRES: executable_test
9+
10+
enum Maybe<Wrapped: ~Copyable>: ~Copyable {
11+
case some(Wrapped)
12+
case none
13+
}
14+
extension Maybe: Copyable where Wrapped: Copyable {}
15+
16+
struct NC: ~Copyable {
17+
let data: Int
18+
}
19+
20+
class Protected<T: ~Copyable> {
21+
var field: Maybe<T>
22+
init(_ t: consuming T) {
23+
self.field = .some(t)
24+
}
25+
}
26+
27+
class Dangerous<T: ~Copyable> {
28+
var field: Optional<T>
29+
init(_ t: consuming T) {
30+
self.field = .some(t)
31+
}
32+
}
33+
34+
func printFields<T: Copyable>(_ val: T) {
35+
let mirror = Mirror.init(reflecting: val)
36+
mirror.children.forEach { print($0.label ?? "", $0.value) }
37+
}
38+
39+
defer { test() }
40+
func test() {
41+
#if CRASH
42+
printFields(Dangerous(NC(data: 22)))
43+
#else
44+
printFields(Protected("oreo")) // CHECK: field ()
45+
printFields(Protected(NC(data: 11))) // CHECK: field ()
46+
printFields(Dangerous("spots")) // CHECK: field Optional("spots")
47+
#endif
48+
}

0 commit comments

Comments
 (0)