Skip to content

Commit 9b50342

Browse files
committed
Add a runtime-internal function, _enumerateTypes(fromImageAt:conformingTo:_:), that will walk the protocol conformance tables looking for types that conform to a given protocol.
1 parent e67e5c1 commit 9b50342

File tree

6 files changed

+360
-1
lines changed

6 files changed

+360
-1
lines changed

stdlib/public/core/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ set(SWIFTLIB_ESSENTIAL
173173
SwiftNativeNSArray.swift
174174
TemporaryAllocation.swift
175175
ThreadLocalStorage.swift
176+
TypeDiscovery.swift
176177
UIntBuffer.swift
177178
UnavailableStringAPIs.swift
178179
UnicodeData.swift

stdlib/public/core/GroupInfo.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,8 @@
155155
"Mirror.swift",
156156
"Mirrors.swift",
157157
"ReflectionMirror.swift",
158-
"ObjectIdentifier.swift"
158+
"ObjectIdentifier.swift",
159+
"TypeDiscovery.swift"
159160
],
160161
"Math": [
161162
"SetAlgebra.swift",
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
@_silgen_name("swift_enumerateConformingTypesFromImage")
14+
private func _swift_enumerateTypes(
15+
fromImageAt imageAddress: UnsafeRawPointer?,
16+
conformingTo protocolType: Any.Type,
17+
_ body: (_ type: Any.Type, _ stop: inout Bool) throws -> Void
18+
) rethrows
19+
20+
/// Enumerate all types in a given image that conform to the specified Swift
21+
/// protocol.
22+
///
23+
/// - Parameters:
24+
/// - imageAddress: A platform-specific pointer to the image of interest. The
25+
/// image must have been loaded into the current process. For the binary of
26+
/// the calling function, you can pass `#dsohandle`. For all binaries, pass
27+
/// `nil` (the default.)
28+
/// - protocolType: The protocol of interest. This protocol does not need to
29+
/// be declared in the image referenced by `imageAddress`.
30+
/// - body: A closure to invoke once per conforming type.
31+
///
32+
/// - Throws: Whatever is thrown by `body`.
33+
///
34+
/// This function walks all known types in the given image and checks each for
35+
/// conformance to `protocolType`. If the type conforms, it is passed to `body`.
36+
///
37+
/// Only Swift protocol types (for example, `Hashable.self` or
38+
/// `(Hashable & Codable).self`) should be passed to this function. All other
39+
/// types will produce a fatal error at runtime.
40+
///
41+
/// - Bug: Generic types that conform to `protocolType` are not enumerated by
42+
/// this function.
43+
///
44+
/// - Bug: Objective-C protocol conformance lookups are not supported yet.
45+
@available(macOS 9999.0, iOS 9999.0, watchOS 9999.0, tvOS 9999.0, *)
46+
public func _enumerateTypes(
47+
fromImageAt imageAddress: UnsafeRawPointer? = nil,
48+
conformingTo protocolType: Any.Type,
49+
_ body: (_ type: Any.Type, _ stop: inout Bool) throws -> Void
50+
) rethrows {
51+
try _swift_enumerateTypes(
52+
fromImageAt: imageAddress,
53+
conformingTo: protocolType,
54+
body
55+
)
56+
}
57+

stdlib/public/runtime/ProtocolConformance.cpp

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,17 @@
2929
#include "llvm/ADT/DenseMap.h"
3030
#include "../CompatibilityOverride/CompatibilityOverride.h"
3131
#include "ImageInspection.h"
32+
#include "ImageInspectionCommon.h"
3233
#include "Private.h"
34+
#include "TypeDiscovery.h"
3335

3436
#include <vector>
3537

38+
#if defined(__MACH__)
39+
#include <mach-o/dyld.h>
40+
#include <mach-o/getsect.h>
41+
#endif
42+
3643
#if __has_include(<mach-o/dyld_priv.h>)
3744
#include <mach-o/dyld_priv.h>
3845
#define DYLD_EXPECTED_SWIFT_OPTIMIZATIONS_VERSION 1u
@@ -1271,5 +1278,188 @@ const Metadata *swift::findConformingSuperclass(
12711278
return conformingType;
12721279
}
12731280

1281+
/// Walk the conformances in a \c ConformanceSection instance, check if they
1282+
/// specify conformance to all the specified protocols, and call \a body for the
1283+
/// types that match.
1284+
SWIFT_CC(swift)
1285+
static void _enumerateConformingTypesFromSection(
1286+
const ConformanceSection *section,
1287+
size_t count,
1288+
const llvm::ArrayRef<ProtocolDescriptorRef>& protocols,
1289+
TypeEnumerationFunction body,
1290+
void *bodyContext,
1291+
bool *stop,
1292+
SWIFT_CONTEXT void *context,
1293+
SWIFT_ERROR_RESULT SwiftError **error) {
1294+
1295+
for (size_t i = 0; i < count; i++) {
1296+
for (const auto& record : *section) {
1297+
auto protocolDescriptor = record->getProtocol();
1298+
auto metadata = record->getCanonicalTypeMetadata();
1299+
if (!protocolDescriptor || !metadata) {
1300+
// This record does not contain the info we're interested in.
1301+
continue;
1302+
}
1303+
1304+
for (const auto& protocol : protocols) {
1305+
const auto& swiftProtocol = protocol.getSwiftProtocol();
1306+
if (!swift_conformsToProtocol(metadata, swiftProtocol)) {
1307+
// This type does not conform to the specified protocol(s), so skip.
1308+
continue;
1309+
}
1310+
}
1311+
1312+
body(metadata, stop, bodyContext, error);
1313+
if (*stop || *error) {
1314+
return;
1315+
}
1316+
}
1317+
}
1318+
}
1319+
1320+
#if defined(__MACH__)
1321+
/// Get the conformance section of a given Mach-O image.
1322+
///
1323+
/// If the image does not have a conformance section, returns \c llvm::None.
1324+
static llvm::Optional<ConformanceSection>
1325+
_getConformanceSectionFromMachImage(const void *imageAddress) {
1326+
unsigned long sectionSize = 0;
1327+
#ifdef __LP64__
1328+
typedef const mach_header_64 *MachHeaderAddress;
1329+
#else
1330+
typedef const mach_header *MachHeaderAddress;
1331+
#endif
1332+
const auto& sectionData = getsectiondata((MachHeaderAddress)imageAddress,
1333+
SEG_TEXT,
1334+
MachOProtocolConformancesSection,
1335+
&sectionSize);
1336+
if (sectionData) {
1337+
return ConformanceSection(sectionData, sectionSize);
1338+
}
1339+
1340+
return llvm::None;
1341+
}
1342+
#endif
1343+
1344+
SWIFT_CC(swift)
1345+
void swift::swift_enumerateConformingTypesFromImage(
1346+
const void *imageAddress,
1347+
const Metadata *protocolMetadata,
1348+
TypeEnumerationFunction body,
1349+
void *bodyContext,
1350+
SWIFT_CONTEXT void *context,
1351+
SWIFT_ERROR_RESULT SwiftError **error) {
1352+
1353+
// Find the protocol(s) requested by the caller.
1354+
auto existentialType = dyn_cast<ExistentialTypeMetadata>(protocolMetadata);
1355+
if (!existentialType) {
1356+
auto typeName = swift_getTypeName(protocolMetadata, true);
1357+
swift::fatalError(
1358+
0,
1359+
"_enumerateTypes(fromImageAt:conformingTo:_:) can only test for "
1360+
"conformance to a protocol, but %.*s is not a protocol.",
1361+
(int)typeName.length, typeName.data);
1362+
}
1363+
const auto& protocols = existentialType->getProtocols();
1364+
1365+
if (protocols.empty()) {
1366+
swift::fatalError(
1367+
0,
1368+
"Any is not a protocol and _enumerateTypes(fromImageAt:conformingTo:_:) "
1369+
"cannot test for conformance to it.");
1370+
}
1371+
1372+
#if SWIFT_OBJC_INTEROP
1373+
// Check for any Objective-C protocols in the input. We do not currently
1374+
// support searching for Objective-C protocol conformances because they do not
1375+
// produce conformance records in the compiled binary. In the future, if we
1376+
// detect any such protocols, we can use objc_copyClassList() to add to the
1377+
// enumerated set (taking care not to enumerate a class twice if it conforms
1378+
// to an Objective-C protocol *and* a Swift protocol.)
1379+
for (const auto& protocol : protocols) {
1380+
if (protocol.isObjC()) {
1381+
swift::fatalError(
1382+
0,
1383+
"_enumerateTypes(fromImageAt:conformingTo:_:) does not currently "
1384+
"support Objective-C protocols such as %s.",
1385+
protocol.getName());
1386+
}
1387+
}
1388+
#endif
1389+
1390+
#if defined(__WASM__)
1391+
// Swift+WASM does not support dynamic linking and #dsohandle is 0, so
1392+
// ignore the passed image address and iterate over any/all available
1393+
// sections. Presumably only one will be available.
1394+
imageAddress = nullptr;
1395+
#endif
1396+
1397+
// Callback parameters (hoisted out of the loops.)
1398+
bool stop = false;
1399+
*error = nullptr;
1400+
1401+
#if defined(__MACH__)
1402+
// This platform has dyld and we can use it instead of walking the section
1403+
// table. Takes care of the nuances of DYLD shared cache support.
1404+
if (imageAddress) {
1405+
if (auto section = _getConformanceSectionFromMachImage(imageAddress)) {
1406+
_enumerateConformingTypesFromSection(section.getPointer(), 1,
1407+
protocols, body, bodyContext,
1408+
&stop, context, error);
1409+
}
1410+
1411+
} else {
1412+
// Walk all loaded images and consider each one in turn. The calls into dyld
1413+
// aren't thread-safe (another thread could modify the set of loaded images)
1414+
// but not in a "I'm going to crash" sense, just a "missed an image" sense.
1415+
auto count = _dyld_image_count();
1416+
for (uint32_t i = 0; i < count; i++) {
1417+
const auto& imageAddress = _dyld_get_image_header(i);
1418+
if (!imageAddress) {
1419+
continue;
1420+
}
1421+
1422+
if (auto section = _getConformanceSectionFromMachImage(imageAddress)) {
1423+
_enumerateConformingTypesFromSection(section.getPointer(), 1,
1424+
protocols, body, bodyContext,
1425+
&stop, context, error);
1426+
if (stop || *error) {
1427+
return;
1428+
}
1429+
}
1430+
}
1431+
}
1432+
1433+
#else
1434+
// This platform doesn't have getsectiondata(), so walk all sections in the
1435+
// conformances table.
1436+
auto snapshot = Conformances.get().SectionsToScan.snapshot();
1437+
1438+
if (imageAddress) {
1439+
// FIXME: This operation is somewhat costly and could probably be optimized on a per-platform basis.
1440+
for (const auto& section : snapshot) {
1441+
// Check if this section came from the image of interest.
1442+
SymbolInfo info;
1443+
if (!lookupSymbol(section.begin(), &info)) {
1444+
continue;
1445+
}
1446+
1447+
if (info.baseAddress == imageAddress) {
1448+
_enumerateConformingTypesFromSection(&section, 1,
1449+
protocols, body, bodyContext,
1450+
&stop, context, error);
1451+
return;
1452+
}
1453+
}
1454+
1455+
} else {
1456+
// Walk all sections.
1457+
_enumerateConformingTypesFromSection(snapshot.begin(), snapshot.count(),
1458+
protocols, body, bodyContext,
1459+
&stop, context, error);
1460+
}
1461+
#endif
1462+
}
1463+
12741464
#define OVERRIDE_PROTOCOLCONFORMANCE COMPATIBILITY_OVERRIDE
12751465
#include COMPATIBILITY_OVERRIDE_INCLUDE_PATH

stdlib/public/runtime/TypeDiscovery.h

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//===--- TypeDiscovery.h - Dynamic type lookup at runtime--------*- C++ -*-===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
//
13+
// Functions to look up types in the Swift type system at runtime.
14+
//
15+
//===----------------------------------------------------------------------===//
16+
17+
#ifndef SWIFT_RUNTIME_TYPE_DISCOVERY_H
18+
#define SWIFT_RUNTIME_TYPE_DISCOVERY_H
19+
20+
#include "swift/Runtime/Config.h"
21+
#include "swift/Runtime/Error.h"
22+
#include "swift/Runtime/Metadata.h"
23+
24+
namespace swift {
25+
/// The closure type used by \c swift_enumerateConformingTypesFromImage().
26+
typedef void (* TypeEnumerationFunction)(
27+
const Metadata *type,
28+
bool *stop,
29+
SWIFT_CONTEXT void *context,
30+
SWIFT_ERROR_RESULT SwiftError **error) SWIFT_CC(swift);
31+
32+
/// Enumerate a list of all types (as Metadata) in a given image that conform
33+
/// to the specified Swift protocol.
34+
///
35+
/// \param imageAddress A platform-specific pointer to the image of interest.
36+
/// The image must have been loaded into the current process. Pass
37+
/// \c nullptr to search all loaded images.
38+
/// \param protocolMetadata The protocol of interest. This protocol does not
39+
/// need to be declared in the image referenced by \a imageAddress.
40+
/// \param body A closure to invoke once per matching type.
41+
/// \param bodyContext The Swift context pointer for \a body.
42+
/// \param context The Swift context pointer for this function. Ignored.
43+
/// \param error An error pointer passed to \a body. If \a body throws an
44+
/// error, enumeration is stopped and the error is rethrown.
45+
///
46+
/// This function walks all known types in the given image and checks each for
47+
/// conformance to \a protocol. Any types that do conform are passed to
48+
/// \c body for evaluation.
49+
///
50+
/// \bug Generic types with constrained conformance to \a protocol are not
51+
/// supported.
52+
///
53+
/// \bug Objective-C protocol conformance lookups are not supported.
54+
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERNAL
55+
void swift_enumerateConformingTypesFromImage(
56+
const void *imageAddress,
57+
const Metadata *protocolMetadata,
58+
TypeEnumerationFunction body,
59+
void *bodyContext,
60+
SWIFT_CONTEXT void *context,
61+
SWIFT_ERROR_RESULT SwiftError **error);
62+
} // end namespace swift
63+
64+
#endif /* SWIFT_RUNTIME_TYPE_DISCOVERY_H */

test/Runtime/type_discovery.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// RUN: %target-run-simple-swift
2+
// REQUIRES: executable_test
3+
4+
import StdlibUnittest
5+
6+
protocol P { }
7+
struct A: P { }
8+
class B: P { }
9+
enum C: P { }
10+
struct D /* does not conform */ { }
11+
12+
guard #available(macOS 9999.0, iOS 9999.0, watchOS 9999.0, tvOS 9999.0, *) else {
13+
print("Skipping tests in \(#fileID) due to unavailability.")
14+
return
15+
}
16+
17+
// Don't use #dsohandle on Windows because __ImageBase is not linked properly
18+
// when building the test target and we get a link-time error trying to use it.
19+
let address: UnsafeRawPointer?
20+
#if os(Windows)
21+
address = nil
22+
#else
23+
address = #dsohandle
24+
#endif
25+
26+
do {
27+
var typeList = [P.Type]()
28+
29+
_enumerateTypes(fromImageAt: address, conformingTo: P.self) { type, _ in
30+
typeList.append(type as! P.Type)
31+
}
32+
expectTrue(typeList.contains(where: { $0 == A.self }))
33+
expectTrue(typeList.contains(where: { $0 == B.self }))
34+
expectTrue(typeList.contains(where: { $0 == C.self }))
35+
expectFalse(typeList.contains(where: { $0 == D.self }))
36+
}
37+
38+
// Test that `stop` works.
39+
do {
40+
var iterationCount = 0
41+
_enumerateTypes(fromImageAt: address, conformingTo: P.self) { type, _ in
42+
iterationCount += 1
43+
stop = true
44+
}
45+
expectEqual(iterationCount, 1)
46+
}

0 commit comments

Comments
 (0)