From fd3c76f36ebc759f11116ed39a954c09957e4112 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Tue, 1 Apr 2025 17:34:23 -0400 Subject: [PATCH 1/3] Replace CRC-32 with SHA-256 in our macro target This PR replaces CRC-32 with SHA-256 in our macro target. We currently use CRC-32 to disambiguate test functions that would otherwise generate identical derived symbol names (using `context.makeUniqueName()`.) By replacing it with the first 64 bits of a SHA-256 hash, we reduce the odds of a collision. We also currently use a 128-bit random number as a unique ID for exit tests during macro expansion (which is as unique as it gets, but unstable.) Replacing this random number with half of a SHA-256 hash allows us to generate _stable_ IDs (that are still statistically unique) which can help when debugging an exit test and may also improve cache quality for tools (e.g. an IDE's symbol cache.) Because there is no SHA-256 implementation available in the Swift standard library or other components we can reliably link to in the macro target, I've borrowed the implementation of SHA-256 in swift-tools-support-core. The original version is [here](https://github.com/swiftlang/swift-tools-support-core/blob/main/Sources/TSCBasic/HashAlgorithms.swift). --- Sources/Testing/ExitTests/ExitTest.swift | 14 +- Sources/TestingMacros/CMakeLists.txt | 2 +- Sources/TestingMacros/ConditionMacro.swift | 45 ++++- .../MacroExpansionContextAdditions.swift | 8 +- Sources/TestingMacros/Support/CRC32.swift | 74 ------- Sources/TestingMacros/Support/SHA256.swift | 184 ++++++++++++++++++ 6 files changed, 242 insertions(+), 85 deletions(-) delete mode 100644 Sources/TestingMacros/Support/CRC32.swift create mode 100644 Sources/TestingMacros/Support/SHA256.swift diff --git a/Sources/Testing/ExitTests/ExitTest.swift b/Sources/Testing/ExitTests/ExitTest.swift index bd5cb95b9..a38c7592e 100644 --- a/Sources/Testing/ExitTests/ExitTest.swift +++ b/Sources/Testing/ExitTests/ExitTest.swift @@ -35,10 +35,20 @@ private import _TestingInternals #endif public struct ExitTest: Sendable, ~Copyable { /// A type whose instances uniquely identify instances of ``ExitTest``. + /// + /// An instance of this type uniquely identifies an exit test within the + /// context of the current test target. You can get an exit test's unique + /// identifier from its ``id`` property. + /// + /// The encoded form of an instance of this type is subject to change over + /// time. Instances of this type are only guaranteed to be decodable by the + /// same version of the testing library that encoded them. @_spi(ForToolsIntegrationOnly) public struct ID: Sendable, Equatable, Codable { - /// An underlying UUID (stored as two `UInt64` values to avoid relying on - /// `UUID` from Foundation or any platform-specific interfaces.) + /// Storage for the underlying bits of the ID. + /// + /// - Note: On Apple platforms, we deploy to OS versions that do not include + /// support for `UInt128`, so we use two `UInt64`s for storage instead. private var _lo: UInt64 private var _hi: UInt64 diff --git a/Sources/TestingMacros/CMakeLists.txt b/Sources/TestingMacros/CMakeLists.txt index b0d809665..72184f94b 100644 --- a/Sources/TestingMacros/CMakeLists.txt +++ b/Sources/TestingMacros/CMakeLists.txt @@ -99,9 +99,9 @@ target_sources(TestingMacros PRIVATE Support/AvailabilityGuards.swift Support/CommentParsing.swift Support/ConditionArgumentParsing.swift - Support/CRC32.swift Support/DiagnosticMessage.swift Support/DiagnosticMessage+Diagnosing.swift + Support/SHA256.swift Support/SourceCodeCapturing.swift Support/SourceLocationGeneration.swift Support/TestContentGeneration.swift diff --git a/Sources/TestingMacros/ConditionMacro.swift b/Sources/TestingMacros/ConditionMacro.swift index f07fad91f..a974e3f50 100644 --- a/Sources/TestingMacros/ConditionMacro.swift +++ b/Sources/TestingMacros/ConditionMacro.swift @@ -8,6 +8,7 @@ // See https://swift.org/CONTRIBUTORS.txt for Swift project authors // +import SwiftParser public import SwiftSyntax import SwiftSyntaxBuilder public import SwiftSyntaxMacros @@ -446,10 +447,6 @@ extension ExitTestConditionMacro { context.diagnose(.captureClauseUnsupported(captureClause, in: closureExpr, inExitTest: macro)) } - // TODO: use UUID() here if we can link to Foundation - let exitTestID = (UInt64.random(in: 0 ... .max), UInt64.random(in: 0 ... .max)) - let exitTestIDExpr: ExprSyntax = "(\(literal: exitTestID.0), \(literal: exitTestID.1))" - var decls = [DeclSyntax]() // Implement the body of the exit test outside the enum we're declaring so @@ -494,7 +491,7 @@ extension ExitTestConditionMacro { enum \(enumName) { private nonisolated static let accessor: Testing.__TestContentRecordAccessor = { outValue, type, hint, _ in Testing.ExitTest.__store( - \(exitTestIDExpr), + \(_makeExitTestIDExpr(for: macro, in: context)), \(bodyThunkName), into: outValue, asTypeAt: type, @@ -541,6 +538,44 @@ extension ExitTestConditionMacro { return try Base.expansion(of: macro, primaryExpression: bodyArgumentExpr, in: context) } + + /// Make an expression representing an exit test ID that can be passed to the + /// `ExitTest.__store()` function at runtime. + /// + /// - Parameters: + /// - macro: The exit test macro being inspected. + /// - context: The macro context in which the expression is being parsed. + /// + /// - Returns: An expression representing the exit test's unique ID. + private static func _makeExitTestIDExpr( + for macro: some FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext + ) -> ExprSyntax { + let exitTestID: (UInt64, UInt64) + if let sourceLocation = context.location(of: macro, at: .afterLeadingTrivia, filePathMode: .fileID), + let fileID = sourceLocation.file.as(StringLiteralExprSyntax.self)?.representedLiteralValue, + let line = sourceLocation.line.as(IntegerLiteralExprSyntax.self)?.representedLiteralValue, + let column = sourceLocation.column.as(IntegerLiteralExprSyntax.self)?.representedLiteralValue { + // Hash the entire source location and store as many bits as possible in + // the resulting ID. + let stringValue = "\(fileID):\(line):\(column)" + exitTestID = SHA256.hash(stringValue.utf8).withUnsafeBytes { sha256 in + sha256.loadUnaligned(as: (UInt64, UInt64).self) + } + } else { + // This branch is dead code in production, but is used when we expand a + // macro in our own unit tests because the macro expansion context does + // not have real source location information. + exitTestID.0 = .random(in: 0 ... .max) + exitTestID.1 = .random(in: 0 ... .max) + } + + // Return a tuple of integer literals (which is what the runtime __store() + // function is expecting.) + return """ + (\(IntegerLiteralExprSyntax(exitTestID.0, radix: .hex)), \(IntegerLiteralExprSyntax(exitTestID.1, radix: .hex))) + """ + } } /// A type describing the expansion of the `#expect(exitsWith:)` macro. diff --git a/Sources/TestingMacros/Support/Additions/MacroExpansionContextAdditions.swift b/Sources/TestingMacros/Support/Additions/MacroExpansionContextAdditions.swift index 3b31caf72..322a84f3a 100644 --- a/Sources/TestingMacros/Support/Additions/MacroExpansionContextAdditions.swift +++ b/Sources/TestingMacros/Support/Additions/MacroExpansionContextAdditions.swift @@ -63,10 +63,12 @@ extension MacroExpansionContext { .tokens(viewMode: .fixedUp) .map(\.textWithoutBackticks) .joined() - let crcValue = crc32(identifierCharacters.utf8) - let suffix = String(crcValue, radix: 16, uppercase: false) + let hashValue = SHA256.hash(identifierCharacters.utf8).withUnsafeBytes { sha256 in + sha256.loadUnaligned(as: UInt64.self) + } + let suffix = String(hashValue, radix: 16, uppercase: false) - // If the caller did not specify a prefix and the CRC32 value starts with a + // If the caller did not specify a prefix and the hash value starts with a // digit, include a single-character prefix to ensure that Swift's name // demangling still works correctly. var prefix = prefix diff --git a/Sources/TestingMacros/Support/CRC32.swift b/Sources/TestingMacros/Support/CRC32.swift deleted file mode 100644 index e58c4c7f7..000000000 --- a/Sources/TestingMacros/Support/CRC32.swift +++ /dev/null @@ -1,74 +0,0 @@ -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2023 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 -// - -/// The precomputed CRC-32 lookup table. -/// -/// This table is used by the ``crc32(_:)`` function below. It is borrowed from -/// the [Swift standard library](https://github.com/swiftlang/swift/blob/main/stdlib/public/Backtracing/Elf.swift). -private let _crc32Table: [UInt32] = [ - 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, - 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, - 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, - 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, - 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, - 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, - 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, - 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, - 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, - 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, - 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, - 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, - 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, - 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, - 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, - 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, - 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, - 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, - 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, - 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, - 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, - 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, - 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, - 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, - 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, - 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, - 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, - 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, - 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, - 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, - 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, - 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, - 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, - 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, - 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, - 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, - 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, - 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, - 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, - 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, - 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, - 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, - 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d -] - -/// Compute the CRC-32 code for a sequence of bytes. -/// -/// - Parameters: -/// - bytes: The bytes for which a CRC-32 code should be computed. -/// -/// - Returns: The CRC-32 code computed for `bytes`. -/// -/// A starting value of `0` is assumed. This function is adapted from the -/// [Swift standard library](https://github.com/swiftlang/swift/blob/main/stdlib/public/Backtracing/Elf.swift). -func crc32(_ bytes: some Sequence) -> UInt32 { - ~bytes.reduce(~0) { crcValue, byte in - _crc32Table[Int(UInt8(truncatingIfNeeded: crcValue) ^ byte)] ^ (crcValue >> 8) - } -} diff --git a/Sources/TestingMacros/Support/SHA256.swift b/Sources/TestingMacros/Support/SHA256.swift new file mode 100644 index 000000000..6e398eb98 --- /dev/null +++ b/Sources/TestingMacros/Support/SHA256.swift @@ -0,0 +1,184 @@ +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014–2025 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 +// + +/// The contents of this file were copied more-or-less verbatim from +/// [swift-tools-support-core](https://github.com/swiftlang/swift-tools-support-core/blob/main/Sources/TSCBasic/HashAlgorithms.swift). + +/// SHA-256 implementation from Secure Hash Algorithm 2 (SHA-2) set of +/// cryptographic hash functions (FIPS PUB 180-2). +enum SHA256 { + /// The length of the output digest (in bits). + private static let _digestLength = 256 + + /// The size of each blocks (in bits). + private static let _blockBitSize = 512 + + /// The initial hash value. + private static let _initialHashValue: [UInt32] = [ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 + ] + + /// The constants in the algorithm (K). + private static let _konstants: [UInt32] = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + ] + + public static func hash(_ bytes: some Sequence) -> [UInt8] { + var input = Array(bytes) + + // Pad the input. + _pad(&input) + + // Break the input into N 512-bit blocks. + let messageBlocks = input.blocks(size: _blockBitSize / 8) + + /// The hash that is being computed. + var hash = _initialHashValue + + // Process each block. + for block in messageBlocks { + _process(block, hash: &hash) + } + + // Finally, compute the result. + var result = [UInt8](repeating: 0, count: _digestLength / 8) + for (idx, element) in hash.enumerated() { + let pos = idx * 4 + result[pos + 0] = UInt8((element >> 24) & 0xff) + result[pos + 1] = UInt8((element >> 16) & 0xff) + result[pos + 2] = UInt8((element >> 8) & 0xff) + result[pos + 3] = UInt8(element & 0xff) + } + + return result + } + + /// Process and compute hash from a block. + private static func _process(_ block: ArraySlice, hash: inout [UInt32]) { + + // Compute message schedule. + var W = [UInt32](repeating: 0, count: _konstants.count) + for t in 0..> 10) + let σ0 = W[t-15].rotateRight(by: 7) ^ W[t-15].rotateRight(by: 18) ^ (W[t-15] >> 3) + W[t] = σ1 &+ W[t-7] &+ σ0 &+ W[t-16] + } + } + + var a = hash[0] + var b = hash[1] + var c = hash[2] + var d = hash[3] + var e = hash[4] + var f = hash[5] + var g = hash[6] + var h = hash[7] + + // Run the main algorithm. + for t in 0..<_konstants.count { + let Σ1 = e.rotateRight(by: 6) ^ e.rotateRight(by: 11) ^ e.rotateRight(by: 25) + let ch = (e & f) ^ (~e & g) + let t1 = h &+ Σ1 &+ ch &+ _konstants[t] &+ W[t] + + let Σ0 = a.rotateRight(by: 2) ^ a.rotateRight(by: 13) ^ a.rotateRight(by: 22) + let maj = (a & b) ^ (a & c) ^ (b & c) + let t2 = Σ0 &+ maj + + h = g + g = f + f = e + e = d &+ t1 + d = c + c = b + b = a + a = t1 &+ t2 + } + + hash[0] = a &+ hash[0] + hash[1] = b &+ hash[1] + hash[2] = c &+ hash[2] + hash[3] = d &+ hash[3] + hash[4] = e &+ hash[4] + hash[5] = f &+ hash[5] + hash[6] = g &+ hash[6] + hash[7] = h &+ hash[7] + } + + /// Pad the given byte array to be a multiple of 512 bits. + private static func _pad(_ input: inout [UInt8]) { + // Find the bit count of input. + let inputBitLength = input.count * 8 + + // Append the bit 1 at end of input. + input.append(0x80) + + // Find the number of bits we need to append. + // + // inputBitLength + 1 + bitsToAppend ≡ 448 mod 512 + let mod = inputBitLength % 512 + let bitsToAppend = mod < 448 ? 448 - 1 - mod : 512 + 448 - mod - 1 + + // We already appended first 7 bits with 0x80 above. + input += [UInt8](repeating: 0, count: (bitsToAppend - 7) / 8) + + // We need to append 64 bits of input length. + for byte in UInt64(inputBitLength).toByteArray().lazy.reversed() { + input.append(byte) + } + assert((input.count * 8) % 512 == 0, "Expected padded length to be 512.") + } +} + +// MARK:- Helpers + +extension UInt64 { + /// Converts the 64 bit integer into an array of single byte integers. + fileprivate func toByteArray() -> [UInt8] { + var value = self.littleEndian + return withUnsafeBytes(of: &value, Array.init) + } +} + +extension UInt32 { + /// Rotates self by given amount. + fileprivate func rotateRight(by amount: UInt32) -> UInt32 { + return (self >> amount) | (self << (32 - amount)) + } +} + +extension Array { + /// Breaks the array into the given size. + fileprivate func blocks(size: Int) -> AnyIterator> { + var currentIndex = startIndex + return AnyIterator { + if let nextIndex = self.index(currentIndex, offsetBy: size, limitedBy: self.endIndex) { + defer { currentIndex = nextIndex } + return self[currentIndex.. Date: Wed, 2 Apr 2025 11:47:22 -0400 Subject: [PATCH 2/3] D'oh --- Sources/TestingMacros/ConditionMacro.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/TestingMacros/ConditionMacro.swift b/Sources/TestingMacros/ConditionMacro.swift index a974e3f50..c82acd725 100644 --- a/Sources/TestingMacros/ConditionMacro.swift +++ b/Sources/TestingMacros/ConditionMacro.swift @@ -447,6 +447,9 @@ extension ExitTestConditionMacro { context.diagnose(.captureClauseUnsupported(captureClause, in: closureExpr, inExitTest: macro)) } + // Generate a unique identifier for this exit test. + let idExpr = _makeExitTestIDExpr(for: macro, in: context) + var decls = [DeclSyntax]() // Implement the body of the exit test outside the enum we're declaring so @@ -491,7 +494,7 @@ extension ExitTestConditionMacro { enum \(enumName) { private nonisolated static let accessor: Testing.__TestContentRecordAccessor = { outValue, type, hint, _ in Testing.ExitTest.__store( - \(_makeExitTestIDExpr(for: macro, in: context)), + \(idExpr), \(bodyThunkName), into: outValue, asTypeAt: type, @@ -522,10 +525,7 @@ extension ExitTestConditionMacro { // Insert the exit test's ID as the first argument. Note that this will // invalidate all indices into `arguments`! arguments.insert( - Argument( - label: "identifiedBy", - expression: exitTestIDExpr - ), + Argument(label: "identifiedBy", expression: idExpr), at: arguments.startIndex ) From c127ef17f731e5fcc02e0ae43988068069018013 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Thu, 3 Apr 2025 12:47:46 -0400 Subject: [PATCH 3/3] Be explicit about the commit we got our SHA-256 impl from --- Sources/TestingMacros/Support/SHA256.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/TestingMacros/Support/SHA256.swift b/Sources/TestingMacros/Support/SHA256.swift index 6e398eb98..a4d88a801 100644 --- a/Sources/TestingMacros/Support/SHA256.swift +++ b/Sources/TestingMacros/Support/SHA256.swift @@ -9,7 +9,7 @@ // /// The contents of this file were copied more-or-less verbatim from -/// [swift-tools-support-core](https://github.com/swiftlang/swift-tools-support-core/blob/main/Sources/TSCBasic/HashAlgorithms.swift). +/// [swift-tools-support-core](https://github.com/swiftlang/swift-tools-support-core/blob/add9e1518ac37a8e52b7612d3eb2f009ae8f6ce8/Sources/TSCBasic/HashAlgorithms.swift). /// SHA-256 implementation from Secure Hash Algorithm 2 (SHA-2) set of /// cryptographic hash functions (FIPS PUB 180-2).