Skip to content

Commit 9518f52

Browse files
committed
Properly quote linker file list contents on Linux and Windows
1 parent d9c8077 commit 9518f52

File tree

9 files changed

+181
-8
lines changed

9 files changed

+181
-8
lines changed

Sources/SWBCore/Settings/BuiltinMacros.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1181,6 +1181,7 @@ public final class BuiltinMacros {
11811181
public static let _WRAPPER_PARENT_PATH = BuiltinMacros.declareStringMacro("_WRAPPER_PARENT_PATH")
11821182
public static let _WRAPPER_RESOURCES_DIR = BuiltinMacros.declareStringMacro("_WRAPPER_RESOURCES_DIR")
11831183
public static let __INPUT_FILE_LIST_PATH__ = BuiltinMacros.declarePathMacro("__INPUT_FILE_LIST_PATH__")
1184+
public static let LINKER_FILE_LIST_FORMAT = BuiltinMacros.declareEnumMacro("LINKER_FILE_LIST_FORMAT") as EnumMacroDeclaration<LinkerFileListFormat>
11841185
public static let SWIFT_RESPONSE_FILE_PATH = BuiltinMacros.declarePathMacro("SWIFT_RESPONSE_FILE_PATH")
11851186
public static let __ARCHS__ = BuiltinMacros.declareStringListMacro("__ARCHS__")
11861187

@@ -2425,6 +2426,7 @@ public final class BuiltinMacros {
24252426
_WRAPPER_PARENT_PATH,
24262427
_WRAPPER_RESOURCES_DIR,
24272428
__INPUT_FILE_LIST_PATH__,
2429+
LINKER_FILE_LIST_FORMAT,
24282430
__ARCHS__,
24292431
__SWIFT_MODULE_ONLY_ARCHS__,
24302432
arch,
@@ -2870,6 +2872,13 @@ public enum StripStyle: String, Equatable, Hashable, EnumerationMacroType {
28702872
case debugging
28712873
}
28722874

2875+
public enum LinkerFileListFormat: String, Equatable, Hashable, EnumerationMacroType {
2876+
public static let defaultValue = Self.unescapedNewlineSeparated
2877+
2878+
case unescapedNewlineSeparated
2879+
case shellQuotedNewlineSeparated
2880+
}
2881+
28732882
public enum MergedBinaryType: String, Equatable, Hashable, EnumerationMacroType {
28742883
public static let defaultValue = Self.none
28752884

Sources/SWBCore/SpecImplementations/LinkerSpec.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,20 @@ open class LinkerSpec : CommandLineToolSpec, @unchecked Sendable {
138138
return ruleInfo
139139
}
140140

141+
public func inputFileListContents(_ cbc: CommandBuildContext) -> ByteString {
142+
let contents = OutputByteStream()
143+
let escaper = UNIXShellCommandCodec(encodingStrategy: .singleQuotes, encodingBehavior: .argumentsOnly)
144+
for input in cbc.inputs {
145+
switch cbc.scope.evaluate(BuiltinMacros.LINKER_FILE_LIST_FORMAT) {
146+
case .unescapedNewlineSeparated:
147+
contents <<< input.absolutePath.strWithPosixSlashes <<< "\n"
148+
case .shellQuotedNewlineSeparated:
149+
contents <<< escaper.encode([input.absolutePath.strWithPosixSlashes]) <<< "\n"
150+
}
151+
}
152+
return contents.bytes
153+
}
154+
141155
open override func constructTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate) async {
142156
// FIXME: We should ensure this cannot happen.
143157
fatalError("unexpected direct invocation")

Sources/SWBCore/SpecImplementations/PropertyDomainSpec.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ private final class EnumBuildOptionType : BuildOptionType {
116116
return try namespace.declareEnumMacro(name) as EnumMacroDeclaration<LinkerDriverChoice>
117117
case "SWIFT_API_DIGESTER_MODE":
118118
return try namespace.declareEnumMacro(name) as EnumMacroDeclaration<SwiftAPIDigesterMode>
119+
case "LINKER_FILE_LIST_FORMAT":
120+
return try namespace.declareEnumMacro(name) as EnumMacroDeclaration<LinkerFileListFormat>
119121
default:
120122
return try namespace.declareStringMacro(name)
121123
}

Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -382,13 +382,8 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
382382
// Define the linker file list.
383383
let fileListPath = cbc.scope.evaluate(BuiltinMacros.__INPUT_FILE_LIST_PATH__, lookup: linkerDriverLookup)
384384
if !fileListPath.isEmpty {
385-
let contents = OutputByteStream()
386-
for input in cbc.inputs {
387-
// ld64 reads lines from the file using fgets, without doing any other processing.
388-
contents <<< input.absolutePath.strWithPosixSlashes <<< "\n"
389-
}
390385
let fileListPath = fileListPath
391-
cbc.producer.writeFileSpec.constructFileTasks(CommandBuildContext(producer: cbc.producer, scope: cbc.scope, inputs: [], output: fileListPath), delegate, contents: contents.bytes, permissions: nil, preparesForIndexing: false, additionalTaskOrderingOptions: [.immediate, .ignorePhaseOrdering])
386+
cbc.producer.writeFileSpec.constructFileTasks(CommandBuildContext(producer: cbc.producer, scope: cbc.scope, inputs: [], output: fileListPath), delegate, contents: inputFileListContents(cbc), permissions: nil, preparesForIndexing: false, additionalTaskOrderingOptions: [.immediate, .ignorePhaseOrdering])
392387
inputPaths.append(fileListPath)
393388
}
394389

@@ -1600,8 +1595,7 @@ public final class LibtoolLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @u
16001595
// Define the linker file list.
16011596
let fileListPath = cbc.scope.evaluate(BuiltinMacros.__INPUT_FILE_LIST_PATH__)
16021597
if !fileListPath.isEmpty {
1603-
let contents = cbc.inputs.map({ return $0.absolutePath.strWithPosixSlashes + "\n" }).joined(separator: "")
1604-
cbc.producer.writeFileSpec.constructFileTasks(CommandBuildContext(producer: cbc.producer, scope: cbc.scope, inputs: [], output: fileListPath), delegate, contents: ByteString(encodingAsUTF8: contents), permissions: nil, preparesForIndexing: false, additionalTaskOrderingOptions: [.immediate, .ignorePhaseOrdering])
1598+
cbc.producer.writeFileSpec.constructFileTasks(CommandBuildContext(producer: cbc.producer, scope: cbc.scope, inputs: [], output: fileListPath), delegate, contents: inputFileListContents(cbc), permissions: nil, preparesForIndexing: false, additionalTaskOrderingOptions: [.immediate, .ignorePhaseOrdering])
16051599
inputPaths.append(fileListPath)
16061600
}
16071601
} else {

Sources/SWBGenericUnixPlatform/Specs/UnixLd.xcspec

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,15 @@
180180
Name = "SWIFTC__INPUT_FILE_LIST_PATH__";
181181
Type = Path;
182182
Condition = "NO";
183+
},
184+
{
185+
Name = "LINKER_FILE_LIST_FORMAT";
186+
Type = Enumeration;
187+
Values = (
188+
unescapedNewlineSeparated,
189+
shellQuotedNewlineSeparated,
190+
);
191+
DefaultValue = shellQuotedNewlineSeparated;
183192
}
184193
);
185194
},

Sources/SWBGenericUnixPlatform/Specs/UnixLibtool.xcspec

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,15 @@
6868
Type = Boolean;
6969
DefaultValue = YES;
7070
},
71+
{
72+
Name = "LINKER_FILE_LIST_FORMAT";
73+
Type = Enumeration;
74+
Values = (
75+
unescapedNewlineSeparated,
76+
shellQuotedNewlineSeparated,
77+
);
78+
DefaultValue = shellQuotedNewlineSeparated;
79+
}
7180
);
7281
},
7382
)

Sources/SWBWindowsPlatform/Specs/WindowsLd.xcspec

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,15 @@
196196
Type = Path;
197197
Condition = "NO";
198198
},
199+
{
200+
Name = "LINKER_FILE_LIST_FORMAT";
201+
Type = Enumeration;
202+
Values = (
203+
unescapedNewlineSeparated,
204+
shellQuotedNewlineSeparated,
205+
);
206+
DefaultValue = shellQuotedNewlineSeparated;
207+
},
199208
{
200209
Name = "ALTERNATE_LINKER";
201210
Type = String;

Sources/SWBWindowsPlatform/Specs/WindowsLibtool.xcspec

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,15 @@
6363
);
6464
IsInputDependency = Yes;
6565
},
66+
{
67+
Name = "LINKER_FILE_LIST_FORMAT";
68+
Type = Enumeration;
69+
Values = (
70+
unescapedNewlineSeparated,
71+
shellQuotedNewlineSeparated,
72+
);
73+
DefaultValue = shellQuotedNewlineSeparated;
74+
},
6675
);
6776
},
6877
)

Tests/SWBBuildSystemTests/BuildOperationTests.swift

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,124 @@ fileprivate struct BuildOperationTests: CoreBasedTests {
382382
}
383383
}
384384

385+
@Test(.requireSDKs(.host))
386+
func commandLineTool_whitespaceEscaping() async throws {
387+
try await withTemporaryDirectory { (tmpDir: Path) in
388+
let tmpDir = tmpDir.join("has whitespace")
389+
let testProject = try await TestProject(
390+
"TestProject",
391+
sourceRoot: tmpDir,
392+
groupTree: TestGroup(
393+
"SomeFiles",
394+
children: [
395+
TestFile("main.swift"),
396+
TestFile("dynamic.swift"),
397+
TestFile("static.swift"),
398+
]),
399+
buildConfigurations: [
400+
TestBuildConfiguration("Debug", buildSettings: [
401+
"ARCHS": "$(ARCHS_STANDARD)",
402+
"CODE_SIGNING_ALLOWED": ProcessInfo.processInfo.hostOperatingSystem() == .macOS ? "YES" : "NO",
403+
"CODE_SIGN_IDENTITY": "-",
404+
"CODE_SIGN_ENTITLEMENTS": "Entitlements.plist",
405+
"DEFINES_MODULE": "YES",
406+
"PRODUCT_NAME": "$(TARGET_NAME)",
407+
"SDKROOT": "$(HOST_PLATFORM)",
408+
"SUPPORTED_PLATFORMS": "$(HOST_PLATFORM)",
409+
"SWIFT_VERSION": swiftVersion,
410+
"GCC_GENERATE_DEBUGGING_SYMBOLS": "YES",
411+
])
412+
],
413+
targets: [
414+
TestStandardTarget(
415+
"tool",
416+
type: .commandLineTool,
417+
buildConfigurations: [
418+
TestBuildConfiguration("Debug", buildSettings: [
419+
"LD_RUNPATH_SEARCH_PATHS": "@loader_path/",
420+
])
421+
],
422+
buildPhases: [
423+
TestSourcesBuildPhase(["main.swift"]),
424+
TestFrameworksBuildPhase([
425+
TestBuildFile(.target("dynamiclib")),
426+
TestBuildFile(.target("staticlib")),
427+
])
428+
],
429+
dependencies: [
430+
"dynamiclib",
431+
"staticlib",
432+
]
433+
),
434+
TestStandardTarget(
435+
"dynamiclib",
436+
type: .dynamicLibrary,
437+
buildConfigurations: [
438+
TestBuildConfiguration("Debug", buildSettings: [
439+
"DYLIB_INSTALL_NAME_BASE": "$ORIGIN",
440+
"DYLIB_INSTALL_NAME_BASE[sdk=macosx*]": "@rpath",
441+
442+
// FIXME: Find a way to make these default
443+
"EXECUTABLE_PREFIX": "lib",
444+
"EXECUTABLE_PREFIX[sdk=windows*]": "",
445+
])
446+
],
447+
buildPhases: [
448+
TestSourcesBuildPhase(["dynamic.swift"]),
449+
]
450+
),
451+
TestStandardTarget(
452+
"staticlib",
453+
type: .staticLibrary,
454+
buildConfigurations: [
455+
TestBuildConfiguration("Debug", buildSettings: [
456+
// FIXME: Find a way to make these default
457+
"EXECUTABLE_PREFIX": "lib",
458+
"EXECUTABLE_PREFIX[sdk=windows*]": "",
459+
])
460+
],
461+
buildPhases: [
462+
TestSourcesBuildPhase(["static.swift"]),
463+
]
464+
),
465+
])
466+
let core = try await getCore()
467+
let tester = try await BuildOperationTester(core, testProject, simulated: false)
468+
469+
let projectDir = tester.workspace.projects[0].sourceRoot
470+
471+
try await tester.fs.writeFileContents(projectDir.join("main.swift")) { stream in
472+
stream <<< "import dynamiclib\n"
473+
stream <<< "import staticlib\n"
474+
stream <<< "dynamicLib()\n"
475+
stream <<< "dynamicLib()\n"
476+
stream <<< "staticLib()\n"
477+
stream <<< "print(\"Hello world\")\n"
478+
}
479+
480+
try await tester.fs.writeFileContents(projectDir.join("dynamic.swift")) { stream in
481+
stream <<< "public func dynamicLib() { }"
482+
}
483+
484+
try await tester.fs.writeFileContents(projectDir.join("static.swift")) { stream in
485+
stream <<< "public func staticLib() { }"
486+
}
487+
488+
try await tester.fs.writePlist(projectDir.join("Entitlements.plist"), .plDict([:]))
489+
490+
let provisioningInputs = [
491+
"dynamiclib": ProvisioningTaskInputs(identityHash: "-", signedEntitlements: .plDict([:]), simulatedEntitlements: .plDict([:])),
492+
"staticlib": ProvisioningTaskInputs(identityHash: "-", signedEntitlements: .plDict([:]), simulatedEntitlements: .plDict([:])),
493+
"tool": ProvisioningTaskInputs(identityHash: "-", signedEntitlements: .plDict([:]), simulatedEntitlements: .plDict([:]))
494+
]
495+
496+
let destination: RunDestinationInfo = .host
497+
try await tester.checkBuild(runDestination: destination, persistent: true, signableTargets: Set(provisioningInputs.keys), signableTargetInputs: provisioningInputs) { results in
498+
results.checkNoErrors()
499+
}
500+
}
501+
}
502+
385503
@Test(.requireSDKs(.macOS))
386504
func unitTestWithGeneratedEntryPointViaMacOSOverride() async throws {
387505
try await withTemporaryDirectory(removeTreeOnDeinit: false) { (tmpDir: Path) in

0 commit comments

Comments
 (0)