Skip to content

Commit 4d6d765

Browse files
committed
Don't let reflection handle noncopyable types yet.
We don't have any language or runtime support for noncopyable types as generic or dynamic types yet, and existing reflection code almost certainly assumes it can copy the values it's working with, and will trap or corrupt state if it does so with noncopyable types. But a class can have noncopyable fields while the type itself is copyable, and existing code assumes that it can use `Mirror` or other reflection mechanisms to safely traverse the contents of an arbitrary class. Allow this sort of code to continue working, while still preparing for forward compatibility with future runtimes that do support noncopyable generics, by emitting the type references for fields using a function that probes the address of a new symbol in the Swift runtime. The symbol will either be missing or defined with an absolute address of zero in current or previous runtime versions, but can be changed to a non-null address in the future.
1 parent df3f332 commit 4d6d765

File tree

6 files changed

+131
-4
lines changed

6 files changed

+131
-4
lines changed

lib/IRGen/GenReflection.cpp

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,54 @@ getTypeRefByFunction(IRGenModule &IGM,
264264
? genericEnv->mapTypeIntoContext(t)->getCanonicalType()
265265
: t;
266266

267+
// If a type is noncopyable, lie about the resolved type unless the
268+
// runtime is sufficiently aware of noncopyable types.
269+
if (substT->isPureMoveOnly()) {
270+
// Darwin-based platforms have ABI stability, and we want binaries
271+
// that use noncopyable types nongenerically today to be forward
272+
// compatible with a future OS runtime that supports noncopyable
273+
// generics. On other platforms, a new Swift compiler and runtime
274+
// require recompilation anyway, so this dance is unnecessary, and
275+
// for now, we can unconditionally lie.
276+
bool useForwardCompatibility =
277+
IGM.Context.LangOpts.Target.isOSDarwin();
278+
279+
llvm::Instruction *br = nullptr;
280+
llvm::BasicBlock *supportedBB = nullptr;
281+
if (useForwardCompatibility) {
282+
auto runtimeSupportsNoncopyableTypesSymbol
283+
= IGM.Module.getOrInsertGlobal("swift_runtimeSupportsNoncopyableTypes",
284+
IGM.Int8Ty);
285+
cast<llvm::GlobalVariable>(runtimeSupportsNoncopyableTypesSymbol)
286+
->setLinkage(llvm::GlobalValue::ExternalWeakLinkage);
287+
288+
auto runtimeSupportsNoncopyableTypes
289+
= IGF.Builder.CreateIsNotNull(runtimeSupportsNoncopyableTypesSymbol,
290+
"supports.noncopyable");
291+
supportedBB = IGF.createBasicBlock("does.support.noncopyable");
292+
auto unsupportedBB = IGF.createBasicBlock("does.not.support.noncopyable");
293+
br = IGF.Builder.CreateCondBr(runtimeSupportsNoncopyableTypes,
294+
supportedBB,
295+
unsupportedBB);
296+
297+
IGF.Builder.emitBlock(unsupportedBB);
298+
}
299+
300+
// If the runtime does not yet support noncopyable types, lie that the
301+
// field is an empty tuple, so the runtime doesn't try to do anything
302+
// with the actual value.
303+
auto phonyRet = IGF.emitTypeMetadataRef(IGM.Context.TheEmptyTupleType);
304+
IGF.Builder.CreateRet(phonyRet);
305+
306+
if (!useForwardCompatibility) {
307+
goto done_building_function;
308+
}
309+
310+
// Emit the type metadata normally otherwise.
311+
IGF.Builder.SetInsertPoint(br);
312+
IGF.Builder.emitBlock(supportedBB);
313+
}
314+
267315
bindFromGenericRequirementsBuffer(
268316
IGF, requirements,
269317
Address(bindingsBufPtr, IGM.Int8Ty, IGM.getPointerAlignment()),
@@ -276,6 +324,7 @@ getTypeRefByFunction(IRGenModule &IGM,
276324
auto ret = IGF.emitTypeMetadataRef(substT);
277325
IGF.Builder.CreateRet(ret);
278326
}
327+
done_building_function:
279328
// Form the mangled name with its relative reference.
280329
auto S = B.beginStruct();
281330
S.setPacked(true);
@@ -315,6 +364,21 @@ getTypeRefImpl(IRGenModule &IGM,
315364
case MangledTypeRefRole::FlatUnique:
316365
useFlatUnique = true;
317366
break;
367+
368+
case MangledTypeRefRole::FieldMetadata:
369+
// We want to keep fields of noncopyable type from being exposed to
370+
// in-process runtime reflection libraries in older Swift runtimes, since
371+
// they more than likely assume they can copy field values, and the language
372+
// support for noncopyable types as dynamic or generic types isn't yet
373+
// implemented as of the writing of this comment. If the type is
374+
// noncopyable, use a function to emit the type ref which will look for a
375+
// signal from future runtimes whether they support noncopyable types before
376+
// exposing their metadata to them.
377+
if (type->isPureMoveOnly()) {
378+
IGM.IRGen.noteUseOfTypeMetadata(type);
379+
return getTypeRefByFunction(IGM, sig, type);
380+
}
381+
LLVM_FALLTHROUGH;
318382

319383
case MangledTypeRefRole::DefaultAssociatedTypeWitness:
320384
case MangledTypeRefRole::Metadata:
@@ -724,14 +788,14 @@ class FieldTypeMetadataBuilder : public ReflectionMetadataBuilder {
724788
if (type->isForeignReferenceType()) {
725789
auto opaqueType = type->getASTContext().getOpaquePointerType();
726790
// The standard library's Mirror demangles metadata from field
727-
// descriptors, so use MangledTypeRefRole::Metadata to ensure
791+
// descriptors, so use MangledTypeRefRole::FieldMetadata to ensure
728792
// runtime metadata is available.
729-
addTypeRef(opaqueType, genericSig, MangledTypeRefRole::Metadata);
793+
addTypeRef(opaqueType, genericSig, MangledTypeRefRole::FieldMetadata);
730794
} else {
731795
// The standard library's Mirror demangles metadata from field
732-
// descriptors, so use MangledTypeRefRole::Metadata to ensure
796+
// descriptors, so use MangledTypeRefRole::FieldMetadata to ensure
733797
// runtime metadata is available.
734-
addTypeRef(type, genericSig, MangledTypeRefRole::Metadata);
798+
addTypeRef(type, genericSig, MangledTypeRefRole::FieldMetadata);
735799
}
736800
}
737801

lib/IRGen/IRGenMangler.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ mangleSymbolNameForSymbolicMangling(const SymbolicMangling &mangling,
300300
prefix = "default assoc type ";
301301
break;
302302

303+
case MangledTypeRefRole::FieldMetadata:
303304
case MangledTypeRefRole::Metadata:
304305
case MangledTypeRefRole::Reflection:
305306
prefix = "symbolic ";

lib/IRGen/IRGenModule.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,10 @@ class TypeEntityReference {
575575

576576
/// Describes the role of a mangled type reference string.
577577
enum class MangledTypeRefRole {
578+
/// The mangled type reference is used for field metadata, which is used
579+
/// by both in-process and out-of-process reflection, so still requires
580+
/// referenced types' metadata to be fully emitted.
581+
FieldMetadata,
578582
/// The mangled type reference is used for normal metadata.
579583
Metadata,
580584
/// The mangled type reference is used for reflection metadata.

lib/IRGen/MetadataRequest.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@ llvm::Constant *IRGenModule::getAddrOfStringForTypeRef(
377377

378378
case MangledTypeRefRole::Metadata:
379379
case MangledTypeRefRole::Reflection:
380+
case MangledTypeRefRole::FieldMetadata:
380381
break;
381382
}
382383

stdlib/public/runtime/Metadata.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,19 @@ extern "C" void _objc_setClassCopyFixupHandler(void (* _Nonnull newFixupHandler)
7575
using namespace swift;
7676
using namespace metadataimpl;
7777

78+
#if defined(__APPLE__)
79+
// Binaries using noncopyable types check the address of the symbol
80+
// `swift_runtimeSupportsNoncopyableTypes` before exposing any noncopyable
81+
// type metadata through in-process reflection, to prevent existing code
82+
// that expects all types to be copyable from crashing or causing bad behavior
83+
// by copying noncopyable types. The runtime does not yet support noncopyable
84+
// types, so we explicitly define this symbol to be zero for now. Binaries
85+
// weak-import this symbol so they will resolve it to a zero address on older
86+
// runtimes as well.
87+
__asm__(" .globl _swift_runtimeSupportsNoncopyableTypes\n");
88+
__asm__(".set _swift_runtimeSupportsNoncopyableTypes, 0\n");
89+
#endif
90+
7891
// GenericParamDescriptor is a single byte, so while it's difficult to
7992
// imagine needing even a quarter this many generic params, there's very
8093
// little harm in doing it.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// RUN: %target-run-simple-swift(-Xfrontend -enable-experimental-move-only)
2+
// REQUIRES: executable_test
3+
4+
// Verify that iterating through the fields of an object whose class has
5+
// a move-only field does not trap from trying to reflect and copy those
6+
// move-only fields.
7+
8+
@_moveOnly
9+
public struct MO {
10+
var x: Int8 = 0
11+
var y: Int8 = 0
12+
var z: Int8 = 0
13+
14+
deinit { print("destroyed MO") }
15+
}
16+
17+
public class MOHaver {
18+
var s: String = "hello"
19+
var mo: MO = MO()
20+
var b: Int8 = 42
21+
var c: Any.Type = MOHaver.self
22+
}
23+
24+
// CHECK-LABEL: doing nongeneric
25+
print("doing nongeneric")
26+
do {
27+
let k = MOHaver()
28+
29+
let mirror = Mirror(reflecting: k)
30+
// CHECK-NEXT: s: hello
31+
// Whether this actually prints the value of `k.mo` or not is irrelevant
32+
// to the test; we care that attempting to reflect the field does not trap
33+
// copying a noncopyable field.
34+
// CHECK-NEXT: mo:
35+
// CHECK-NEXT: b: 42
36+
// CHECK-NEXT: c: {{.*}}.MOHaver
37+
for c in mirror.children {
38+
print("\(c.label!): \(c.value)")
39+
}
40+
// CHECK-NEXT: destroyed MO
41+
}
42+
// CHECK-NEXT: done with nongeneric
43+
print("done with nongeneric")
44+

0 commit comments

Comments
 (0)