Skip to content

Commit fdb1926

Browse files
authored
[SR-5289] Teach Mirror how to handle unowned/unmanaged references (#28823)
SR-5289: Teach Mirror how to inspect weak, unowned, and unmanaged refs Correctly reflect weak, unowned, and unmanaged references to both Swift and Obj-C types (including existential references to such types) that occur in both Swift class objects and in Swift structs. This includes the specific reported case (unowned reference to an Obj-C object) and several related ones. Related changes in this PR: * Tweak internal bitmap used for tracking ownership modifiers to reject unsupported combinations. * Move FieldType into ReflectionMirror.mm FieldType is really just an internal implementation detail of this one source file, so it does not belong in an ABI header. * Use TypeReferenceOwnership directly to track field ownership This avoids bitwise copying of properties and localizes some of the knowledge about reference ownership * Generate a top-level "copyFieldContents" from ReferenceStorage.def Adding new ownership types to ReferenceStorage.def will now automatically produce calls to `copy*FieldContents` - failure to provide a suitable implementation will fail the build. * Add `deallocateBoxForExistentialIn` to match `allocateBoxForExistentialIn` Caveat: The unit tests are not as strict as I'd like. Attempting to make them so ran afoul of otherwise-unrelated bugs in dynamic casting.
1 parent d595213 commit fdb1926

File tree

7 files changed

+343
-142
lines changed

7 files changed

+343
-142
lines changed

include/swift/ABI/Metadata.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ using TargetRelativeIndirectablePointer
160160

161161
struct HeapObject;
162162
class WeakReference;
163+
struct UnownedReference;
163164

164165
template <typename Runtime> struct TargetMetadata;
165166
using Metadata = TargetMetadata<InProcess>;
@@ -645,6 +646,9 @@ struct TargetMetadata {
645646
// NOTE: This *is* a box for copy-on-write existentials.
646647
OpaqueValue *allocateBoxForExistentialIn(ValueBuffer *Buffer) const;
647648

649+
// Deallocate an out-of-line buffer box if one is present.
650+
void deallocateBoxForExistentialIn(ValueBuffer *Buffer) const;
651+
648652
/// Get the nominal type descriptor if this metadata describes a nominal type,
649653
/// or return null if it does not.
650654
ConstTargetMetadataPointer<Runtime, TargetTypeContextDescriptor>

include/swift/ABI/MetadataValues.h

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -913,55 +913,6 @@ class TargetTupleTypeFlags {
913913
};
914914
using TupleTypeFlags = TargetTupleTypeFlags<size_t>;
915915

916-
/// Field types and flags as represented in a nominal type's field/case type
917-
/// vector.
918-
class FieldType {
919-
typedef uintptr_t int_type;
920-
// Type metadata is always at least pointer-aligned, so we get at least two
921-
// low bits to stash flags. We could use three low bits on 64-bit, and maybe
922-
// some high bits as well.
923-
enum : int_type {
924-
Indirect = 1,
925-
Weak = 2,
926-
927-
TypeMask = ((uintptr_t)-1) & ~(alignof(void*) - 1),
928-
};
929-
int_type Data;
930-
931-
constexpr FieldType(int_type Data) : Data(Data) {}
932-
public:
933-
constexpr FieldType() : Data(0) {}
934-
FieldType withType(const Metadata *T) const {
935-
return FieldType((Data & ~TypeMask) | (uintptr_t)T);
936-
}
937-
938-
constexpr FieldType withIndirect(bool indirect) const {
939-
return FieldType((Data & ~Indirect)
940-
| (indirect ? Indirect : 0));
941-
}
942-
943-
constexpr FieldType withWeak(bool weak) const {
944-
return FieldType((Data & ~Weak)
945-
| (weak ? Weak : 0));
946-
}
947-
948-
bool isIndirect() const {
949-
return bool(Data & Indirect);
950-
}
951-
952-
bool isWeak() const {
953-
return bool(Data & Weak);
954-
}
955-
956-
const Metadata *getType() const {
957-
return (const Metadata *)(Data & TypeMask);
958-
}
959-
960-
int_type getIntValue() const {
961-
return Data;
962-
}
963-
};
964-
965916
/// Flags for exclusivity-checking operations.
966917
enum class ExclusivityFlags : uintptr_t {
967918
Read = 0x0,

include/swift/Runtime/ExistentialContainer.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ struct ClassExistentialContainerImpl {
9595
using ClassExistentialContainer = ClassExistentialContainerImpl<void *>;
9696
using WeakClassExistentialContainer =
9797
ClassExistentialContainerImpl<WeakReference>;
98+
using UnownedClassExistentialContainer =
99+
ClassExistentialContainerImpl<UnownedReference>;
98100

99101
} // end swift namespace
100102

stdlib/public/runtime/Metadata.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3862,6 +3862,13 @@ template <> OpaqueValue *Metadata::allocateBoxForExistentialIn(ValueBuffer *buff
38623862
return refAndValueAddr.buffer;
38633863
}
38643864

3865+
template <> void Metadata::deallocateBoxForExistentialIn(ValueBuffer *buffer) const {
3866+
auto *vwt = getValueWitnesses();
3867+
if (vwt->isValueInline())
3868+
return;
3869+
swift_deallocBox(reinterpret_cast<HeapObject *>(buffer->PrivateData[0]));
3870+
}
3871+
38653872
template <> OpaqueValue *Metadata::allocateBufferIn(ValueBuffer *buffer) const {
38663873
auto *vwt = getValueWitnesses();
38673874
if (vwt->isValueInline())

stdlib/public/runtime/Private.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,10 @@ class TypeReferenceOwnership {
5050

5151
#define REF_STORAGE(Name, ...) \
5252
void set##Name() { Data |= Name; } \
53-
bool is##Name() const { return Data & Name; }
53+
bool is##Name() const { return Data == Name; }
5454
#include "swift/AST/ReferenceStorage.def"
55+
56+
bool isStrong() const { return Data == 0; }
5557
};
5658

5759
/// Type information consists of metadata and its ownership info,
@@ -76,9 +78,11 @@ class TypeInfo {
7678
const Metadata *getMetadata() const { return Response.Value; }
7779
MetadataResponse getResponse() const { return Response; }
7880

79-
bool isWeak() const { return ReferenceOwnership.isWeak(); }
80-
bool isUnowned() const { return ReferenceOwnership.isUnowned(); }
81-
bool isUnmanaged() const { return ReferenceOwnership.isUnmanaged(); }
81+
#define REF_STORAGE(Name, ...) \
82+
bool is##Name() const { return ReferenceOwnership.is##Name(); }
83+
#include "swift/AST/ReferenceStorage.def"
84+
85+
bool isStrong() const { return ReferenceOwnership.isStrong(); }
8286

8387
TypeReferenceOwnership getReferenceOwnership() const {
8488
return ReferenceOwnership;

stdlib/public/runtime/ReflectionMirror.mm

Lines changed: 87 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,29 @@ - (id)debugQuickLookObject;
8989

9090
namespace {
9191

92+
class FieldType {
93+
const Metadata *type;
94+
bool indirect;
95+
TypeReferenceOwnership referenceOwnership;
96+
public:
97+
98+
constexpr FieldType() : type(nullptr), indirect(false), referenceOwnership() { }
99+
constexpr FieldType(const Metadata *T) : type(T), indirect(false), referenceOwnership() { }
100+
101+
static constexpr FieldType untypedEnumCase(bool indirect) {
102+
FieldType type{};
103+
type.indirect = indirect;
104+
return type;
105+
}
106+
const Metadata *getType() const { return type; }
107+
const TypeReferenceOwnership getReferenceOwnership() const { return referenceOwnership; }
108+
bool isIndirect() const { return indirect; }
109+
void setIndirect(bool value) { indirect = value; }
110+
void setReferenceOwnership(TypeReferenceOwnership newOwnership) {
111+
referenceOwnership = newOwnership;
112+
}
113+
};
114+
92115
/// The layout of Any.
93116
using Any = OpaqueExistentialContainer;
94117

@@ -123,58 +146,63 @@ - (id)debugQuickLookObject;
123146
return std::make_tuple(T, Value);
124147
}
125148

126-
static bool loadSpecialReferenceStorage(OpaqueValue *fieldData,
127-
const FieldType fieldType,
128-
Any *outValue) {
129-
// isWeak() implies a reference type via Sema.
130-
if (!fieldType.isWeak())
131-
return false;
132-
133-
auto type = fieldType.getType();
149+
static void copyWeakFieldContents(OpaqueValue *destContainer, const Metadata *type, OpaqueValue *fieldData) {
134150
assert(type->getKind() == MetadataKind::Optional);
151+
auto *srcContainer = reinterpret_cast<WeakClassExistentialContainer*>(fieldData);
152+
auto *destClassContainer = reinterpret_cast<ClassExistentialContainer*>(destContainer);
153+
destClassContainer->Value = swift_unknownObjectWeakLoadStrong(&srcContainer->Value);
154+
auto witnessTablesSize = type->vw_size() - sizeof(WeakClassExistentialContainer);
155+
memcpy(destClassContainer->getWitnessTables(), srcContainer->getWitnessTables(), witnessTablesSize);
156+
}
135157

136-
auto *weakField = reinterpret_cast<WeakReference *>(fieldData);
137-
auto *strongValue = swift_unknownObjectWeakLoadStrong(weakField);
138-
139-
// Now that we have a strong reference, we need to create a temporary buffer
140-
// from which to copy the whole value, which might be a native class-bound
141-
// existential, which means we also need to copy n witness tables, for
142-
// however many protocols are in the protocol composition. For example, if we
143-
// are copying a:
144-
// weak var myWeakProperty : (Protocol1 & Protocol2)?
145-
// then we need to copy three values:
146-
// - the instance
147-
// - the witness table for Protocol1
148-
// - the witness table for Protocol2
149-
150-
auto *weakContainer =
151-
reinterpret_cast<WeakClassExistentialContainer *>(fieldData);
152-
153-
// Create a temporary existential where we can put the strong reference.
154-
// The allocateBuffer value witness requires a ValueBuffer to own the
155-
// allocated storage.
156-
ValueBuffer temporaryBuffer;
157-
158-
auto *temporaryValue = reinterpret_cast<ClassExistentialContainer *>(
159-
type->allocateBufferIn(&temporaryBuffer));
160-
161-
// Now copy the entire value out of the parent, which will include the
162-
// witness tables.
163-
temporaryValue->Value = strongValue;
164-
auto valueWitnessesSize = type->getValueWitnesses()->getSize() -
165-
sizeof(WeakClassExistentialContainer);
166-
memcpy(temporaryValue->getWitnessTables(), weakContainer->getWitnessTables(),
167-
valueWitnessesSize);
168-
169-
outValue->Type = type;
170-
auto *opaqueValueAddr = type->allocateBoxForExistentialIn(&outValue->Buffer);
171-
type->vw_initializeWithCopy(opaqueValueAddr,
172-
reinterpret_cast<OpaqueValue *>(temporaryValue));
173-
174-
type->deallocateBufferIn(&temporaryBuffer);
175-
swift_unknownObjectRelease(strongValue);
176-
177-
return true;
158+
static void copyUnownedFieldContents(OpaqueValue *destContainer, const Metadata *type, OpaqueValue *fieldData) {
159+
auto *srcContainer = reinterpret_cast<UnownedClassExistentialContainer*>(fieldData);
160+
auto *destClassContainer = reinterpret_cast<ClassExistentialContainer*>(destContainer);
161+
destClassContainer->Value = swift_unknownObjectUnownedLoadStrong(&srcContainer->Value);
162+
auto witnessTablesSize = type->vw_size() - sizeof(UnownedClassExistentialContainer);
163+
memcpy(destClassContainer->getWitnessTables(), srcContainer->getWitnessTables(), witnessTablesSize);
164+
}
165+
166+
static void copyUnmanagedFieldContents(OpaqueValue *destContainer, const Metadata *type, OpaqueValue *fieldData) {
167+
// Also known as "unowned(unsafe)".
168+
// This is simpler than the unowned/weak cases because unmanaged
169+
// references are fundamentally the same as strong ones, so we
170+
// can use the regular strong reference support that already
171+
// knows how to handle existentials and Obj-C references.
172+
type->vw_initializeWithCopy(destContainer, fieldData);
173+
}
174+
175+
static AnyReturn copyFieldContents(OpaqueValue *fieldData,
176+
const FieldType fieldType) {
177+
Any outValue;
178+
auto *type = fieldType.getType();
179+
outValue.Type = type;
180+
auto ownership = fieldType.getReferenceOwnership();
181+
auto *destContainer = type->allocateBoxForExistentialIn(&outValue.Buffer);
182+
183+
if (ownership.isStrong()) {
184+
type->vw_initializeWithCopy(destContainer, fieldData);
185+
}
186+
187+
// Generate a conditional clause for every known ownership type.
188+
// If this causes errors, it's because someone added a new ownership type
189+
// to ReferenceStorage.def and missed some related updates.
190+
#define REF_STORAGE(Name, ...) \
191+
else if (ownership.is##Name()) { \
192+
copy##Name##FieldContents(destContainer, type, fieldData); \
193+
}
194+
#include "swift/AST/ReferenceStorage.def"
195+
196+
else {
197+
// The field was declared with a reference type we don't understand.
198+
warning(0, "Value with unrecognized reference type is reflected as ()");
199+
// Clean up the buffer allocated above
200+
type->deallocateBoxForExistentialIn(&outValue.Buffer);
201+
// Return an existential containing Void
202+
outValue.Type = &METADATA_SYM(EMPTY_TUPLE_MANGLING);
203+
}
204+
205+
return AnyReturn(outValue);
178206
}
179207

180208

@@ -310,11 +338,7 @@ static bool _shouldReportMissingReflectionMetadataWarnings() {
310338
"type '%*s' that claims to be reflectable. Its fields will show up as "
311339
"'unknown' in Mirrors\n",
312340
(int)typeName.length, typeName.data);
313-
return {"unknown",
314-
FieldType()
315-
.withType(&METADATA_SYM(EMPTY_TUPLE_MANGLING))
316-
.withIndirect(false)
317-
.withWeak(false)};
341+
return {"unknown", FieldType(&METADATA_SYM(EMPTY_TUPLE_MANGLING))};
318342
};
319343

320344
auto *baseDesc = base->getTypeContextDescriptor();
@@ -325,14 +349,13 @@ static bool _shouldReportMissingReflectionMetadataWarnings() {
325349
if (!fields)
326350
return failedToFindMetadata();
327351

328-
const FieldDescriptor &descriptor = *fields;
329-
auto &field = descriptor.getFields()[index];
352+
auto &field = fields->getFields()[index];
330353
// Bounds are always valid as the offset is constant.
331354
auto name = field.getFieldName();
332355

333356
// Enum cases don't always have types.
334357
if (!field.hasMangledTypeName())
335-
return {name, FieldType().withIndirect(field.isIndirectCase())};
358+
return {name, FieldType::untypedEnumCase(field.isIndirectCase())};
336359

337360
auto typeName = field.getMangledTypeName();
338361

@@ -360,10 +383,10 @@ static bool _shouldReportMissingReflectionMetadataWarnings() {
360383
(int)typeName.size(), typeName.data());
361384
}
362385

363-
return {name, FieldType()
364-
.withType(typeInfo.getMetadata())
365-
.withIndirect(field.isIndirectCase())
366-
.withWeak(typeInfo.isWeak())};
386+
auto fieldType = FieldType(typeInfo.getMetadata());
387+
fieldType.setIndirect(field.isIndirectCase());
388+
fieldType.setReferenceOwnership(typeInfo.getReferenceOwnership());
389+
return {name, fieldType};
367390
}
368391

369392
// Implementation for structs.
@@ -397,7 +420,6 @@ AnyReturn subscript(intptr_t i, const char **outName,
397420
// Load the offset from its respective vector.
398421
auto fieldOffset = Struct->getFieldOffsets()[i];
399422

400-
Any result;
401423
StringRef name;
402424
FieldType fieldInfo;
403425
std::tie(name, fieldInfo) = getFieldAt(type, i);
@@ -409,15 +431,7 @@ AnyReturn subscript(intptr_t i, const char **outName,
409431
auto *bytes = reinterpret_cast<char*>(value);
410432
auto *fieldData = reinterpret_cast<OpaqueValue *>(bytes + fieldOffset);
411433

412-
bool didLoad = loadSpecialReferenceStorage(fieldData, fieldInfo, &result);
413-
if (!didLoad) {
414-
result.Type = fieldInfo.getType();
415-
auto *opaqueValueAddr = result.Type->allocateBoxForExistentialIn(&result.Buffer);
416-
result.Type->vw_initializeWithCopy(opaqueValueAddr,
417-
const_cast<OpaqueValue *>(fieldData));
418-
}
419-
420-
return AnyReturn(result);
434+
return copyFieldContents(fieldData, fieldInfo);
421435
}
422436
};
423437

@@ -559,7 +573,6 @@ AnyReturn subscript(intptr_t i, const char **outName,
559573
#endif
560574
}
561575

562-
Any result;
563576
StringRef name;
564577
FieldType fieldInfo;
565578
std::tie(name, fieldInfo) = getFieldAt(type, i);
@@ -571,15 +584,7 @@ AnyReturn subscript(intptr_t i, const char **outName,
571584
*outName = name.data();
572585
*outFreeFunc = nullptr;
573586

574-
bool didLoad = loadSpecialReferenceStorage(fieldData, fieldInfo, &result);
575-
if (!didLoad) {
576-
result.Type = fieldInfo.getType();
577-
auto *opaqueValueAddr = result.Type->allocateBoxForExistentialIn(&result.Buffer);
578-
result.Type->vw_initializeWithCopy(opaqueValueAddr,
579-
const_cast<OpaqueValue *>(fieldData));
580-
}
581-
582-
return AnyReturn(result);
587+
return copyFieldContents(fieldData, fieldInfo);
583588
}
584589

585590
#if SWIFT_OBJC_INTEROP

0 commit comments

Comments
 (0)