Skip to content

Commit 0f8fc45

Browse files
Back-port foundation fixes to 6.0
1 parent c74d309 commit 0f8fc45

File tree

10 files changed

+2273
-0
lines changed

10 files changed

+2273
-0
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
From effb33569a926c12dc8e7bae979ac2075c219189 Mon Sep 17 00:00:00 2001
2+
From: Yuta Saito <[email protected]>
3+
Date: Fri, 26 Jul 2024 03:59:24 +0000
4+
Subject: [PATCH] [CMake] Add support for WebAssembly target architectures
5+
6+
To repair the swift-corelibs-foundation build on WebAssembly, we need to
7+
add support for Wasm targets to swift-collections' CMake build system.
8+
---
9+
cmake/modules/SwiftSupport.cmake | 4 ++++
10+
1 file changed, 4 insertions(+)
11+
12+
diff --git a/cmake/modules/SwiftSupport.cmake b/cmake/modules/SwiftSupport.cmake
13+
index 0ce99fb8..21b9d693 100644
14+
--- a/cmake/modules/SwiftSupport.cmake
15+
+++ b/cmake/modules/SwiftSupport.cmake
16+
@@ -45,6 +45,10 @@ function(get_swift_host_arch result_var_name)
17+
set("${result_var_name}" "i686" PARENT_SCOPE)
18+
elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "i686")
19+
set("${result_var_name}" "i686" PARENT_SCOPE)
20+
+ elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "wasm32")
21+
+ set("${result_var_name}" "wasm32" PARENT_SCOPE)
22+
+ elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "wasm64")
23+
+ set("${result_var_name}" "wasm64" PARENT_SCOPE)
24+
else()
25+
message(FATAL_ERROR "Unrecognized architecture on host system: ${CMAKE_SYSTEM_PROCESSOR}")
26+
endif()
27+
--
28+
2.43.2
29+
Lines changed: 357 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,357 @@
1+
From 6a78203fc24702ed81a2b2bb2a9949168e694161 Mon Sep 17 00:00:00 2001
2+
From: Yuta Saito <[email protected]>
3+
Date: Thu, 8 Aug 2024 23:33:15 +0000
4+
Subject: [PATCH 1/3] [XMLParser] Use `TaskLocal` for storing the current
5+
parser
6+
7+
Instead of thread-local storage, use `TaskLocal` to store the current
8+
parser. This solves three issues:
9+
10+
1. If someone calls `XMLParser.parse()` with a new parser instance in
11+
a delegate method call, it overwrote the current parser and wrote
12+
it back after the call as `nil`, not the previous current parser.
13+
This reentrancy issue can be a problem especially when someone uses
14+
external entity resolving since the feature depends on the current
15+
parser tracking. Using `TaskLocal` solves this issue since it tracks
16+
values as a stack and restores the previous value at the end of the
17+
`withValue` call.
18+
2. Since jobs of different tasks can be scheduled on the same thread,
19+
different tasks can refer to the same thread-local storage. This
20+
wouldn't be a problem for now since the `parse()` method doesn't
21+
have any suspention points and different tasks can't run on the same
22+
thread during the parsing. However, it's better to use `TaskLocal`
23+
to leverage the concurrency model of Swift.
24+
3. The global variable `_currentParser` existed in the WASI platform
25+
path but it's unsafe in the Swift concurrency model. It wouldn't be a
26+
problem on WASI since it's always single-threaded, we should avoid
27+
platform-specific assumption as much as possible.
28+
---
29+
Sources/FoundationXML/XMLParser.swift | 85 ++++++++++-----------------
30+
Tests/Foundation/TestXMLParser.swift | 43 +++++++++++++-
31+
2 files changed, 74 insertions(+), 54 deletions(-)
32+
33+
diff --git a/Sources/FoundationXML/XMLParser.swift b/Sources/FoundationXML/XMLParser.swift
34+
index d89d0ee1f4..e3d718a86f 100644
35+
--- a/Sources/FoundationXML/XMLParser.swift
36+
+++ b/Sources/FoundationXML/XMLParser.swift
37+
@@ -398,9 +398,7 @@ extension XMLParser : @unchecked Sendable { }
38+
39+
open class XMLParser : NSObject {
40+
private var _handler: _CFXMLInterfaceSAXHandler
41+
-#if !os(WASI)
42+
internal var _stream: InputStream?
43+
-#endif
44+
internal var _data: Data?
45+
46+
internal var _chunkSize = Int(4096 * 32) // a suitably large number for a decent chunk size
47+
@@ -469,33 +467,35 @@ open class XMLParser : NSObject {
48+
open var externalEntityResolvingPolicy: ExternalEntityResolvingPolicy = .never
49+
50+
open var allowedExternalEntityURLs: Set<URL>?
51+
-
52+
-#if os(WASI)
53+
- private static var _currentParser: XMLParser?
54+
-#endif
55+
56+
- internal static func currentParser() -> XMLParser? {
57+
-#if os(WASI)
58+
- return _currentParser
59+
-#else
60+
- if let current = Thread.current.threadDictionary["__CurrentNSXMLParser"] {
61+
- return current as? XMLParser
62+
- } else {
63+
- return nil
64+
+ /// The current parser is stored in a task local variable to allow for
65+
+ /// concurrent parsing in different tasks with different parsers.
66+
+ ///
67+
+ /// Rationale for `@unchecked Sendable`:
68+
+ /// While the ``XMLParser`` class itself is not `Sendable`, `TaskLocal`
69+
+ /// requires the value type to be `Sendable`. The sendability requirement
70+
+ /// of `TaskLocal` is only for the "default" value and values set with
71+
+ /// `withValue` will not be shared between tasks.
72+
+ /// So as long as 1. the default value is safe to be shared between tasks
73+
+ /// and 2. the `Sendable` conformance of `_CurrentParser` is not used
74+
+ /// outside of `TaskLocal`, it is safe to mark it as `@unchecked Sendable`.
75+
+ private struct _CurrentParser: @unchecked Sendable {
76+
+ let parser: XMLParser?
77+
+
78+
+ static var `default`: _CurrentParser {
79+
+ return _CurrentParser(parser: nil)
80+
}
81+
-#endif
82+
+ }
83+
+
84+
+ @TaskLocal
85+
+ private static var _currentParser: _CurrentParser = .default
86+
+
87+
+ internal static func currentParser() -> XMLParser? {
88+
+ return _currentParser.parser
89+
}
90+
91+
- internal static func setCurrentParser(_ parser: XMLParser?) {
92+
-#if os(WASI)
93+
- _currentParser = parser
94+
-#else
95+
- if let p = parser {
96+
- Thread.current.threadDictionary["__CurrentNSXMLParser"] = p
97+
- } else {
98+
- Thread.current.threadDictionary.removeObject(forKey: "__CurrentNSXMLParser")
99+
- }
100+
-#endif
101+
+ internal static func withCurrentParser<R>(_ parser: XMLParser, _ body: () -> R) -> R {
102+
+ return self.$_currentParser.withValue(_CurrentParser(parser: parser), operation: body)
103+
}
104+
105+
internal func _handleParseResult(_ parseResult: Int32) -> Bool {
106+
@@ -569,7 +569,6 @@ open class XMLParser : NSObject {
107+
return result
108+
}
109+
110+
-#if !os(WASI)
111+
internal func parseFrom(_ stream : InputStream) -> Bool {
112+
var result = true
113+
114+
@@ -598,37 +597,17 @@ open class XMLParser : NSObject {
115+
116+
return result
117+
}
118+
-#else
119+
- internal func parse(from data: Data) -> Bool {
120+
- var result = true
121+
- var chunkStart = 0
122+
- var chunkEnd = min(_chunkSize, data.count)
123+
- while result && chunkStart < chunkEnd {
124+
- let chunk = data[chunkStart..<chunkEnd]
125+
- result = parseData(chunk)
126+
- chunkStart = chunkEnd
127+
- chunkEnd = min(chunkEnd + _chunkSize, data.count)
128+
- }
129+
- return result
130+
- }
131+
-#endif
132+
133+
// called to start the event-driven parse. Returns YES in the event of a successful parse, and NO in case of error.
134+
open func parse() -> Bool {
135+
-#if os(WASI)
136+
- return _data.map { parse(from: $0) } ?? false
137+
-#else
138+
- XMLParser.setCurrentParser(self)
139+
- defer { XMLParser.setCurrentParser(nil) }
140+
-
141+
- if _stream != nil {
142+
- return parseFrom(_stream!)
143+
- } else if _data != nil {
144+
- return parseData(_data!, lastChunkOfData: true)
145+
+ return Self.withCurrentParser(self) {
146+
+ if _stream != nil {
147+
+ return parseFrom(_stream!)
148+
+ } else if _data != nil {
149+
+ return parseData(_data!, lastChunkOfData: true)
150+
+ }
151+
+ return false
152+
}
153+
-
154+
- return false
155+
-#endif
156+
}
157+
158+
// called by the delegate to stop the parse. The delegate will get an error message sent to it.
159+
diff --git a/Tests/Foundation/TestXMLParser.swift b/Tests/Foundation/TestXMLParser.swift
160+
index c98741eb38..df3685a82e 100644
161+
--- a/Tests/Foundation/TestXMLParser.swift
162+
+++ b/Tests/Foundation/TestXMLParser.swift
163+
@@ -198,5 +198,46 @@ class TestXMLParser : XCTestCase {
164+
ElementNameChecker("noPrefix").check()
165+
ElementNameChecker("myPrefix:myLocalName").check()
166+
}
167+
-
168+
+
169+
+ func testExternalEntity() throws {
170+
+ class Delegate: XMLParserDelegateEventStream {
171+
+ override func parserDidStartDocument(_ parser: XMLParser) {
172+
+ // Start a child parser, updating `currentParser` to the child parser
173+
+ // to ensure that `currentParser` won't be reset to `nil`, which would
174+
+ // ignore any external entity related configuration.
175+
+ let childParser = XMLParser(data: "<child />".data(using: .utf8)!)
176+
+ XCTAssertTrue(childParser.parse())
177+
+ super.parserDidStartDocument(parser)
178+
+ }
179+
+ }
180+
+ try withTemporaryDirectory { dir, _ in
181+
+ let greetingPath = dir.appendingPathComponent("greeting.xml")
182+
+ try Data("<hello />".utf8).write(to: greetingPath)
183+
+ let xml = """
184+
+ <?xml version="1.0" standalone="no"?>
185+
+ <!DOCTYPE doc [
186+
+ <!ENTITY greeting SYSTEM "\(greetingPath.absoluteString)">
187+
+ ]>
188+
+ <doc>&greeting;</doc>
189+
+ """
190+
+
191+
+ let parser = XMLParser(data: xml.data(using: .utf8)!)
192+
+ // Explicitly disable external entity resolving
193+
+ parser.externalEntityResolvingPolicy = .never
194+
+ let delegate = Delegate()
195+
+ parser.delegate = delegate
196+
+ // The parse result changes depending on the libxml2 version
197+
+ // because of the following libxml2 commit (shipped in libxml2 2.9.10):
198+
+ // https://gitlab.gnome.org/GNOME/libxml2/-/commit/eddfbc38fa7e84ccd480eab3738e40d1b2c83979
199+
+ // So we don't check the parse result here.
200+
+ _ = parser.parse()
201+
+ XCTAssertEqual(delegate.events, [
202+
+ .startDocument,
203+
+ .didStartElement("doc", nil, nil, [:]),
204+
+ // Should not have parsed the external entity
205+
+ .didEndElement("doc", nil, nil),
206+
+ .endDocument,
207+
+ ])
208+
+ }
209+
+ }
210+
}
211+
212+
From 7a6125f16e07bb9cdd15e8bee5e7e2c283da4205 Mon Sep 17 00:00:00 2001
213+
From: Yuta Saito <[email protected]>
214+
Date: Fri, 9 Aug 2024 01:51:50 +0000
215+
Subject: [PATCH 2/3] Remove unnecessary `#if os(WASI)` condition in
216+
XMLParser.swift
217+
218+
---
219+
Sources/FoundationXML/XMLParser.swift | 6 ------
220+
1 file changed, 6 deletions(-)
221+
222+
diff --git a/Sources/FoundationXML/XMLParser.swift b/Sources/FoundationXML/XMLParser.swift
223+
index e3d718a86f..39eea6c3d8 100644
224+
--- a/Sources/FoundationXML/XMLParser.swift
225+
+++ b/Sources/FoundationXML/XMLParser.swift
226+
@@ -412,9 +412,6 @@ open class XMLParser : NSObject {
227+
228+
// initializes the parser with the specified URL.
229+
public convenience init?(contentsOf url: URL) {
230+
-#if os(WASI)
231+
- return nil
232+
-#else
233+
setupXMLParsing()
234+
if url.isFileURL {
235+
if let stream = InputStream(url: url) {
236+
@@ -432,7 +429,6 @@ open class XMLParser : NSObject {
237+
return nil
238+
}
239+
}
240+
-#endif
241+
}
242+
243+
// create the parser from data
244+
@@ -448,7 +444,6 @@ open class XMLParser : NSObject {
245+
_CFXMLInterfaceDestroyContext(_parserContext)
246+
}
247+
248+
-#if !os(WASI)
249+
//create a parser that incrementally pulls data from the specified stream and parses it.
250+
public init(stream: InputStream) {
251+
setupXMLParsing()
252+
@@ -456,7 +451,6 @@ open class XMLParser : NSObject {
253+
_handler = _CFXMLInterfaceCreateSAXHandler()
254+
_parserContext = nil
255+
}
256+
-#endif
257+
258+
open weak var delegate: XMLParserDelegate?
259+
260+
261+
From a4a80e1c2f6721b7e92e137d10dbf37dacb65be9 Mon Sep 17 00:00:00 2001
262+
From: Yuta Saito <[email protected]>
263+
Date: Fri, 23 Aug 2024 06:20:59 +0000
264+
Subject: [PATCH 3/3] Keep the current parser in TLS instead of TaskLocal
265+
266+
TaskLocal storage is inherited by non-detached child tasks, which can
267+
lead to the parser being shared between tasks. This is not our intention
268+
and can lead to inconsistent state. Instead, we should keep the current
269+
parser in thread-local storage. This should be safe as long as we don't
270+
have any structured suspension points in `withCurrentParser` block.
271+
---
272+
Sources/FoundationXML/XMLParser.swift | 65 ++++++++++++++++++---------
273+
1 file changed, 44 insertions(+), 21 deletions(-)
274+
275+
diff --git a/Sources/FoundationXML/XMLParser.swift b/Sources/FoundationXML/XMLParser.swift
276+
index 39eea6c3d8..952c25cd58 100644
277+
--- a/Sources/FoundationXML/XMLParser.swift
278+
+++ b/Sources/FoundationXML/XMLParser.swift
279+
@@ -462,34 +462,57 @@ open class XMLParser : NSObject {
280+
281+
open var allowedExternalEntityURLs: Set<URL>?
282+
283+
- /// The current parser is stored in a task local variable to allow for
284+
- /// concurrent parsing in different tasks with different parsers.
285+
- ///
286+
- /// Rationale for `@unchecked Sendable`:
287+
- /// While the ``XMLParser`` class itself is not `Sendable`, `TaskLocal`
288+
- /// requires the value type to be `Sendable`. The sendability requirement
289+
- /// of `TaskLocal` is only for the "default" value and values set with
290+
- /// `withValue` will not be shared between tasks.
291+
- /// So as long as 1. the default value is safe to be shared between tasks
292+
- /// and 2. the `Sendable` conformance of `_CurrentParser` is not used
293+
- /// outside of `TaskLocal`, it is safe to mark it as `@unchecked Sendable`.
294+
- private struct _CurrentParser: @unchecked Sendable {
295+
- let parser: XMLParser?
296+
-
297+
- static var `default`: _CurrentParser {
298+
- return _CurrentParser(parser: nil)
299+
+ /// The current parser context for the current thread.
300+
+ private class _CurrentParserContext {
301+
+ var _stack: [XMLParser] = []
302+
+ var _current: XMLParser? {
303+
+ return _stack.last
304+
}
305+
}
306+
307+
- @TaskLocal
308+
- private static var _currentParser: _CurrentParser = .default
309+
+ #if os(WASI)
310+
+ /// The current parser associated with the current thread. (assuming no multi-threading)
311+
+ /// FIXME: Unify the implementation with the other platforms once we unlock `threadDictionary`
312+
+ /// or migrate to `FoundationEssentials._ThreadLocal`.
313+
+ private static nonisolated(unsafe) var _currentParserContext: _CurrentParserContext?
314+
+ #else
315+
+ /// The current parser associated with the current thread.
316+
+ private static var _currentParserContext: _CurrentParserContext? {
317+
+ get {
318+
+ return Thread.current.threadDictionary["__CurrentNSXMLParser"] as? _CurrentParserContext
319+
+ }
320+
+ set {
321+
+ Thread.current.threadDictionary["__CurrentNSXMLParser"] = newValue
322+
+ }
323+
+ }
324+
+ #endif
325+
326+
+ /// The current parser associated with the current thread.
327+
internal static func currentParser() -> XMLParser? {
328+
- return _currentParser.parser
329+
+ if let ctx = _currentParserContext {
330+
+ return ctx._current
331+
+ }
332+
+ return nil
333+
}
334+
-
335+
+
336+
+ /// Execute the given closure with the current parser set to the given parser.
337+
internal static func withCurrentParser<R>(_ parser: XMLParser, _ body: () -> R) -> R {
338+
- return self.$_currentParser.withValue(_CurrentParser(parser: parser), operation: body)
339+
+ var ctx: _CurrentParserContext
340+
+ if let current = _currentParserContext {
341+
+ // Use the existing context if it exists
342+
+ ctx = current
343+
+ } else {
344+
+ // Create a new context in TLS
345+
+ ctx = _CurrentParserContext()
346+
+ _currentParserContext = ctx
347+
+ }
348+
+ // Push the parser onto the stack
349+
+ ctx._stack.append(parser)
350+
+ defer {
351+
+ // Pop the parser off the stack
352+
+ ctx._stack.removeLast()
353+
+ }
354+
+ return body()
355+
}
356+
357+
internal func _handleParseResult(_ parseResult: Int32) -> Bool {

0 commit comments

Comments
 (0)