Skip to content

Commit 6bf6782

Browse files
committed
NCGenerics: limit field metadata heuristic
We have had a heuristic that lets you reflect fields of types that are conditionally copyable, despite the reflection infrastructure always copying the fields. This is really to allow types like Optional in the stdlib continue to be reflected, despite having been upgraded to support noncopyable types, because there are many uses of that type with a Copyable payload. As a consequence, we were also allowing reflection of user-defined conditionally copyable types, when that's unsafe for no real benefit. So, this patch limits the damage to only stdlib-defined types, until Mirror and friends are updated.
1 parent 8dbe6f9 commit 6bf6782

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)