Skip to content

Commit 7aa9f5b

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 7aa9f5b

File tree

6 files changed

+439
-1
lines changed

6 files changed

+439
-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(SwiftStdlib 9999, *)
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: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,19 @@
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 <dlfcn.h>
40+
#include <mach-o/dyld.h>
41+
#include <mach-o/getsect.h>
42+
#include <mach-o/loader.h>
43+
#endif
44+
3645
#if __has_include(<mach-o/dyld_priv.h>)
3746
#include <mach-o/dyld_priv.h>
3847
#define DYLD_EXPECTED_SWIFT_OPTIMIZATIONS_VERSION 1u
@@ -1271,5 +1280,265 @@ const Metadata *swift::findConformingSuperclass(
12711280
return conformingType;
12721281
}
12731282

1283+
/// Walk the conformances in a \c ConformanceSection instance, check if they
1284+
/// specify conformance to all the specified protocols, and call \a body for the
1285+
/// types that match.
1286+
SWIFT_CC(swift)
1287+
static void _enumerateConformingTypesFromSections(
1288+
const ConformanceSection *sections,
1289+
size_t count,
1290+
const llvm::ArrayRef<ProtocolDescriptorRef>& protocols,
1291+
TypeEnumerationFunction body,
1292+
void *bodyContext,
1293+
bool *stop,
1294+
SWIFT_CONTEXT void *context,
1295+
SWIFT_ERROR_RESULT SwiftError **error) {
1296+
1297+
for (size_t i = 0; i < count; i++) {
1298+
const auto& section = sections[i];
1299+
for (const auto& record : section) {
1300+
auto protocolDescriptor = record->getProtocol();
1301+
auto metadata = record->getCanonicalTypeMetadata();
1302+
if (!protocolDescriptor || !metadata) {
1303+
// This record does not contain the info we're interested in.
1304+
continue;
1305+
}
1306+
1307+
bool conforms = true; {
1308+
for (const auto& protocol : protocols) {
1309+
const auto& swiftProtocol = protocol.getSwiftProtocol();
1310+
if (!swift_conformsToProtocol(metadata, swiftProtocol)) {
1311+
// This type does not conform to all the specified protocol(s).
1312+
conforms = false;
1313+
break;
1314+
}
1315+
}
1316+
}
1317+
if (!conforms) {
1318+
continue;
1319+
}
1320+
1321+
body(metadata, stop, bodyContext, error);
1322+
if (*stop || *error) {
1323+
return;
1324+
}
1325+
}
1326+
}
1327+
}
1328+
1329+
#if defined(__MACH__)
1330+
/// Get the conformance section of a given Mach-O image.
1331+
///
1332+
/// \param imageAddress The address of a Mach header or of a handle returned
1333+
/// from \c dlopen().
1334+
/// \param maybeDlopenHandle Whether or not \a imageAddress might be a
1335+
/// \c dlopen() handle. If it is, an additional call to dyld is made to get
1336+
/// its corresponding Mach header.
1337+
///
1338+
/// If the image does not have a conformance section, returns \c llvm::None.
1339+
static llvm::Optional<ConformanceSection>
1340+
_getConformanceSectionFromMachImage(const void *imageAddress,
1341+
bool maybeDlopenHandle) {
1342+
#ifdef __LP64__
1343+
typedef const mach_header_64 *MachHeaderAddress;
1344+
#else
1345+
typedef const mach_header *MachHeaderAddress;
1346+
#endif
1347+
auto machHeader = reinterpret_cast<MachHeaderAddress>(imageAddress);
1348+
1349+
if (maybeDlopenHandle) {
1350+
// The input pointer might be a dlopen() handle instead of a Mach header.
1351+
// Try to get the corresponding Mach header.
1352+
auto dyldAddress = dlsym(const_cast<void *>(imageAddress), "__dso_handle");
1353+
if (dyldAddress) {
1354+
machHeader = reinterpret_cast<MachHeaderAddress>(dyldAddress);
1355+
}
1356+
1357+
// If dyld failed to get a Mach header from the image address, it might mean
1358+
// it's already a Mach header or that it could not resolve the Mac header.
1359+
// getsectiondata() does not check that the header passed to it is valid, so
1360+
// perform a basic validation before passing along the pointer we have.
1361+
if (machHeader->magic != MH_MAGIC && machHeader->magic != MH_MAGIC_64) {
1362+
return llvm::None;
1363+
}
1364+
}
1365+
1366+
unsigned long sectionSize = 0;
1367+
const auto& sectionData = getsectiondata(machHeader,
1368+
SEG_TEXT,
1369+
MachOProtocolConformancesSection,
1370+
&sectionSize);
1371+
if (sectionData) {
1372+
return ConformanceSection(sectionData, sectionSize);
1373+
}
1374+
1375+
return llvm::None;
1376+
}
1377+
#endif
1378+
1379+
SWIFT_CC(swift)
1380+
static void _enumerateConformingTypesFromImage(
1381+
const void *imageAddress,
1382+
const llvm::ArrayRef<ProtocolDescriptorRef>& protocols,
1383+
TypeEnumerationFunction body,
1384+
void *bodyContext,
1385+
SWIFT_CONTEXT void *context,
1386+
SWIFT_ERROR_RESULT SwiftError **error) {
1387+
1388+
bool stop = false;
1389+
*error = nullptr;
1390+
1391+
#if defined(__MACH__)
1392+
// This platform has dyld and we can use it instead of walking the section
1393+
// table. Takes care of the nuances of DYLD shared cache support.
1394+
auto section = _getConformanceSectionFromMachImage(imageAddress, true);
1395+
if (section) {
1396+
_enumerateConformingTypesFromSections(section.getPointer(), 1,
1397+
protocols, body, bodyContext,
1398+
&stop, context, error);
1399+
}
1400+
1401+
#else
1402+
// This platform doesn't have dyld, so walk all sections in the conformances
1403+
// table and try to find one that matches the input image address.
1404+
auto snapshot = Conformances.get().SectionsToScan.snapshot();
1405+
1406+
// FIXME: This operation is somewhat costly and could probably be optimized on a per-platform basis.
1407+
for (const auto& section : snapshot) {
1408+
// Check if this section came from the image of interest.
1409+
SymbolInfo info;
1410+
if (!lookupSymbol(section.begin(), &info)) {
1411+
continue;
1412+
}
1413+
1414+
if (info.baseAddress == imageAddress) {
1415+
_enumerateConformingTypesFromSections(&section, 1,
1416+
protocols, body, bodyContext,
1417+
&stop, context, error);
1418+
return;
1419+
}
1420+
}
1421+
#endif
1422+
}
1423+
1424+
SWIFT_CC(swift)
1425+
static void _enumerateConformingTypesFromAllImages(
1426+
const llvm::ArrayRef<ProtocolDescriptorRef>& protocols,
1427+
TypeEnumerationFunction body,
1428+
void *bodyContext,
1429+
SWIFT_CONTEXT void *context,
1430+
SWIFT_ERROR_RESULT SwiftError **error) {
1431+
1432+
bool stop = false;
1433+
*error = nullptr;
1434+
1435+
#if defined(__MACH__)
1436+
// This platform has the dyld shared cache and that means that the
1437+
// conformances table won't contain conformance sections that are otherwise
1438+
// held there. Enumerate all libraries known to dyld.
1439+
//
1440+
// There are a couple of race conditions here:
1441+
// 1. If images are added or removed during enumeration, we may end up
1442+
// missing types that we should be enumerating.
1443+
// 2. If an image containing a conforming ObjC class is loaded while this
1444+
// function is enumerating, the function may enumerate an Objective-C class
1445+
// that has been incompletely initialized.
1446+
//
1447+
// Adding and removing images are rare events that generally don't happen once
1448+
// user code starts executing, but callers may wish to take care not to use
1449+
// this function if the image list may be changed by another thread or by the
1450+
// body closure.
1451+
auto count = _dyld_image_count();
1452+
for (uint32_t i = 0; i < count; i++) {
1453+
const auto& imageAddress = _dyld_get_image_header(i);
1454+
if (!imageAddress) {
1455+
continue;
1456+
}
1457+
1458+
auto section = _getConformanceSectionFromMachImage(imageAddress, false);
1459+
if (section) {
1460+
_enumerateConformingTypesFromSections(section.getPointer(), 1,
1461+
protocols, body, bodyContext,
1462+
&stop, context, error);
1463+
if (stop || *error) {
1464+
return;
1465+
}
1466+
}
1467+
}
1468+
1469+
#else
1470+
// This platform doesn't have the dyld shared cache, so the conformances
1471+
// table should be complete and can be used directly.
1472+
auto snapshot = Conformances.get().SectionsToScan.snapshot();
1473+
1474+
// Walk all sections.
1475+
_enumerateConformingTypesFromSections(snapshot.begin(), snapshot.count(),
1476+
protocols, body, bodyContext,
1477+
&stop, context, error);
1478+
#endif
1479+
}
1480+
1481+
SWIFT_CC(swift)
1482+
void swift::swift_enumerateConformingTypesFromImage(
1483+
const void *imageAddress,
1484+
const Metadata *protocolMetadata,
1485+
TypeEnumerationFunction body,
1486+
void *bodyContext,
1487+
SWIFT_CONTEXT void *context,
1488+
SWIFT_ERROR_RESULT SwiftError **error) {
1489+
1490+
// Find the protocol(s) requested by the caller.
1491+
auto existentialType = dyn_cast<ExistentialTypeMetadata>(protocolMetadata);
1492+
if (!existentialType) {
1493+
auto typeName = swift_getTypeName(protocolMetadata, true);
1494+
swift::fatalError(
1495+
0,
1496+
"_enumerateTypes(fromImageAt:conformingTo:_:) can only test for "
1497+
"conformance to a protocol, but %.*s is not a protocol.",
1498+
(int)typeName.length, typeName.data);
1499+
}
1500+
const auto& protocols = existentialType->getProtocols();
1501+
1502+
if (protocols.empty()) {
1503+
swift::fatalError(
1504+
0,
1505+
"Any is not a protocol and _enumerateTypes(fromImageAt:conformingTo:_:) "
1506+
"cannot test for conformance to it.");
1507+
}
1508+
1509+
#if SWIFT_OBJC_INTEROP
1510+
// Check for any Objective-C protocols in the input. We do not currently
1511+
// support searching for Objective-C protocol conformances because they do not
1512+
// produce conformance records in the compiled binary. In the future, if we
1513+
// detect any such protocols, we can use objc_copyClassList() to add to the
1514+
// enumerated set (taking care not to enumerate a class twice if it conforms
1515+
// to an Objective-C protocol *and* a Swift protocol.)
1516+
for (const auto& protocol : protocols) {
1517+
if (protocol.isObjC()) {
1518+
swift::fatalError(
1519+
0,
1520+
"_enumerateTypes(fromImageAt:conformingTo:_:) does not currently "
1521+
"support Objective-C protocols such as %s.",
1522+
protocol.getName());
1523+
}
1524+
}
1525+
#endif
1526+
1527+
#if defined(__WASM__)
1528+
// Swift+WASM does not support dynamic linking and #dsohandle is 0, so
1529+
// ignore the passed image address and iterate over any/all available
1530+
// sections. Presumably only one will be available.
1531+
imageAddress = nullptr;
1532+
#endif
1533+
1534+
if (imageAddress) {
1535+
_enumerateConformingTypesFromImage(imageAddress, protocols, body,
1536+
bodyContext, context, error);
1537+
} else {
1538+
_enumerateConformingTypesFromAllImages(protocols, body, bodyContext,
1539+
context, error);
1540+
}
1541+
}
1542+
12741543
#define OVERRIDE_PROTOCOLCONFORMANCE COMPATIBILITY_OVERRIDE
12751544
#include COMPATIBILITY_OVERRIDE_INCLUDE_PATH

0 commit comments

Comments
 (0)