From 02bedf79a9d965389834b31eccfd379df1006cb8 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Mon, 9 Sep 2024 14:23:35 -0400 Subject: [PATCH 1/5] Optionally symbolicate backtraces. This PR adds the ability to symbolicate backtraces on Darwin and Windows. A few different modes are provided: mangled, demangled, and "precise demangled" (which includes symbol addresses and instruction pointer offsets.) Tools such as the Swift VS Code plugin will be able to adopt this new feature along with VS Code's new call stacks API (https://github.com/microsoft/vscode/pull/222126). Note that on Linux, it is not currently possible to symbolicate backtraces meaningfully. The standard library's `Backtrace` type has the ability to do this for us, but we'll need some tweaks to its interface before we can adopt it. Note also that Apple's internal Core Symbolication framework is not used; we may be able to add support for it in a future PR (or Apple may opt to use it in their internal fork of Swift Testing.) There is no way to emit backtraces to the command line right now. I considered having `--very-verbose` imply backtraces, but it's something I'm going to reserve for a future PR after discussion with the community. --- .../Testing/ABI/EntryPoints/EntryPoint.swift | 24 +++ .../v0/Encoded/ABIv0.EncodedBacktrace.swift | 53 +++++ .../ABI/v0/Encoded/ABIv0.EncodedEvent.swift | 2 +- .../ABI/v0/Encoded/ABIv0.EncodedIssue.swift | 8 +- Sources/Testing/CMakeLists.txt | 2 + Sources/Testing/Running/Configuration.swift | 10 + .../Backtrace+Symbolication.swift | 191 ++++++++++++++++++ Sources/_TestingInternals/CMakeLists.txt | 1 + Sources/_TestingInternals/Demangle.cpp | 72 +++++++ Sources/_TestingInternals/include/Demangle.h | 54 +++++ Tests/TestingTests/BacktraceTests.swift | 10 + Tests/TestingTests/SwiftPMTests.swift | 17 ++ 12 files changed, 440 insertions(+), 4 deletions(-) create mode 100644 Sources/Testing/ABI/v0/Encoded/ABIv0.EncodedBacktrace.swift create mode 100644 Sources/Testing/SourceAttribution/Backtrace+Symbolication.swift create mode 100644 Sources/_TestingInternals/Demangle.cpp create mode 100644 Sources/_TestingInternals/include/Demangle.h diff --git a/Sources/Testing/ABI/EntryPoints/EntryPoint.swift b/Sources/Testing/ABI/EntryPoints/EntryPoint.swift index 8e0416ba0..3de4dd468 100644 --- a/Sources/Testing/ABI/EntryPoints/EntryPoint.swift +++ b/Sources/Testing/ABI/EntryPoints/EntryPoint.swift @@ -191,6 +191,9 @@ public struct __CommandLineArguments_v0: Sendable { /// The value of the `--parallel` or `--no-parallel` argument. public var parallel: Bool? + /// The value of the `--symbolicate-backtraces` argument. + public var symbolicateBacktraces: String? + /// The value of the `--verbose` argument. public var verbose: Bool? @@ -280,6 +283,7 @@ extension __CommandLineArguments_v0: Codable { enum CodingKeys: String, CodingKey { case listTests case parallel + case symbolicateBacktraces case verbose case veryVerbose case quiet @@ -366,6 +370,11 @@ func parseCommandLineArguments(from args: [String]) throws -> __CommandLineArgum result.parallel = false } + // Whether or not to symbolicate backtraces in the event stream. + if let symbolicateBacktracesIndex = args.firstIndex(of: "--symbolicate-backtraces"), !isLastArgument(at: symbolicateBacktracesIndex) { + result.symbolicateBacktraces = args[args.index(after: symbolicateBacktracesIndex)] + } + // Verbosity if let verbosityIndex = args.firstIndex(of: "--verbosity"), !isLastArgument(at: verbosityIndex), let verbosity = Int(args[args.index(after: verbosityIndex)]) { @@ -425,6 +434,21 @@ public func configurationForEntryPoint(from args: __CommandLineArguments_v0) thr // Parallelization (on by default) configuration.isParallelizationEnabled = args.parallel ?? true + // Whether or not to symbolicate backtraces in the event stream. + if let symbolicateBacktraces = args.symbolicateBacktraces { + switch symbolicateBacktraces.lowercased() { + case "mangled", "on", "true": + configuration.backtraceSymbolicationMode = .mangled + case "demangled": + configuration.backtraceSymbolicationMode = .demangled + case "precise-demangled": + configuration.backtraceSymbolicationMode = .preciseDemangled + default: + throw _EntryPointError.invalidArgument("--symbolicate-backtraces", value: symbolicateBacktraces) + + } + } + #if !SWT_NO_FILE_IO // XML output if let xunitOutputPath = args.xunitOutput { diff --git a/Sources/Testing/ABI/v0/Encoded/ABIv0.EncodedBacktrace.swift b/Sources/Testing/ABI/v0/Encoded/ABIv0.EncodedBacktrace.swift new file mode 100644 index 000000000..64c088294 --- /dev/null +++ b/Sources/Testing/ABI/v0/Encoded/ABIv0.EncodedBacktrace.swift @@ -0,0 +1,53 @@ +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for Swift project authors +// + +extension ABIv0 { + /// A type implementing the JSON encoding of ``Backtrace`` for the ABI entry + /// point and event stream output. + /// + /// This type is not part of the public interface of the testing library. It + /// assists in converting values to JSON; clients that consume this JSON are + /// expected to write their own decoders. + struct EncodedBacktrace: Sendable { + /// A type describing a frame in the backtrace. + struct Frame: Sendable { + /// The address of the frame. + var address: Backtrace.Address + + /// The name of the frame, possibly demangled, if available. + var symbolName: String? + } + + /// The frames in the backtrace. + var frames: [Frame] + + init(encoding backtrace: borrowing Backtrace, in eventContext: borrowing Event.Context) { + if let symbolicationMode = eventContext.configuration?.backtraceSymbolicationMode { + frames = zip(backtrace.addresses, backtrace.symbolicate(symbolicationMode)).map(Frame.init) + } else { + frames = backtrace.addresses.map { Frame(address: $0) } + } + } + } +} + +// MARK: - Codable + +extension ABIv0.EncodedBacktrace: Codable { + func encode(to encoder: any Encoder) throws { + try frames.encode(to: encoder) + } + + init(from decoder: any Decoder) throws { + self.frames = try [Frame](from: decoder) + } +} + +extension ABIv0.EncodedBacktrace.Frame: Codable {} diff --git a/Sources/Testing/ABI/v0/Encoded/ABIv0.EncodedEvent.swift b/Sources/Testing/ABI/v0/Encoded/ABIv0.EncodedEvent.swift index 951244af3..65cd78234 100644 --- a/Sources/Testing/ABI/v0/Encoded/ABIv0.EncodedEvent.swift +++ b/Sources/Testing/ABI/v0/Encoded/ABIv0.EncodedEvent.swift @@ -70,7 +70,7 @@ extension ABIv0 { kind = .testCaseStarted case let .issueRecorded(recordedIssue): kind = .issueRecorded - issue = EncodedIssue(encoding: recordedIssue) + issue = EncodedIssue(encoding: recordedIssue, in: eventContext) case .testCaseEnded: if eventContext.test?.isParameterized == false { return nil diff --git a/Sources/Testing/ABI/v0/Encoded/ABIv0.EncodedIssue.swift b/Sources/Testing/ABI/v0/Encoded/ABIv0.EncodedIssue.swift index 7399fd830..419af3d73 100644 --- a/Sources/Testing/ABI/v0/Encoded/ABIv0.EncodedIssue.swift +++ b/Sources/Testing/ABI/v0/Encoded/ABIv0.EncodedIssue.swift @@ -23,12 +23,14 @@ extension ABIv0 { var sourceLocation: SourceLocation? /// The backtrace where this issue occurred, if available. - var _backtrace: [Backtrace.Address]? + var _backtrace: EncodedBacktrace? - init(encoding issue: borrowing Issue) { + init(encoding issue: borrowing Issue, in eventContext: borrowing Event.Context) { isKnown = issue.isKnown sourceLocation = issue.sourceLocation - _backtrace = issue.sourceContext.backtrace.map(\.addresses) + if let backtrace = issue.sourceContext.backtrace { + _backtrace = EncodedBacktrace(encoding: backtrace, in: eventContext) + } } } } diff --git a/Sources/Testing/CMakeLists.txt b/Sources/Testing/CMakeLists.txt index 69b851645..2afb51367 100644 --- a/Sources/Testing/CMakeLists.txt +++ b/Sources/Testing/CMakeLists.txt @@ -13,6 +13,7 @@ add_library(Testing ABI/v0/ABIv0.Record.swift ABI/v0/ABIv0.Record+Streaming.swift ABI/v0/ABIv0.swift + ABI/v0/Encoded/ABIv0.EncodedBacktrace.swift ABI/v0/Encoded/ABIv0.EncodedEvent.swift ABI/v0/Encoded/ABIv0.EncodedInstant.swift ABI/v0/Encoded/ABIv0.EncodedIssue.swift @@ -50,6 +51,7 @@ add_library(Testing Running/Runner.swift Running/SkipInfo.swift SourceAttribution/Backtrace.swift + SourceAttribution/Backtrace+Symbolication.swift SourceAttribution/CustomTestStringConvertible.swift SourceAttribution/Expression.swift SourceAttribution/Expression+Macro.swift diff --git a/Sources/Testing/Running/Configuration.swift b/Sources/Testing/Running/Configuration.swift index 80cd65a49..c359d4ee0 100644 --- a/Sources/Testing/Running/Configuration.swift +++ b/Sources/Testing/Running/Configuration.swift @@ -20,6 +20,16 @@ public struct Configuration: Sendable { /// Whether or not to parallelize the execution of tests and test cases. public var isParallelizationEnabled = true + /// How to symbolicate backtraces captured during a test run. + /// + /// If the value of this property is not `nil`, symbolication will be + /// performed automatically when a backtrace is encoded into an event stream. + /// + /// The value of this property does not affect event handlers implemented in + /// Swift in-process. When handling a backtrace in Swift, use its + /// ``Backtrace/symbolicate(_:)`` function to symbolicate it. + public var backtraceSymbolicationMode: Backtrace.SymbolicationMode? + /// A type describing whether or not, and how, to iterate a test plan /// repeatedly. /// diff --git a/Sources/Testing/SourceAttribution/Backtrace+Symbolication.swift b/Sources/Testing/SourceAttribution/Backtrace+Symbolication.swift new file mode 100644 index 000000000..7acd62c2a --- /dev/null +++ b/Sources/Testing/SourceAttribution/Backtrace+Symbolication.swift @@ -0,0 +1,191 @@ +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for Swift project authors +// + +private import _TestingInternals + +/// A type representing a backtrace or stack trace. +@_spi(ForToolsIntegrationOnly) +extension Backtrace { + /// Demangle a symbol name. + /// + /// - Parameters: + /// - mangledSymbolName: The symbol name to demangle. + /// + /// - Returns: The demangled form of `mangledSymbolName` according to the + /// Swift standard library or the platform's C++ standard library, or `nil` + /// if the symbol name could not be demangled. + private static func _demangle(_ mangledSymbolName: String) -> String? { + guard let demangledSymbolName = swt_copyDemangledSymbolName(mangledSymbolName) else { + return nil + } + defer { + free(demangledSymbolName) + } + return String(validatingCString: demangledSymbolName) + } + + /// Symbolicate a sequence of addresses. + /// + /// - Parameters: + /// - addresses: The sequence of addresses. These addresses must have + /// originated in the current process. + /// - mode: How to symbolicate the addresses in the backtrace. + /// + /// - Returns: An array of strings representing the names of symbols in + /// `addresses`. + /// + /// If an address in `addresses` cannot be symbolicated, it is converted to a + /// string using ``Swift/String/init(describingForTest:)``. + private static func _symbolicate(addresses: UnsafeBufferPointer, mode: SymbolicationMode) -> [String] { + let count = addresses.count + var symbolNames = [(String, displacement: UInt)?](repeating: nil, count: count) + +#if SWT_TARGET_OS_APPLE + for (i, address) in addresses.enumerated() { + guard let address else { + continue + } + var info = Dl_info() + if 0 != dladdr(address, &info) { + let displacement = UInt(bitPattern: address) - UInt(bitPattern: info.dli_saddr) + if var symbolName = info.dli_sname.flatMap(String.init(validatingCString:)) { + if mode != .mangled { + symbolName = _demangle(symbolName) ?? symbolName + } + symbolNames[i] = (symbolName, displacement) + } + } + } +#elseif os(Linux) + // Although Linux has dladdr(), it does not have symbol names from ELF + // binaries by default. The standard library's backtracing functionality has + // implemented sufficient ELF/DWARF parsing to be able to symbolicate Linux + // backtraces. TODO: adopt the standard library's Backtrace on Linux + // Note that this means on Linux we don't have demangling capability (since + // we don't have the mangled symbol names in the first place) so this code + // does not check the mode argument. +#elseif os(Windows) + _withDbgHelpLibrary { hProcess in + guard let hProcess else { + return + } + for (i, address) in addresses.enumerated() { + guard let address else { + continue + } + + withUnsafeTemporaryAllocation(of: SYMBOL_INFO_PACKAGEW.self, capacity: 1) { symbolInfo in + let symbolInfo = symbolInfo.baseAddress! + symbolInfo.pointee.si.SizeOfStruct = ULONG(MemoryLayout.stride) + symbolInfo.pointee.si.MaxNameLen = ULONG(MAX_SYM_NAME) + var displacement = DWORD64(0) + if SymFromAddrW(hProcess, DWORD64(clamping: UInt(bitPattern: address)), &displacement, symbolInfo.pointer(to: \.si)!), + var symbolName = String.decodeCString(symbolInfo.pointer(to: \.si.Name)!, as: UTF16.self)?.result { + if mode != .mangled { + symbolName = _demangle(symbolName) ?? symbolName + } + symbolNames[i] = (symbolName, UInt(clamping: displacement)) + } + } + } + } +#elseif os(WASI) + // WASI does not currently support backtracing let alone symbolication. +#else +#warning("Platform-specific implementation missing: backtrace symbolication unavailable") +#endif + + var result = [String]() + result.reserveCapacity(count) + for (i, address) in addresses.enumerated() { + let formatted = if let (symbolName, displacement) = symbolNames[i] { + if mode == .preciseDemangled { + "\(i) \(symbolName) (\(String(describingForTest: address))+\(displacement))" + } else { + symbolName + } + } else { + String(describingForTest: address) + } + result.append(formatted) + } + return result + } + + /// An enumeration describing the symbolication mode to use when handling + /// events containing backtraces. + public enum SymbolicationMode: Sendable { + /// The backtrace should be symbolicated, but no demangling should be + /// performed. + case mangled + + /// The backtrace should be symbolicated and Swift and C++ symbols should be + /// demangled if possible. + case demangled + + /// The backtrace should be symbolicated, Swift and C++ symbols should be + /// demangled if possible, and precise symbol addresses and offsets should + /// be provided if available. + case preciseDemangled + } + + /// Symbolicate the addresses in this backtrace. + /// + /// - Parameters: + /// - mode: How to symbolicate the addresses in the backtrace. + /// + /// - Returns: An array of strings representing the names of symbols in + /// `addresses`. + /// + /// If an address in `addresses` cannot be symbolicated, it is converted to a + /// string using ``Swift/String/init(describingForTest:)``. + public func symbolicate(_ mode: SymbolicationMode) -> [String] { +#if _pointerBitWidth(_64) + // The width of a pointer equals the width of an `Address`, so we can just + // bitcast the memory rather than mapping through UInt first. + addresses.withUnsafeBufferPointer { addresses in + addresses.withMemoryRebound(to: UnsafeRawPointer?.self) { addresses in + Self._symbolicate(addresses: addresses, mode: mode) + } + } +#else + let addresses = addresses.map { UnsafeRawPointer(bitPattern: UInt($0)) } + return addresses.withUnsafeBufferPointer { addresses in + Self._symbolicate(addresses: addresses, mode: mode) + } +#endif + } +} + +#if os(Windows) +// MARK: - + +/// Configure the environment to allow calling into the Debug Help library. +/// +/// - Parameters: +/// - body: A function to invoke. A process handle valid for use with Debug +/// Help functions is passed in, or `nullptr` if the Debug Help library +/// could not be initialized. +/// - context: An arbitrary pointer to pass to `body`. +/// +/// On Windows, the Debug Help library (DbgHelp.lib) is not thread-safe. All +/// calls into it from the Swift runtime and stdlib should route through this +/// function. +private func _withDbgHelpLibrary(_ body: (HANDLE?) -> Void) { + withoutActuallyEscaping(body) { body in + withUnsafePointer(to: body) { context in + _swift_win32_withDbgHelpLibrary({ hProcess, context in + let body = context!.load(as: ((HANDLE?) -> Void).self) + body(hProcess) + }, .init(mutating: context)) + } + } +} +#endif diff --git a/Sources/_TestingInternals/CMakeLists.txt b/Sources/_TestingInternals/CMakeLists.txt index b0bde88ed..763bb9d24 100644 --- a/Sources/_TestingInternals/CMakeLists.txt +++ b/Sources/_TestingInternals/CMakeLists.txt @@ -11,6 +11,7 @@ set(CMAKE_CXX_SCAN_FOR_MODULES 0) include(LibraryVersion) include(TargetTriple) add_library(_TestingInternals STATIC + Demangle.cpp Discovery.cpp Versions.cpp WillThrow.cpp) diff --git a/Sources/_TestingInternals/Demangle.cpp b/Sources/_TestingInternals/Demangle.cpp new file mode 100644 index 000000000..2ee7b3f21 --- /dev/null +++ b/Sources/_TestingInternals/Demangle.cpp @@ -0,0 +1,72 @@ +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for Swift project authors +// + +#include "Demangle.h" + +#include +#include +#include + +#if defined(__APPLE__) || defined(__linux__) +#include +#elif defined(_WIN32) +#include +#pragma comment(lib, "DbgHelp.lib") +#endif + +SWT_IMPORT_FROM_STDLIB char *swift_demangle(const char *mangledName, size_t mangledNameLength, char *outputBuffer, size_t *outputBufferSize, uint32_t flags); + +char *swt_copyDemangledSymbolName(const char *mangledName) { + if (mangledName[0] == '\0') { + return nullptr; + } + + // First, try using Swift's demangler. + char *result = swift_demangle(mangledName, std::strlen(mangledName), nullptr, nullptr, 0); + + // Try using the platform's C++ demangler instead. + if (!result) { +#if defined(__APPLE__) || defined(__linux__) + int status = 0; + result = __cxxabiv1::__cxa_demangle(mangledName, nullptr, nullptr, &status); +#elif defined(_WIN32) + // std::type_info::raw_name() has a leading period that interferes with + // demangling. Strip it off if found. + if (mangledName[0] == '.') { + mangledName += 1; + } + + // MSVC++-style mangled names always start with '?'. + if (mangledName[0] != '?') { + return nullptr; + } + + // Allocate some space for the demangled type name. + static const constexpr size_t MAX_DEMANGLED_NAME_SIZE = 1024; + if (auto demangledName = reinterpret_cast(std::malloc(MAX_DEMANGLED_NAME_SIZE))) { + + // Call into DbgHelp to perform the demangling. These flags should + // give us the correct demangling of a mangled C++ type name. + DWORD undecorateFlags = UNDNAME_NAME_ONLY | UNDNAME_NO_ARGUMENTS; +#if !defined(_WIN64) + undecorateFlags |= UNDNAME_32_BIT_DECODE; +#endif + if (UnDecorateSymbolName(mangledName, demangledName, MAX_DEMANGLED_NAME_SIZE, undecorateFlags)) { + result = demangledName; + } else { + // Don't leak the allocated buffer if demangling failed. + std::free(demangledName); + } + } +#endif + } + + return result; +} diff --git a/Sources/_TestingInternals/include/Demangle.h b/Sources/_TestingInternals/include/Demangle.h new file mode 100644 index 000000000..4f6b964bc --- /dev/null +++ b/Sources/_TestingInternals/include/Demangle.h @@ -0,0 +1,54 @@ +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for Swift project authors +// + +#if !defined(SWT_DEMANGLE_H) +#define SWT_DEMANGLE_H + +#include "Defines.h" +#include "Includes.h" + +#include + +SWT_ASSUME_NONNULL_BEGIN + +/// Attempt to demangle the given symbol name. +/// +/// - Parameters: +/// - symbolName: The symbol name to demangle. +/// +/// - Returns: The demangled form of `symbolName`, or `nullptr` if it could not +/// be demangled. +/// +/// On Windows, this function must only be called from within a call to +/// `_swift_win32_withDbgHelpLibrary()`. Note that Swift symbol demangling may +/// internally use `operator new`. +SWT_EXTERN char *_Nullable swt_copyDemangledSymbolName(const char *symbolName); + +#if defined(_WIN32) +/// Configure the environment to allow calling into the Debug Help library. +/// +/// - Parameters: +/// - body: A function to invoke. A process handle valid for use with Debug +/// Help functions is passed in, or `nullptr` if the Debug Help library +/// could not be initialized. +/// - context: An arbitrary pointer to pass to `body`. +/// +/// On Windows, the Debug Help library (DbgHelp.lib) is not thread-safe. All +/// calls into it from the Swift runtime and stdlib should route through this +/// function. +SWT_IMPORT_FROM_STDLIB void _swift_win32_withDbgHelpLibrary( + void (* body)(HANDLE _Nullable hProcess, void *_Null_unspecified context), + void *_Null_unspecified context +); +#endif + +SWT_ASSUME_NONNULL_END + +#endif diff --git a/Tests/TestingTests/BacktraceTests.swift b/Tests/TestingTests/BacktraceTests.swift index ac0fc98f5..9bb723453 100644 --- a/Tests/TestingTests/BacktraceTests.swift +++ b/Tests/TestingTests/BacktraceTests.swift @@ -149,4 +149,14 @@ struct BacktraceTests { #expect(original == copy) } #endif + + @Test("Symbolication", arguments: [Backtrace.SymbolicationMode.mangled, .demangled, .preciseDemangled]) + func symbolication(mode: Backtrace.SymbolicationMode) { + let backtrace = Backtrace.current() + let symbolNames = backtrace.symbolicate(mode) + #expect(backtrace.addresses.count == symbolNames.count) + if testsWithSignificantIOAreEnabled { + print(symbolNames.joined(separator: "\n")) + } + } } diff --git a/Tests/TestingTests/SwiftPMTests.swift b/Tests/TestingTests/SwiftPMTests.swift index 8e13967f5..fb24fcee1 100644 --- a/Tests/TestingTests/SwiftPMTests.swift +++ b/Tests/TestingTests/SwiftPMTests.swift @@ -43,6 +43,23 @@ struct SwiftPMTests { #expect(!configuration.isParallelizationEnabled) } + @Test("--symbolicate-backtraces argument", + arguments: [ + (String?.none, Backtrace.SymbolicationMode?.none), + ("mangled", .mangled), ("on", .mangled), ("true", .mangled), + ("demangled", .demangled), + ("precise-demangled", .preciseDemangled), + ] + ) + func symbolicateBacktraces(argumentValue: String?, expectedMode: Backtrace.SymbolicationMode?) throws { + let configuration = if let argumentValue { + try configurationForEntryPoint(withArguments: ["PATH", "--symbolicate-backtraces", argumentValue]) + } else { + try configurationForEntryPoint(withArguments: ["PATH"]) + } + #expect(configuration.backtraceSymbolicationMode == expectedMode) + } + @Test("No --filter or --skip argument") func defaultFiltering() async throws { let configuration = try configurationForEntryPoint(withArguments: ["PATH"]) From fd48220d89a1647ee1055b2b82fb65260d6d8835 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Tue, 10 Sep 2024 17:37:01 -0400 Subject: [PATCH 2/5] Rather than have a separate .preciseDemangled, just output the backtrace as structured values and let the JSON consumer construct a single string as desired --- .../Testing/ABI/EntryPoints/EntryPoint.swift | 2 - .../v0/Encoded/ABIv0.EncodedBacktrace.swift | 21 +-- .../Backtrace+Symbolication.swift | 137 +++++++----------- Tests/TestingTests/BacktraceTests.swift | 4 +- Tests/TestingTests/SwiftPMTests.swift | 1 - 5 files changed, 60 insertions(+), 105 deletions(-) diff --git a/Sources/Testing/ABI/EntryPoints/EntryPoint.swift b/Sources/Testing/ABI/EntryPoints/EntryPoint.swift index 3de4dd468..976aa7725 100644 --- a/Sources/Testing/ABI/EntryPoints/EntryPoint.swift +++ b/Sources/Testing/ABI/EntryPoints/EntryPoint.swift @@ -441,8 +441,6 @@ public func configurationForEntryPoint(from args: __CommandLineArguments_v0) thr configuration.backtraceSymbolicationMode = .mangled case "demangled": configuration.backtraceSymbolicationMode = .demangled - case "precise-demangled": - configuration.backtraceSymbolicationMode = .preciseDemangled default: throw _EntryPointError.invalidArgument("--symbolicate-backtraces", value: symbolicateBacktraces) diff --git a/Sources/Testing/ABI/v0/Encoded/ABIv0.EncodedBacktrace.swift b/Sources/Testing/ABI/v0/Encoded/ABIv0.EncodedBacktrace.swift index 64c088294..8f681c20a 100644 --- a/Sources/Testing/ABI/v0/Encoded/ABIv0.EncodedBacktrace.swift +++ b/Sources/Testing/ABI/v0/Encoded/ABIv0.EncodedBacktrace.swift @@ -16,23 +16,14 @@ extension ABIv0 { /// assists in converting values to JSON; clients that consume this JSON are /// expected to write their own decoders. struct EncodedBacktrace: Sendable { - /// A type describing a frame in the backtrace. - struct Frame: Sendable { - /// The address of the frame. - var address: Backtrace.Address - - /// The name of the frame, possibly demangled, if available. - var symbolName: String? - } - /// The frames in the backtrace. - var frames: [Frame] + var symbolicatedAddresses: [Backtrace.SymbolicatedAddress] init(encoding backtrace: borrowing Backtrace, in eventContext: borrowing Event.Context) { if let symbolicationMode = eventContext.configuration?.backtraceSymbolicationMode { - frames = zip(backtrace.addresses, backtrace.symbolicate(symbolicationMode)).map(Frame.init) + symbolicatedAddresses = backtrace.symbolicate(symbolicationMode) } else { - frames = backtrace.addresses.map { Frame(address: $0) } + symbolicatedAddresses = backtrace.addresses.map { Backtrace.SymbolicatedAddress(address: $0) } } } } @@ -42,12 +33,10 @@ extension ABIv0 { extension ABIv0.EncodedBacktrace: Codable { func encode(to encoder: any Encoder) throws { - try frames.encode(to: encoder) + try symbolicatedAddresses.encode(to: encoder) } init(from decoder: any Decoder) throws { - self.frames = try [Frame](from: decoder) + self.symbolicatedAddresses = try [Backtrace.SymbolicatedAddress](from: decoder) } } - -extension ABIv0.EncodedBacktrace.Frame: Codable {} diff --git a/Sources/Testing/SourceAttribution/Backtrace+Symbolication.swift b/Sources/Testing/SourceAttribution/Backtrace+Symbolication.swift index 7acd62c2a..e9813eba1 100644 --- a/Sources/Testing/SourceAttribution/Backtrace+Symbolication.swift +++ b/Sources/Testing/SourceAttribution/Backtrace+Symbolication.swift @@ -14,10 +14,10 @@ private import _TestingInternals @_spi(ForToolsIntegrationOnly) extension Backtrace { /// Demangle a symbol name. - /// + /// /// - Parameters: /// - mangledSymbolName: The symbol name to demangle. - /// + /// /// - Returns: The demangled form of `mangledSymbolName` according to the /// Swift standard library or the platform's C++ standard library, or `nil` /// if the symbol name could not be demangled. @@ -31,35 +31,62 @@ extension Backtrace { return String(validatingCString: demangledSymbolName) } - /// Symbolicate a sequence of addresses. - /// + /// An enumeration describing the symbolication mode to use when handling + /// events containing backtraces. + public enum SymbolicationMode: Sendable { + /// The backtrace should be symbolicated, but no demangling should be + /// performed. + case mangled + + /// The backtrace should be symbolicated and Swift and C++ symbols should be + /// demangled if possible. + case demangled + } + + /// A type representing an instance of ``Backtrace/Address`` that has been + /// symbolicated by a call to ``Backtrace/symbolicate(_:)``. + public struct SymbolicatedAddress: Sendable { + /// The (unsymbolicated) address from the backtrace. + public var address: Address + + /// The offset of ``address`` from the start of the corresponding function, + /// if available. + /// + /// If ``address`` could not be resolved to a symbol, the value of this + /// property is `nil`. + public var offset: UInt64? + + /// The name of the symbol at ``address``, if available. + /// + /// If ``address`` could not be resolved to a symbol, the value of this + /// property is `nil`. + public var symbolName: String? + } + + /// Symbolicate the addresses in this backtrace. + /// /// - Parameters: - /// - addresses: The sequence of addresses. These addresses must have - /// originated in the current process. /// - mode: How to symbolicate the addresses in the backtrace. - /// + /// /// - Returns: An array of strings representing the names of symbols in /// `addresses`. - /// - /// If an address in `addresses` cannot be symbolicated, it is converted to a - /// string using ``Swift/String/init(describingForTest:)``. - private static func _symbolicate(addresses: UnsafeBufferPointer, mode: SymbolicationMode) -> [String] { - let count = addresses.count - var symbolNames = [(String, displacement: UInt)?](repeating: nil, count: count) + /// + /// If an address in `addresses` cannot be symbolicated, the corresponding + /// instance of ``SymbolicatedAddress`` in the resulting array has a `nil` + /// value for its ``Backtrace/SymbolicatedAddress/symbolName`` property. + public func symbolicate(_ mode: SymbolicationMode) -> [SymbolicatedAddress] { + var result = addresses.map { SymbolicatedAddress(address: $0) } #if SWT_TARGET_OS_APPLE for (i, address) in addresses.enumerated() { - guard let address else { - continue - } var info = Dl_info() - if 0 != dladdr(address, &info) { - let displacement = UInt(bitPattern: address) - UInt(bitPattern: info.dli_saddr) + if 0 != dladdr(UnsafePointer(bitPattern: UInt(clamping: address)), &info) { + let offset = address - Address(clamping: UInt(bitPattern: info.dli_saddr)) if var symbolName = info.dli_sname.flatMap(String.init(validatingCString:)) { if mode != .mangled { - symbolName = _demangle(symbolName) ?? symbolName + symbolName = Self._demangle(symbolName) ?? symbolName } - symbolNames[i] = (symbolName, displacement) + result[i] = SymbolicatedAddress(address: address, offset: offset, symbolName: symbolName) } } } @@ -77,21 +104,17 @@ extension Backtrace { return } for (i, address) in addresses.enumerated() { - guard let address else { - continue - } - withUnsafeTemporaryAllocation(of: SYMBOL_INFO_PACKAGEW.self, capacity: 1) { symbolInfo in let symbolInfo = symbolInfo.baseAddress! symbolInfo.pointee.si.SizeOfStruct = ULONG(MemoryLayout.stride) symbolInfo.pointee.si.MaxNameLen = ULONG(MAX_SYM_NAME) var displacement = DWORD64(0) - if SymFromAddrW(hProcess, DWORD64(clamping: UInt(bitPattern: address)), &displacement, symbolInfo.pointer(to: \.si)!), + if SymFromAddrW(hProcess, DWORD64(clamping: address), &displacement, symbolInfo.pointer(to: \.si)!), var symbolName = String.decodeCString(symbolInfo.pointer(to: \.si.Name)!, as: UTF16.self)?.result { if mode != .mangled { - symbolName = _demangle(symbolName) ?? symbolName + symbolName = Self._demangle(symbolName) ?? symbolName } - symbolNames[i] = (symbolName, UInt(clamping: displacement)) + result[i] = SymbolicatedAddress(address: address, offset: displacement, symbolName: symbolName) } } } @@ -102,67 +125,13 @@ extension Backtrace { #warning("Platform-specific implementation missing: backtrace symbolication unavailable") #endif - var result = [String]() - result.reserveCapacity(count) - for (i, address) in addresses.enumerated() { - let formatted = if let (symbolName, displacement) = symbolNames[i] { - if mode == .preciseDemangled { - "\(i) \(symbolName) (\(String(describingForTest: address))+\(displacement))" - } else { - symbolName - } - } else { - String(describingForTest: address) - } - result.append(formatted) - } return result } +} - /// An enumeration describing the symbolication mode to use when handling - /// events containing backtraces. - public enum SymbolicationMode: Sendable { - /// The backtrace should be symbolicated, but no demangling should be - /// performed. - case mangled - - /// The backtrace should be symbolicated and Swift and C++ symbols should be - /// demangled if possible. - case demangled - - /// The backtrace should be symbolicated, Swift and C++ symbols should be - /// demangled if possible, and precise symbol addresses and offsets should - /// be provided if available. - case preciseDemangled - } +// MARK: - Codable - /// Symbolicate the addresses in this backtrace. - /// - /// - Parameters: - /// - mode: How to symbolicate the addresses in the backtrace. - /// - /// - Returns: An array of strings representing the names of symbols in - /// `addresses`. - /// - /// If an address in `addresses` cannot be symbolicated, it is converted to a - /// string using ``Swift/String/init(describingForTest:)``. - public func symbolicate(_ mode: SymbolicationMode) -> [String] { -#if _pointerBitWidth(_64) - // The width of a pointer equals the width of an `Address`, so we can just - // bitcast the memory rather than mapping through UInt first. - addresses.withUnsafeBufferPointer { addresses in - addresses.withMemoryRebound(to: UnsafeRawPointer?.self) { addresses in - Self._symbolicate(addresses: addresses, mode: mode) - } - } -#else - let addresses = addresses.map { UnsafeRawPointer(bitPattern: UInt($0)) } - return addresses.withUnsafeBufferPointer { addresses in - Self._symbolicate(addresses: addresses, mode: mode) - } -#endif - } -} +extension Backtrace.SymbolicatedAddress: Codable {} #if os(Windows) // MARK: - diff --git a/Tests/TestingTests/BacktraceTests.swift b/Tests/TestingTests/BacktraceTests.swift index 9bb723453..88e829a9f 100644 --- a/Tests/TestingTests/BacktraceTests.swift +++ b/Tests/TestingTests/BacktraceTests.swift @@ -150,13 +150,13 @@ struct BacktraceTests { } #endif - @Test("Symbolication", arguments: [Backtrace.SymbolicationMode.mangled, .demangled, .preciseDemangled]) + @Test("Symbolication", arguments: [Backtrace.SymbolicationMode.mangled, .demangled]) func symbolication(mode: Backtrace.SymbolicationMode) { let backtrace = Backtrace.current() let symbolNames = backtrace.symbolicate(mode) #expect(backtrace.addresses.count == symbolNames.count) if testsWithSignificantIOAreEnabled { - print(symbolNames.joined(separator: "\n")) + print(symbolNames.map(String.init(describingForTest:)).joined(separator: "\n")) } } } diff --git a/Tests/TestingTests/SwiftPMTests.swift b/Tests/TestingTests/SwiftPMTests.swift index fb24fcee1..51764fa1a 100644 --- a/Tests/TestingTests/SwiftPMTests.swift +++ b/Tests/TestingTests/SwiftPMTests.swift @@ -48,7 +48,6 @@ struct SwiftPMTests { (String?.none, Backtrace.SymbolicationMode?.none), ("mangled", .mangled), ("on", .mangled), ("true", .mangled), ("demangled", .demangled), - ("precise-demangled", .preciseDemangled), ] ) func symbolicateBacktraces(argumentValue: String?, expectedMode: Backtrace.SymbolicationMode?) throws { From 8e235ef75e27d59007f24b1bea6da413b0293df7 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Wed, 11 Sep 2024 09:25:44 -0400 Subject: [PATCH 3/5] Mark new stuff experimental --- Sources/Testing/SourceAttribution/Backtrace+Symbolication.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Testing/SourceAttribution/Backtrace+Symbolication.swift b/Sources/Testing/SourceAttribution/Backtrace+Symbolication.swift index e9813eba1..29deb6b3b 100644 --- a/Sources/Testing/SourceAttribution/Backtrace+Symbolication.swift +++ b/Sources/Testing/SourceAttribution/Backtrace+Symbolication.swift @@ -11,7 +11,7 @@ private import _TestingInternals /// A type representing a backtrace or stack trace. -@_spi(ForToolsIntegrationOnly) +@_spi(Experimental) @_spi(ForToolsIntegrationOnly) extension Backtrace { /// Demangle a symbol name. /// From 2958be947f2dd0080c8f730dacfbcf8fd39bf31a Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Wed, 11 Sep 2024 17:45:14 -0400 Subject: [PATCH 4/5] Simplify demangling, don't do C++ demangling for now --- .../Backtrace+Symbolication.swift | 71 +++++++++--------- Sources/_TestingInternals/CMakeLists.txt | 1 - Sources/_TestingInternals/Demangle.cpp | 72 ------------------- Sources/_TestingInternals/include/Demangle.h | 26 ++++--- 4 files changed, 56 insertions(+), 114 deletions(-) delete mode 100644 Sources/_TestingInternals/Demangle.cpp diff --git a/Sources/Testing/SourceAttribution/Backtrace+Symbolication.swift b/Sources/Testing/SourceAttribution/Backtrace+Symbolication.swift index 29deb6b3b..21b39dfed 100644 --- a/Sources/Testing/SourceAttribution/Backtrace+Symbolication.swift +++ b/Sources/Testing/SourceAttribution/Backtrace+Symbolication.swift @@ -13,24 +13,6 @@ private import _TestingInternals /// A type representing a backtrace or stack trace. @_spi(Experimental) @_spi(ForToolsIntegrationOnly) extension Backtrace { - /// Demangle a symbol name. - /// - /// - Parameters: - /// - mangledSymbolName: The symbol name to demangle. - /// - /// - Returns: The demangled form of `mangledSymbolName` according to the - /// Swift standard library or the platform's C++ standard library, or `nil` - /// if the symbol name could not be demangled. - private static func _demangle(_ mangledSymbolName: String) -> String? { - guard let demangledSymbolName = swt_copyDemangledSymbolName(mangledSymbolName) else { - return nil - } - defer { - free(demangledSymbolName) - } - return String(validatingCString: demangledSymbolName) - } - /// An enumeration describing the symbolication mode to use when handling /// events containing backtraces. public enum SymbolicationMode: Sendable { @@ -38,8 +20,10 @@ extension Backtrace { /// performed. case mangled - /// The backtrace should be symbolicated and Swift and C++ symbols should be + /// The backtrace should be symbolicated and Swift symbols should be /// demangled if possible. + /// + /// "Foreign" symbol names such as those produced by C++ are not demangled. case demangled } @@ -82,12 +66,8 @@ extension Backtrace { var info = Dl_info() if 0 != dladdr(UnsafePointer(bitPattern: UInt(clamping: address)), &info) { let offset = address - Address(clamping: UInt(bitPattern: info.dli_saddr)) - if var symbolName = info.dli_sname.flatMap(String.init(validatingCString:)) { - if mode != .mangled { - symbolName = Self._demangle(symbolName) ?? symbolName - } - result[i] = SymbolicatedAddress(address: address, offset: offset, symbolName: symbolName) - } + let symbolName = info.dli_sname.flatMap(String.init(validatingCString:)) + result[i] = SymbolicatedAddress(address: address, offset: offset, symbolName: symbolName) } } #elseif os(Linux) @@ -109,11 +89,8 @@ extension Backtrace { symbolInfo.pointee.si.SizeOfStruct = ULONG(MemoryLayout.stride) symbolInfo.pointee.si.MaxNameLen = ULONG(MAX_SYM_NAME) var displacement = DWORD64(0) - if SymFromAddrW(hProcess, DWORD64(clamping: address), &displacement, symbolInfo.pointer(to: \.si)!), - var symbolName = String.decodeCString(symbolInfo.pointer(to: \.si.Name)!, as: UTF16.self)?.result { - if mode != .mangled { - symbolName = Self._demangle(symbolName) ?? symbolName - } + if SymFromAddrW(hProcess, DWORD64(clamping: address), &displacement, symbolInfo.pointer(to: \.si)!) { + let symbolName = String.decodeCString(symbolInfo.pointer(to: \.si.Name)!, as: UTF16.self)?.result result[i] = SymbolicatedAddress(address: address, offset: displacement, symbolName: symbolName) } } @@ -125,6 +102,16 @@ extension Backtrace { #warning("Platform-specific implementation missing: backtrace symbolication unavailable") #endif + if mode != .mangled { + result = result.map { symbolicatedAddress in + var symbolicatedAddress = symbolicatedAddress + if let demangledName = symbolicatedAddress.symbolName.flatMap(_demangle) { + symbolicatedAddress.symbolName = demangledName + } + return symbolicatedAddress + } + } + return result } } @@ -133,9 +120,29 @@ extension Backtrace { extension Backtrace.SymbolicatedAddress: Codable {} -#if os(Windows) -// MARK: - +// MARK: - Swift runtime wrappers + +/// Demangle a symbol name. +/// +/// - Parameters: +/// - mangledSymbolName: The symbol name to demangle. +/// +/// - Returns: The demangled form of `mangledSymbolName` according to the +/// Swift standard library or the platform's C++ standard library, or `nil` +/// if the symbol name could not be demangled. +private func _demangle(_ mangledSymbolName: String) -> String? { + mangledSymbolName.withCString { mangledSymbolName in + guard let demangledSymbolName = swift_demangle(mangledSymbolName, strlen(mangledSymbolName), nil, nil, 0) else { + return nil + } + defer { + free(demangledSymbolName) + } + return String(validatingCString: demangledSymbolName) + } +} +#if os(Windows) /// Configure the environment to allow calling into the Debug Help library. /// /// - Parameters: diff --git a/Sources/_TestingInternals/CMakeLists.txt b/Sources/_TestingInternals/CMakeLists.txt index 763bb9d24..b0bde88ed 100644 --- a/Sources/_TestingInternals/CMakeLists.txt +++ b/Sources/_TestingInternals/CMakeLists.txt @@ -11,7 +11,6 @@ set(CMAKE_CXX_SCAN_FOR_MODULES 0) include(LibraryVersion) include(TargetTriple) add_library(_TestingInternals STATIC - Demangle.cpp Discovery.cpp Versions.cpp WillThrow.cpp) diff --git a/Sources/_TestingInternals/Demangle.cpp b/Sources/_TestingInternals/Demangle.cpp deleted file mode 100644 index 2ee7b3f21..000000000 --- a/Sources/_TestingInternals/Demangle.cpp +++ /dev/null @@ -1,72 +0,0 @@ -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for Swift project authors -// - -#include "Demangle.h" - -#include -#include -#include - -#if defined(__APPLE__) || defined(__linux__) -#include -#elif defined(_WIN32) -#include -#pragma comment(lib, "DbgHelp.lib") -#endif - -SWT_IMPORT_FROM_STDLIB char *swift_demangle(const char *mangledName, size_t mangledNameLength, char *outputBuffer, size_t *outputBufferSize, uint32_t flags); - -char *swt_copyDemangledSymbolName(const char *mangledName) { - if (mangledName[0] == '\0') { - return nullptr; - } - - // First, try using Swift's demangler. - char *result = swift_demangle(mangledName, std::strlen(mangledName), nullptr, nullptr, 0); - - // Try using the platform's C++ demangler instead. - if (!result) { -#if defined(__APPLE__) || defined(__linux__) - int status = 0; - result = __cxxabiv1::__cxa_demangle(mangledName, nullptr, nullptr, &status); -#elif defined(_WIN32) - // std::type_info::raw_name() has a leading period that interferes with - // demangling. Strip it off if found. - if (mangledName[0] == '.') { - mangledName += 1; - } - - // MSVC++-style mangled names always start with '?'. - if (mangledName[0] != '?') { - return nullptr; - } - - // Allocate some space for the demangled type name. - static const constexpr size_t MAX_DEMANGLED_NAME_SIZE = 1024; - if (auto demangledName = reinterpret_cast(std::malloc(MAX_DEMANGLED_NAME_SIZE))) { - - // Call into DbgHelp to perform the demangling. These flags should - // give us the correct demangling of a mangled C++ type name. - DWORD undecorateFlags = UNDNAME_NAME_ONLY | UNDNAME_NO_ARGUMENTS; -#if !defined(_WIN64) - undecorateFlags |= UNDNAME_32_BIT_DECODE; -#endif - if (UnDecorateSymbolName(mangledName, demangledName, MAX_DEMANGLED_NAME_SIZE, undecorateFlags)) { - result = demangledName; - } else { - // Don't leak the allocated buffer if demangling failed. - std::free(demangledName); - } - } -#endif - } - - return result; -} diff --git a/Sources/_TestingInternals/include/Demangle.h b/Sources/_TestingInternals/include/Demangle.h index 4f6b964bc..682bd9449 100644 --- a/Sources/_TestingInternals/include/Demangle.h +++ b/Sources/_TestingInternals/include/Demangle.h @@ -18,18 +18,26 @@ SWT_ASSUME_NONNULL_BEGIN -/// Attempt to demangle the given symbol name. +/// Demangle a Swift symbol name. /// /// - Parameters: -/// - symbolName: The symbol name to demangle. +/// - mangledName: A pointer to the mangled symbol name to demangle. +/// - mangledNameLength: The length of `mangledName` in bytes, not including +/// any trailing null byte. +/// - outputBuffer: Unused by the testing library. Pass `nullptr`. +/// - outputBufferSize: Unused by the testing library. Pass `nullptr`. +/// - flags: Unused by the testing library. Pass `0`. /// -/// - Returns: The demangled form of `symbolName`, or `nullptr` if it could not -/// be demangled. -/// -/// On Windows, this function must only be called from within a call to -/// `_swift_win32_withDbgHelpLibrary()`. Note that Swift symbol demangling may -/// internally use `operator new`. -SWT_EXTERN char *_Nullable swt_copyDemangledSymbolName(const char *symbolName); +/// - Returns: The demangled form of `mangledName`, or `nullptr` if demangling +/// failed. The caller is responsible for freeing this string with `free()` +/// when done. +SWT_IMPORT_FROM_STDLIB char *_Nullable swift_demangle( + const char *mangledName, + size_t mangledNameLength, + char *_Nullable outputBuffer, + size_t *_Nullable outputBufferSize, + uint32_t flags +); #if defined(_WIN32) /// Configure the environment to allow calling into the Debug Help library. From c2e2df44c221c222ac400f9a732b1718b2013cc4 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Wed, 11 Sep 2024 17:50:38 -0400 Subject: [PATCH 5/5] Fix up header includes --- Sources/_TestingInternals/include/Demangle.h | 2 -- Sources/_TestingInternals/include/Discovery.h | 3 +-- Sources/_TestingInternals/include/Includes.h | 3 +++ 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/_TestingInternals/include/Demangle.h b/Sources/_TestingInternals/include/Demangle.h index 682bd9449..aa5f9fb1d 100644 --- a/Sources/_TestingInternals/include/Demangle.h +++ b/Sources/_TestingInternals/include/Demangle.h @@ -14,8 +14,6 @@ #include "Defines.h" #include "Includes.h" -#include - SWT_ASSUME_NONNULL_BEGIN /// Demangle a Swift symbol name. diff --git a/Sources/_TestingInternals/include/Discovery.h b/Sources/_TestingInternals/include/Discovery.h index 56dd4db0e..f15ad1b69 100644 --- a/Sources/_TestingInternals/include/Discovery.h +++ b/Sources/_TestingInternals/include/Discovery.h @@ -12,8 +12,7 @@ #define SWT_DISCOVERY_H #include "Defines.h" - -#include +#include "Includes.h" SWT_ASSUME_NONNULL_BEGIN diff --git a/Sources/_TestingInternals/include/Includes.h b/Sources/_TestingInternals/include/Includes.h index fe0c67adf..420a8c745 100644 --- a/Sources/_TestingInternals/include/Includes.h +++ b/Sources/_TestingInternals/include/Includes.h @@ -27,6 +27,7 @@ /// - Note: Avoid including headers that aren't actually used. #include +#include /// Guard against including `signal.h` on WASI. The `signal.h` header file /// itself is available in wasi-libc, but it's just a stub that doesn't actually /// do anything. And also including it requires a special macro definition @@ -34,6 +35,8 @@ #if __has_include() && !defined(__wasi__) #include #endif +#include +#include #include #include #include