From ae42dd5b193035f2ab388552dd24c3e4a2eff672 Mon Sep 17 00:00:00 2001 From: Himanshu Kumar <7786778+hi-kumar@users.noreply.github.com> Date: Wed, 16 Jul 2025 22:48:00 -0700 Subject: [PATCH] Address Sanitizer options in run/test schemes (#1550) * Expose address sanitizer flags in run and test BuildActions in Schemes * Update testJSONEncodable to test the new fields * Also test the asan setting values for run scheme * Update changelog --------- Co-authored-by: Yonas Kolb --- CHANGELOG.md | 3 + Sources/ProjectSpec/Scheme.swift | 74 ++++++++++++++++++- Sources/XcodeGenKit/SchemeGenerator.swift | 8 ++ Tests/ProjectSpecTests/ProjectSpecTests.swift | 8 ++ 4 files changed, 92 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3ed1916..ddfbace2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Next Version +### Added +- Added sanitizer options to run and test actions in Scheme #1550 @hi-kumar + ### Fixed - Added validation to ensure that all values in `settings.configs` are mappings. Previously, passing non-mapping values did not raise an error, making it difficult to detect misconfigurations. Now, `SpecParsingError.invalidConfigsMappingFormat` is thrown if misused. #1547 @Ryu0118 diff --git a/Sources/ProjectSpec/Scheme.swift b/Sources/ProjectSpec/Scheme.swift index e417fcf0..a83dff58 100644 --- a/Sources/ProjectSpec/Scheme.swift +++ b/Sources/ProjectSpec/Scheme.swift @@ -127,6 +127,10 @@ public struct Scheme: Equatable { } public struct Run: BuildAction { + public static let enableAddressSanitizerDefault = false + public static let enableASanStackUseAfterReturnDefault = false + public static let enableThreadSanitizerDefault = false + public static let enableUBSanitizerDefault = false public static let disableMainThreadCheckerDefault = false public static let stopOnEveryMainThreadCheckerIssueDefault = false public static let disableThreadPerformanceCheckerDefault = false @@ -140,6 +144,10 @@ public struct Scheme: Equatable { public var environmentVariables: [XCScheme.EnvironmentVariable] public var enableGPUFrameCaptureMode: XCScheme.LaunchAction.GPUFrameCaptureMode public var enableGPUValidationMode: Bool + public var enableAddressSanitizer: Bool + public var enableASanStackUseAfterReturn: Bool + public var enableThreadSanitizer: Bool + public var enableUBSanitizer: Bool public var disableMainThreadChecker: Bool public var stopOnEveryMainThreadCheckerIssue: Bool public var disableThreadPerformanceChecker: Bool @@ -163,6 +171,10 @@ public struct Scheme: Equatable { environmentVariables: [XCScheme.EnvironmentVariable] = [], enableGPUFrameCaptureMode: XCScheme.LaunchAction.GPUFrameCaptureMode = XCScheme.LaunchAction.defaultGPUFrameCaptureMode, enableGPUValidationMode: Bool = enableGPUValidationModeDefault, + enableAddressSanitizer: Bool = enableAddressSanitizerDefault, + enableASanStackUseAfterReturn: Bool = enableASanStackUseAfterReturnDefault, + enableThreadSanitizer: Bool = enableThreadSanitizerDefault, + enableUBSanitizer: Bool = enableUBSanitizerDefault, disableMainThreadChecker: Bool = disableMainThreadCheckerDefault, stopOnEveryMainThreadCheckerIssue: Bool = stopOnEveryMainThreadCheckerIssueDefault, disableThreadPerformanceChecker: Bool = disableThreadPerformanceCheckerDefault, @@ -181,6 +193,10 @@ public struct Scheme: Equatable { self.preActions = preActions self.postActions = postActions self.environmentVariables = environmentVariables + self.enableAddressSanitizer = enableAddressSanitizer + self.enableASanStackUseAfterReturn = enableASanStackUseAfterReturn + self.enableThreadSanitizer = enableThreadSanitizer + self.enableUBSanitizer = enableUBSanitizer self.disableMainThreadChecker = disableMainThreadChecker self.enableGPUFrameCaptureMode = enableGPUFrameCaptureMode self.enableGPUValidationMode = enableGPUValidationMode @@ -200,6 +216,10 @@ public struct Scheme: Equatable { public struct Test: BuildAction { public static let gatherCoverageDataDefault = false + public static let enableAddressSanitizerDefault = false + public static let enableASanStackUseAfterReturnDefault = false + public static let enableThreadSanitizerDefault = false + public static let enableUBSanitizerDefault = false public static let disableMainThreadCheckerDefault = false public static let debugEnabledDefault = true public static let captureScreenshotsAutomaticallyDefault = true @@ -209,6 +229,10 @@ public struct Scheme: Equatable { public var config: String? public var gatherCoverageData: Bool public var coverageTargets: [TestableTargetReference] + public var enableAddressSanitizer: Bool + public var enableASanStackUseAfterReturn: Bool + public var enableThreadSanitizer: Bool + public var enableUBSanitizer: Bool public var disableMainThreadChecker: Bool public var commandLineArguments: [String: Bool] public var targets: [TestTarget] @@ -276,6 +300,10 @@ public struct Scheme: Equatable { config: String? = nil, gatherCoverageData: Bool = gatherCoverageDataDefault, coverageTargets: [TestableTargetReference] = [], + enableAddressSanitizer: Bool = enableAddressSanitizerDefault, + enableASanStackUseAfterReturn: Bool = enableASanStackUseAfterReturnDefault, + enableThreadSanitizer: Bool = enableThreadSanitizerDefault, + enableUBSanitizer: Bool = enableUBSanitizerDefault, disableMainThreadChecker: Bool = disableMainThreadCheckerDefault, randomExecutionOrder: Bool = false, parallelizable: Bool = false, @@ -297,6 +325,10 @@ public struct Scheme: Equatable { self.config = config self.gatherCoverageData = gatherCoverageData self.coverageTargets = coverageTargets + self.enableAddressSanitizer = enableAddressSanitizer + self.enableASanStackUseAfterReturn = enableASanStackUseAfterReturn + self.enableThreadSanitizer = enableThreadSanitizer + self.enableUBSanitizer = enableUBSanitizer self.disableMainThreadChecker = disableMainThreadChecker self.commandLineArguments = commandLineArguments self.targets = targets @@ -500,6 +532,10 @@ extension Scheme.Run: JSONObjectConvertible { } else { enableGPUValidationMode = jsonDictionary.json(atKeyPath: "enableGPUValidationMode") ?? Scheme.Run.enableGPUValidationModeDefault } + enableAddressSanitizer = jsonDictionary.json(atKeyPath: "enableAddressSanitizer") ?? Scheme.Run.enableAddressSanitizerDefault + enableASanStackUseAfterReturn = jsonDictionary.json(atKeyPath: "enableASanStackUseAfterReturn") ?? Scheme.Run.enableASanStackUseAfterReturnDefault + enableThreadSanitizer = jsonDictionary.json(atKeyPath: "enableThreadSanitizer") ?? Scheme.Run.enableThreadSanitizerDefault + enableUBSanitizer = jsonDictionary.json(atKeyPath: "enableUBSanitizer") ?? Scheme.Run.enableUBSanitizerDefault disableMainThreadChecker = jsonDictionary.json(atKeyPath: "disableMainThreadChecker") ?? Scheme.Run.disableMainThreadCheckerDefault stopOnEveryMainThreadCheckerIssue = jsonDictionary.json(atKeyPath: "stopOnEveryMainThreadCheckerIssue") ?? Scheme.Run.stopOnEveryMainThreadCheckerIssueDefault disableThreadPerformanceChecker = jsonDictionary.json(atKeyPath: "disableThreadPerformanceChecker") ?? Scheme.Run.disableThreadPerformanceCheckerDefault @@ -550,6 +586,22 @@ extension Scheme.Run: JSONEncodable { dict["enableGPUValidationMode"] = enableGPUValidationMode } + if enableAddressSanitizer != Scheme.Run.enableAddressSanitizerDefault { + dict["enableAddressSanitizer"] = enableAddressSanitizer + } + + if enableASanStackUseAfterReturn != Scheme.Run.enableASanStackUseAfterReturnDefault { + dict["enableASanStackUseAfterReturn"] = enableASanStackUseAfterReturn + } + + if enableThreadSanitizer != Scheme.Run.enableThreadSanitizerDefault { + dict["enableThreadSanitizer"] = enableThreadSanitizer + } + + if enableUBSanitizer != Scheme.Run.enableUBSanitizerDefault { + dict["enableUBSanitizer"] = enableUBSanitizer + } + if disableMainThreadChecker != Scheme.Run.disableMainThreadCheckerDefault { dict["disableMainThreadChecker"] = disableMainThreadChecker } @@ -608,7 +660,11 @@ extension Scheme.Test: JSONObjectConvertible { } else { coverageTargets = [] } - + + enableAddressSanitizer = jsonDictionary.json(atKeyPath: "enableAddressSanitizer") ?? Scheme.Test.enableAddressSanitizerDefault + enableASanStackUseAfterReturn = jsonDictionary.json(atKeyPath: "enableASanStackUseAfterReturn") ?? Scheme.Test.enableASanStackUseAfterReturnDefault + enableThreadSanitizer = jsonDictionary.json(atKeyPath: "enableThreadSanitizer") ?? Scheme.Test.enableThreadSanitizerDefault + enableUBSanitizer = jsonDictionary.json(atKeyPath: "enableUBSanitizer") ?? Scheme.Test.enableUBSanitizerDefault disableMainThreadChecker = jsonDictionary.json(atKeyPath: "disableMainThreadChecker") ?? Scheme.Test.disableMainThreadCheckerDefault commandLineArguments = jsonDictionary.json(atKeyPath: "commandLineArguments") ?? [:] if let targets = jsonDictionary["targets"] as? [Any] { @@ -659,6 +715,22 @@ extension Scheme.Test: JSONEncodable { dict["gatherCoverageData"] = gatherCoverageData } + if enableAddressSanitizer != Scheme.Test.enableAddressSanitizerDefault { + dict["enableAddressSanitizer"] = enableAddressSanitizer + } + + if enableASanStackUseAfterReturn != Scheme.Test.enableASanStackUseAfterReturnDefault { + dict["enableASanStackUseAfterReturn"] = enableASanStackUseAfterReturn + } + + if enableThreadSanitizer != Scheme.Test.enableThreadSanitizerDefault { + dict["enableThreadSanitizer"] = enableThreadSanitizer + } + + if enableUBSanitizer != Scheme.Test.enableUBSanitizerDefault { + dict["enableUBSanitizer"] = enableUBSanitizer + } + if disableMainThreadChecker != Scheme.Test.disableMainThreadCheckerDefault { dict["disableMainThreadChecker"] = disableMainThreadChecker } diff --git a/Sources/XcodeGenKit/SchemeGenerator.swift b/Sources/XcodeGenKit/SchemeGenerator.swift index 69ad80fa..8cb89cb4 100644 --- a/Sources/XcodeGenKit/SchemeGenerator.swift +++ b/Sources/XcodeGenKit/SchemeGenerator.swift @@ -311,6 +311,10 @@ public class SchemeGenerator { codeCoverageEnabled: scheme.test?.gatherCoverageData ?? Scheme.Test.gatherCoverageDataDefault, codeCoverageTargets: coverageBuildableTargets, onlyGenerateCoverageForSpecifiedTargets: !coverageBuildableTargets.isEmpty, + enableAddressSanitizer: scheme.test?.enableAddressSanitizer ?? Scheme.Test.enableAddressSanitizerDefault, + enableASanStackUseAfterReturn: scheme.test?.enableASanStackUseAfterReturn ?? Scheme.Test.enableASanStackUseAfterReturnDefault, + enableThreadSanitizer: scheme.test?.enableThreadSanitizer ?? Scheme.Test.enableThreadSanitizerDefault, + enableUBSanitizer: scheme.test?.enableUBSanitizer ?? Scheme.Test.enableUBSanitizerDefault, disableMainThreadChecker: scheme.test?.disableMainThreadChecker ?? Scheme.Test.disableMainThreadCheckerDefault, commandlineArguments: testCommandLineArgs, environmentVariables: testVariables, @@ -359,6 +363,10 @@ public class SchemeGenerator { locationScenarioReference: locationScenarioReference, enableGPUFrameCaptureMode: scheme.run?.enableGPUFrameCaptureMode ?? XCScheme.LaunchAction.defaultGPUFrameCaptureMode, disableGPUValidationMode: !(scheme.run?.enableGPUValidationMode ?? Scheme.Run.enableGPUValidationModeDefault), + enableAddressSanitizer: scheme.run?.enableAddressSanitizer ?? Scheme.Run.enableAddressSanitizerDefault, + enableASanStackUseAfterReturn: scheme.run?.enableASanStackUseAfterReturn ?? Scheme.Run.enableASanStackUseAfterReturnDefault, + enableThreadSanitizer: scheme.run?.enableThreadSanitizer ?? Scheme.Run.enableThreadSanitizerDefault, + enableUBSanitizer: scheme.run?.enableUBSanitizer ?? Scheme.Run.enableUBSanitizerDefault, disableMainThreadChecker: scheme.run?.disableMainThreadChecker ?? Scheme.Run.disableMainThreadCheckerDefault, disablePerformanceAntipatternChecker: scheme.run?.disableThreadPerformanceChecker ?? Scheme.Run.disableThreadPerformanceCheckerDefault, stopOnEveryMainThreadCheckerIssue: scheme.run?.stopOnEveryMainThreadCheckerIssue ?? Scheme.Run.stopOnEveryMainThreadCheckerIssueDefault, diff --git a/Tests/ProjectSpecTests/ProjectSpecTests.swift b/Tests/ProjectSpecTests/ProjectSpecTests.swift index 311597ad..3ae878ac 100644 --- a/Tests/ProjectSpecTests/ProjectSpecTests.swift +++ b/Tests/ProjectSpecTests/ProjectSpecTests.swift @@ -768,10 +768,18 @@ class ProjectSpecTests: XCTestCase { value: "bar", enabled: false)], enableGPUFrameCaptureMode: .openGL, + enableAddressSanitizer: true, + enableASanStackUseAfterReturn: true, + enableThreadSanitizer: true, + enableUBSanitizer: true, launchAutomaticallySubstyle: "2", storeKitConfiguration: "Configuration.storekit"), test: Scheme.Test(config: "Config", gatherCoverageData: true, + enableAddressSanitizer: true, + enableASanStackUseAfterReturn: true, + enableThreadSanitizer: true, + enableUBSanitizer: true, disableMainThreadChecker: true, randomExecutionOrder: false, parallelizable: false,