/src/swift-protobuf/Sources/SwiftProtobuf/NameMap.swift
Line | Count | Source (jump to first uncovered line) |
1 | | // Sources/SwiftProtobuf/NameMap.swift - Bidirectional number/name mapping |
2 | | // |
3 | | // Copyright (c) 2014 - 2016 Apple Inc. and the project authors |
4 | | // Licensed under Apache License v2.0 with Runtime Library Exception |
5 | | // |
6 | | // See LICENSE.txt for license information: |
7 | | // https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt |
8 | | // |
9 | | // ----------------------------------------------------------------------------- |
10 | | |
11 | | /// TODO: Right now, only the NameMap and the NameDescription enum |
12 | | /// (which are directly used by the generated code) are public. |
13 | | /// This means that code outside the library has no way to actually |
14 | | /// use this data. We should develop and publicize a suitable API |
15 | | /// for that purpose. (Which might be the same as the internal API.) |
16 | | |
17 | | /// This must produce exactly the same outputs as the corresponding |
18 | | /// code in the protoc-gen-swift code generator. Changing it will |
19 | | /// break compatibility of the library with older generated code. |
20 | | /// |
21 | | /// It does not necessarily need to match protoc's JSON field naming |
22 | | /// logic, however. |
23 | 1.15k | private func toJSONFieldName(_ s: UnsafeBufferPointer<UInt8>) -> String { |
24 | 1.15k | var result = String.UnicodeScalarView() |
25 | 1.15k | var capitalizeNext = false |
26 | 18.8k | for c in s { |
27 | 18.8k | if c == UInt8(ascii: "_") { |
28 | 2.01k | capitalizeNext = true |
29 | 18.8k | } else if capitalizeNext { |
30 | 2.01k | result.append(Unicode.Scalar(c).uppercasedAssumingASCII) |
31 | 2.01k | capitalizeNext = false |
32 | 18.8k | } else { |
33 | 16.8k | result.append(Unicode.Scalar(c)) |
34 | 18.8k | } |
35 | 18.8k | } |
36 | 1.15k | return String(result) |
37 | 1.15k | } |
38 | | #if !REMOVE_LEGACY_NAMEMAP_INITIALIZERS |
39 | 0 | private func toJSONFieldName(_ s: StaticString) -> String { |
40 | 0 | guard s.hasPointerRepresentation else { |
41 | 0 | // If it's a single code point, it wouldn't be changed by the above algorithm. |
42 | 0 | // Return it as-is. |
43 | 0 | return s.description |
44 | 0 | } |
45 | 0 | return toJSONFieldName(UnsafeBufferPointer(start: s.utf8Start, count: s.utf8CodeUnitCount)) |
46 | 0 | } |
47 | | #endif // !REMOVE_LEGACY_NAMEMAP_INITIALIZERS |
48 | | |
49 | | /// Allocate static memory buffers to intern UTF-8 |
50 | | /// string data. Track the buffers and release all of those buffers |
51 | | /// in case we ever get deallocated. |
52 | | private class InternPool { |
53 | 640 | private var interned = [UnsafeRawBufferPointer]() |
54 | | |
55 | 1.16k | func intern(utf8: String.UTF8View) -> UnsafeRawBufferPointer { |
56 | 1.16k | let mutable = UnsafeMutableRawBufferPointer.allocate( |
57 | 1.16k | byteCount: utf8.count, |
58 | 1.16k | alignment: MemoryLayout<UInt8>.alignment |
59 | 1.16k | ) |
60 | 1.16k | mutable.copyBytes(from: utf8) |
61 | 1.16k | let immutable = UnsafeRawBufferPointer(mutable) |
62 | 1.16k | interned.append(immutable) |
63 | 1.16k | return immutable |
64 | 1.16k | } |
65 | | |
66 | 0 | func intern(utf8Ptr: UnsafeBufferPointer<UInt8>) -> UnsafeRawBufferPointer { |
67 | 0 | let mutable = UnsafeMutableRawBufferPointer.allocate( |
68 | 0 | byteCount: utf8Ptr.count, |
69 | 0 | alignment: MemoryLayout<UInt8>.alignment |
70 | 0 | ) |
71 | 0 | mutable.copyBytes(from: utf8Ptr) |
72 | 0 | let immutable = UnsafeRawBufferPointer(mutable) |
73 | 0 | interned.append(immutable) |
74 | 0 | return immutable |
75 | 0 | } |
76 | | |
77 | 0 | deinit { |
78 | 0 | for buff in interned { |
79 | 0 | buff.deallocate() |
80 | 0 | } |
81 | 0 | } |
82 | | } |
83 | | |
84 | | /// Instructions used in bytecode streams that define proto name mappings. |
85 | | /// |
86 | | /// Since field and enum case names are encoded in numeric order, field and case number operands in |
87 | | /// the bytecode are stored as adjacent differences. Most messages/enums use densely packed |
88 | | /// numbers, so we've optimized the opcodes for that; each instruction that takes a single |
89 | | /// field/case number has two forms: one that assumes the next number is +1 from the previous |
90 | | /// number, and a second form that takes an arbitrary delta from the previous number. |
91 | | /// |
92 | | /// This has package visibility so that it is also visible to the generator. |
93 | | package enum ProtoNameInstruction: UInt64, CaseIterable { |
94 | | /// The proto (text format) name and the JSON name are the same string. |
95 | | /// |
96 | | /// ## Operands |
97 | | /// * (Delta only) An integer representing the (delta from the previous) field or enum case |
98 | | /// number. |
99 | | /// * A string containing the single text format and JSON name. |
100 | | case sameNext = 1 |
101 | | case sameDelta = 2 |
102 | | |
103 | | /// The JSON name can be computed from the proto string. |
104 | | /// |
105 | | /// ## Operands |
106 | | /// * (Delta only) An integer representing the (delta from the previous) field or enum case |
107 | | /// number. |
108 | | /// * A string containing the single text format name, from which the JSON name will be |
109 | | /// dynamically computed. |
110 | | case standardNext = 3 |
111 | | case standardDelta = 4 |
112 | | |
113 | | /// The JSON and text format names are just different. |
114 | | /// |
115 | | /// ## Operands |
116 | | /// * (Delta only) An integer representing the (delta from the previous) field or enum case |
117 | | /// number. |
118 | | /// * A string containing the text format name. |
119 | | /// * A string containing the JSON name. |
120 | | case uniqueNext = 5 |
121 | | case uniqueDelta = 6 |
122 | | |
123 | | /// Used for group fields only to represent the message type name of a group. |
124 | | /// |
125 | | /// ## Operands |
126 | | /// * (Delta only) An integer representing the (delta from the previous) field number. |
127 | | /// * A string containing the (UpperCamelCase by convention) message type name, from which the |
128 | | /// text format and JSON names can be derived (lowercase). |
129 | | case groupNext = 7 |
130 | | case groupDelta = 8 |
131 | | |
132 | | /// Used for enum cases only to represent a value's primary proto name (the first defined case) |
133 | | /// and its aliases. The JSON and text format names for enums are always the same. |
134 | | /// |
135 | | /// ## Operands |
136 | | /// * (Delta only) An integer representing the (delta from the previous) enum case number. |
137 | | /// * An integer `aliasCount` representing the number of aliases. |
138 | | /// * A string containing the text format/JSON name (the first defined case with this number). |
139 | | /// * `aliasCount` strings containing other text format/JSON names that are aliases. |
140 | | case aliasNext = 9 |
141 | | case aliasDelta = 10 |
142 | | |
143 | | /// Represents a reserved name in a proto message. |
144 | | /// |
145 | | /// ## Operands |
146 | | /// * The name of a reserved field. |
147 | | case reservedName = 11 |
148 | | |
149 | | /// Represents a range of reserved field numbers or enum case numbers in a proto message. |
150 | | /// |
151 | | /// ## Operands |
152 | | /// * An integer representing the lower bound (inclusive) of the reserved number range. |
153 | | /// * An integer representing the delta between the upper bound (exclusive) and the lower bound |
154 | | /// of the reserved number range. |
155 | | case reservedNumbers = 12 |
156 | | |
157 | | /// Indicates whether the opcode represents an instruction that has an explicit delta encoded |
158 | | /// as its first operand. |
159 | 689 | var hasExplicitDelta: Bool { |
160 | 689 | switch self { |
161 | 689 | case .sameDelta, .standardDelta, .uniqueDelta, .groupDelta, .aliasDelta: return true |
162 | 689 | default: return false |
163 | 689 | } |
164 | 689 | } |
165 | | } |
166 | | |
167 | | /// An immutable bidirectional mapping between field/enum-case names |
168 | | /// and numbers, used to record field names for text-based |
169 | | /// serialization (JSON and text). These maps are lazily instantiated |
170 | | /// for each message as needed, so there is no run-time overhead for |
171 | | /// users who do not use text-based serialization formats. |
172 | | public struct _NameMap: ExpressibleByDictionaryLiteral { |
173 | | |
174 | | /// An immutable interned string container. The `utf8Start` pointer |
175 | | /// is guaranteed valid for the lifetime of the `NameMap` that you |
176 | | /// fetched it from. Since `NameMap`s are only instantiated as |
177 | | /// immutable static values, that should be the lifetime of the |
178 | | /// program. |
179 | | /// |
180 | | /// Internally, this uses `StaticString` (which refers to a fixed |
181 | | /// block of UTF-8 data) where possible. In cases where the string |
182 | | /// has to be computed, it caches the UTF-8 bytes in an |
183 | | /// unmovable and immutable heap area. |
184 | | package struct Name: Hashable, CustomStringConvertible { |
185 | | #if !REMOVE_LEGACY_NAMEMAP_INITIALIZERS |
186 | | // This should not be used outside of this file, as it requires |
187 | | // coordinating the lifecycle with the lifecycle of the pool |
188 | | // where the raw UTF8 gets interned. |
189 | 0 | fileprivate init(staticString: StaticString, pool: InternPool) { |
190 | 0 | if staticString.hasPointerRepresentation { |
191 | 0 | self.utf8Buffer = UnsafeRawBufferPointer( |
192 | 0 | start: staticString.utf8Start, |
193 | 0 | count: staticString.utf8CodeUnitCount |
194 | 0 | ) |
195 | 0 | } else { |
196 | 0 | self.utf8Buffer = staticString.withUTF8Buffer { pool.intern(utf8Ptr: $0) } |
197 | 0 | } |
198 | 0 | } |
199 | | #endif // !REMOVE_LEGACY_NAMEMAP_INITIALIZERS |
200 | | |
201 | | // This should not be used outside of this file, as it requires |
202 | | // coordinating the lifecycle with the lifecycle of the pool |
203 | | // where the raw UTF8 gets interned. |
204 | 1.16k | fileprivate init(string: String, pool: InternPool) { |
205 | 1.16k | let utf8 = string.utf8 |
206 | 1.16k | self.utf8Buffer = pool.intern(utf8: utf8) |
207 | 1.16k | } |
208 | | |
209 | | // This is for building a transient `Name` object sufficient for lookup purposes. |
210 | | // It MUST NOT be exposed outside of this file. |
211 | 30.4M | fileprivate init(transientUtf8Buffer: UnsafeRawBufferPointer) { |
212 | 30.4M | self.utf8Buffer = transientUtf8Buffer |
213 | 30.4M | } |
214 | | |
215 | | // This is for building a `Name` object from a slice of a bytecode `StaticString`. |
216 | | // It MUST NOT be exposed outside of this file. |
217 | 1.37k | fileprivate init(bytecodeUTF8Buffer: UnsafeBufferPointer<UInt8>) { |
218 | 1.37k | self.utf8Buffer = UnsafeRawBufferPointer(bytecodeUTF8Buffer) |
219 | 1.37k | } |
220 | | |
221 | | internal let utf8Buffer: UnsafeRawBufferPointer |
222 | | |
223 | 124k | public var description: String { |
224 | 124k | String(decoding: self.utf8Buffer, as: UTF8.self) |
225 | 124k | } |
226 | | |
227 | 17.2M | public func hash(into hasher: inout Hasher) { |
228 | 974M | for byte in utf8Buffer { |
229 | 974M | hasher.combine(byte) |
230 | 974M | } |
231 | 17.2M | } |
232 | | |
233 | 12.8M | public static func == (lhs: Name, rhs: Name) -> Bool { |
234 | 12.8M | if lhs.utf8Buffer.count != rhs.utf8Buffer.count { |
235 | 9.65M | return false |
236 | 9.65M | } |
237 | 3.16M | return lhs.utf8Buffer.elementsEqual(rhs.utf8Buffer) |
238 | 12.8M | } |
239 | | } |
240 | | |
241 | | /// The JSON and proto names for a particular field, enum case, or extension. |
242 | | internal struct Names { |
243 | | private(set) var json: Name? |
244 | | private(set) var proto: Name |
245 | | } |
246 | | |
247 | | #if !REMOVE_LEGACY_NAMEMAP_INITIALIZERS |
248 | | |
249 | | /// A description of the names for a particular field or enum case. |
250 | | /// The different forms here let us minimize the amount of string |
251 | | /// data that we store in the binary. |
252 | | /// |
253 | | /// These are only used in the generated code to initialize a NameMap. |
254 | | public enum NameDescription { |
255 | | |
256 | | /// The proto (text format) name and the JSON name are the same string. |
257 | | case same(proto: StaticString) |
258 | | |
259 | | /// The JSON name can be computed from the proto string |
260 | | case standard(proto: StaticString) |
261 | | |
262 | | /// The JSON and text format names are just different. |
263 | | case unique(proto: StaticString, json: StaticString) |
264 | | |
265 | | /// Used for enum cases only to represent a value's primary proto name (the |
266 | | /// first defined case) and its aliases. The JSON and text format names for |
267 | | /// enums are always the same. |
268 | | case aliased(proto: StaticString, aliases: [StaticString]) |
269 | | } |
270 | | |
271 | | #endif // !REMOVE_LEGACY_NAMEMAP_INITIALIZERS |
272 | | |
273 | 640 | private var internPool = InternPool() |
274 | | |
275 | | /// The mapping from field/enum-case numbers to names. |
276 | 640 | private var numberToNameMap: [Int: Names] = [:] |
277 | | |
278 | | /// The mapping from proto/text names to field/enum-case numbers. |
279 | 640 | private var protoToNumberMap: [Name: Int] = [:] |
280 | | |
281 | | /// The mapping from JSON names to field/enum-case numbers. |
282 | | /// Note that this also contains all of the proto/text names, |
283 | | /// as required by Google's spec for protobuf JSON. |
284 | 640 | private var jsonToNumberMap: [Name: Int] = [:] |
285 | | |
286 | | /// The reserved names in for this object. Currently only used for Message to |
287 | | /// support TextFormat's requirement to skip these names in all cases. |
288 | 640 | private var reservedNames: [String] = [] |
289 | | |
290 | | /// The reserved numbers in for this object. Currently only used for Message to |
291 | | /// support TextFormat's requirement to skip these numbers in all cases. |
292 | 640 | private var reservedRanges: [Range<Int32>] = [] |
293 | | |
294 | | /// Creates a new empty field/enum-case name/number mapping. |
295 | 20 | public init() {} |
296 | | |
297 | | #if REMOVE_LEGACY_NAMEMAP_INITIALIZERS |
298 | | |
299 | | // Provide a dummy for ExpressibleByDictionaryLiteral conformance. |
300 | | public init(dictionaryLiteral elements: (Int, Int)...) { |
301 | | fatalError("Support compiled out removed") |
302 | | } |
303 | | |
304 | | #else // !REMOVE_LEGACY_NAMEMAP_INITIALIZERS |
305 | | |
306 | | /// Build the bidirectional maps between numbers and proto/JSON names. |
307 | | public init( |
308 | | reservedNames: [String], |
309 | | reservedRanges: [Range<Int32>], |
310 | | numberNameMappings: KeyValuePairs<Int, NameDescription> |
311 | 0 | ) { |
312 | 0 | self.reservedNames = reservedNames |
313 | 0 | self.reservedRanges = reservedRanges |
314 | 0 |
|
315 | 0 | initHelper(numberNameMappings) |
316 | 0 | } |
317 | | |
318 | | /// Build the bidirectional maps between numbers and proto/JSON names. |
319 | 0 | public init(dictionaryLiteral elements: (Int, NameDescription)...) { |
320 | 0 | initHelper(elements) |
321 | 0 | } |
322 | | |
323 | | /// Helper to share the building of mappings between the two initializers. |
324 | | private mutating func initHelper<Pairs: Collection>( |
325 | | _ elements: Pairs |
326 | 0 | ) where Pairs.Element == (key: Int, value: NameDescription) { |
327 | 0 | for (number, description) in elements { |
328 | 0 | switch description { |
329 | 0 |
|
330 | 0 | case .same(proto: let p): |
331 | 0 | let protoName = Name(staticString: p, pool: internPool) |
332 | 0 | let names = Names(json: protoName, proto: protoName) |
333 | 0 | numberToNameMap[number] = names |
334 | 0 | protoToNumberMap[protoName] = number |
335 | 0 | jsonToNumberMap[protoName] = number |
336 | 0 |
|
337 | 0 | case .standard(proto: let p): |
338 | 0 | let protoName = Name(staticString: p, pool: internPool) |
339 | 0 | let jsonString = toJSONFieldName(p) |
340 | 0 | let jsonName = Name(string: jsonString, pool: internPool) |
341 | 0 | let names = Names(json: jsonName, proto: protoName) |
342 | 0 | numberToNameMap[number] = names |
343 | 0 | protoToNumberMap[protoName] = number |
344 | 0 | jsonToNumberMap[protoName] = number |
345 | 0 | jsonToNumberMap[jsonName] = number |
346 | 0 |
|
347 | 0 | case .unique(proto: let p, json: let j): |
348 | 0 | let jsonName = Name(staticString: j, pool: internPool) |
349 | 0 | let protoName = Name(staticString: p, pool: internPool) |
350 | 0 | let names = Names(json: jsonName, proto: protoName) |
351 | 0 | numberToNameMap[number] = names |
352 | 0 | protoToNumberMap[protoName] = number |
353 | 0 | jsonToNumberMap[protoName] = number |
354 | 0 | jsonToNumberMap[jsonName] = number |
355 | 0 |
|
356 | 0 | case .aliased(proto: let p, let aliases): |
357 | 0 | let protoName = Name(staticString: p, pool: internPool) |
358 | 0 | let names = Names(json: protoName, proto: protoName) |
359 | 0 | numberToNameMap[number] = names |
360 | 0 | protoToNumberMap[protoName] = number |
361 | 0 | jsonToNumberMap[protoName] = number |
362 | 0 | for alias in aliases { |
363 | 0 | let protoName = Name(staticString: alias, pool: internPool) |
364 | 0 | protoToNumberMap[protoName] = number |
365 | 0 | jsonToNumberMap[protoName] = number |
366 | 0 | } |
367 | 0 | } |
368 | 0 | } |
369 | 0 | } |
370 | | |
371 | | #endif // !REMOVE_LEGACY_NAMEMAP_INITIALIZERS |
372 | | |
373 | 120 | public init(bytecode: StaticString) { |
374 | 120 | var previousNumber = 0 |
375 | 1.37k | BytecodeInterpreter<ProtoNameInstruction>(program: bytecode).execute { instruction, reader in |
376 | 1.37k | func nextNumber() -> Int { |
377 | 1.37k | let next: Int |
378 | 1.37k | if instruction.hasExplicitDelta { |
379 | 74 | next = previousNumber + Int(reader.nextInt32()) |
380 | 1.37k | } else { |
381 | 1.30k | next = previousNumber + 1 |
382 | 1.37k | } |
383 | 1.37k | previousNumber = next |
384 | 1.37k | return next |
385 | 1.37k | } |
386 | 1.37k | |
387 | 1.37k | switch instruction { |
388 | 1.37k | case .sameNext, .sameDelta: |
389 | 210 | let number = nextNumber() |
390 | 210 | let protoName = Name(bytecodeUTF8Buffer: reader.nextNullTerminatedString()) |
391 | 210 | numberToNameMap[number] = Names(json: protoName, proto: protoName) |
392 | 210 | protoToNumberMap[protoName] = number |
393 | 210 | jsonToNumberMap[protoName] = number |
394 | 1.37k | |
395 | 1.37k | case .standardNext, .standardDelta: |
396 | 1.15k | let number = nextNumber() |
397 | 1.15k | let protoNameBuffer = reader.nextNullTerminatedString() |
398 | 1.15k | let protoName = Name(bytecodeUTF8Buffer: protoNameBuffer) |
399 | 1.15k | let jsonString = toJSONFieldName(protoNameBuffer) |
400 | 1.15k | let jsonName = Name(string: jsonString, pool: internPool) |
401 | 1.15k | numberToNameMap[number] = Names(json: jsonName, proto: protoName) |
402 | 1.15k | protoToNumberMap[protoName] = number |
403 | 1.15k | jsonToNumberMap[protoName] = number |
404 | 1.15k | jsonToNumberMap[jsonName] = number |
405 | 1.37k | |
406 | 1.37k | case .uniqueNext, .uniqueDelta: |
407 | 0 | let number = nextNumber() |
408 | 0 | let protoName = Name(bytecodeUTF8Buffer: reader.nextNullTerminatedString()) |
409 | 0 | let jsonName = Name(bytecodeUTF8Buffer: reader.nextNullTerminatedString()) |
410 | 0 | numberToNameMap[number] = Names(json: jsonName, proto: protoName) |
411 | 0 | protoToNumberMap[protoName] = number |
412 | 0 | jsonToNumberMap[protoName] = number |
413 | 0 | jsonToNumberMap[jsonName] = number |
414 | 1.37k | |
415 | 1.37k | case .groupNext, .groupDelta: |
416 | 12 | let number = nextNumber() |
417 | 12 | let protoNameBuffer = reader.nextNullTerminatedString() |
418 | 12 | let protoName = Name(bytecodeUTF8Buffer: protoNameBuffer) |
419 | 12 | protoToNumberMap[protoName] = number |
420 | 12 | jsonToNumberMap[protoName] = number |
421 | 12 | |
422 | 12 | let lowercaseName: Name |
423 | 12 | let hasUppercase = protoNameBuffer.contains { (UInt8(ascii: "A")...UInt8(ascii: "Z")).contains($0) } |
424 | 12 | if hasUppercase { |
425 | 12 | lowercaseName = Name( |
426 | 12 | string: String(decoding: protoNameBuffer, as: UTF8.self).lowercased(), |
427 | 12 | pool: internPool |
428 | 12 | ) |
429 | 12 | protoToNumberMap[lowercaseName] = number |
430 | 12 | jsonToNumberMap[lowercaseName] = number |
431 | 12 | } else { |
432 | 0 | // No need to convert and intern a separate copy of the string |
433 | 0 | // if it would be identical. |
434 | 0 | lowercaseName = protoName |
435 | 12 | } |
436 | 12 | numberToNameMap[number] = Names(json: lowercaseName, proto: protoName) |
437 | 1.37k | |
438 | 1.37k | case .aliasNext, .aliasDelta: |
439 | 0 | let number = nextNumber() |
440 | 0 | let protoName = Name(bytecodeUTF8Buffer: reader.nextNullTerminatedString()) |
441 | 0 | numberToNameMap[number] = Names(json: protoName, proto: protoName) |
442 | 0 | protoToNumberMap[protoName] = number |
443 | 0 | jsonToNumberMap[protoName] = number |
444 | 0 | for alias in reader.nextNullTerminatedStringArray() { |
445 | 0 | let protoName = Name(bytecodeUTF8Buffer: alias) |
446 | 0 | protoToNumberMap[protoName] = number |
447 | 0 | jsonToNumberMap[protoName] = number |
448 | 1.37k | } |
449 | 1.37k | |
450 | 1.37k | case .reservedName: |
451 | 0 | let name = String(decoding: reader.nextNullTerminatedString(), as: UTF8.self) |
452 | 0 | reservedNames.append(name) |
453 | 1.37k | |
454 | 1.37k | case .reservedNumbers: |
455 | 0 | let lowerBound = reader.nextInt32() |
456 | 0 | let upperBound = lowerBound + reader.nextInt32() |
457 | 0 | reservedRanges.append(lowerBound..<upperBound) |
458 | 1.37k | } |
459 | 1.37k | } |
460 | 120 | } |
461 | | |
462 | | /// Returns the name bundle for the field/enum-case with the given number, or |
463 | | /// `nil` if there is no match. |
464 | 186M | internal func names(for number: Int) -> Names? { |
465 | 186M | numberToNameMap[number] |
466 | 186M | } |
467 | | |
468 | | /// Returns the field/enum-case number that has the given JSON name, |
469 | | /// or `nil` if there is no match. |
470 | | /// |
471 | | /// This is used by the Text format parser to look up field or enum |
472 | | /// names using a direct reference to the un-decoded UTF8 bytes. |
473 | 145k | internal func number(forProtoName raw: UnsafeRawBufferPointer) -> Int? { |
474 | 145k | let n = Name(transientUtf8Buffer: raw) |
475 | 145k | return protoToNumberMap[n] |
476 | 145k | } |
477 | | |
478 | | /// Returns the field/enum-case number that has the given JSON name, |
479 | | /// or `nil` if there is no match. |
480 | | /// |
481 | | /// This accepts a regular `String` and is used in JSON parsing |
482 | | /// only when a field name or enum name was decoded from a string |
483 | | /// containing backslash escapes. |
484 | | /// |
485 | | /// JSON parsing must interpret *both* the JSON name of the |
486 | | /// field/enum-case provided by the descriptor *as well as* its |
487 | | /// original proto/text name. |
488 | 121k | internal func number(forJSONName name: String) -> Int? { |
489 | 121k | let utf8 = Array(name.utf8) |
490 | 127k | return utf8.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) in |
491 | 127k | let n = Name(transientUtf8Buffer: buffer) |
492 | 127k | return jsonToNumberMap[n] |
493 | 127k | } |
494 | 121k | } |
495 | | |
496 | | /// Returns the field/enum-case number that has the given JSON name, |
497 | | /// or `nil` if there is no match. |
498 | | /// |
499 | | /// This is used by the JSON parser when a field name or enum name |
500 | | /// required no special processing. As a result, we can avoid |
501 | | /// copying the name and look up the number using a direct reference |
502 | | /// to the un-decoded UTF8 bytes. |
503 | 7.22M | internal func number(forJSONName raw: UnsafeRawBufferPointer) -> Int? { |
504 | 7.22M | let n = Name(transientUtf8Buffer: raw) |
505 | 7.22M | return jsonToNumberMap[n] |
506 | 7.22M | } |
507 | | |
508 | | /// Returns all proto names |
509 | 0 | internal var names: [Name] { |
510 | 0 | numberToNameMap.map(\.value.proto) |
511 | 0 | } |
512 | | |
513 | | /// Returns if the given name was reserved. |
514 | 409 | internal func isReserved(name: UnsafeRawBufferPointer) -> Bool { |
515 | 409 | guard !reservedNames.isEmpty, |
516 | 409 | let baseAddress = name.baseAddress, |
517 | 409 | let s = utf8ToString(bytes: baseAddress, count: name.count) |
518 | 409 | else { |
519 | 409 | return false |
520 | 409 | } |
521 | 0 | return reservedNames.contains(s) |
522 | 409 | } |
523 | | |
524 | | /// Returns if the given number was reserved. |
525 | 234 | internal func isReserved(number: Int32) -> Bool { |
526 | 234 | for range in reservedRanges { |
527 | 0 | if range.contains(number) { |
528 | 0 | return true |
529 | 0 | } |
530 | 234 | } |
531 | 234 | return false |
532 | 234 | } |
533 | | } |
534 | | |
535 | | // The `_NameMap` (and supporting types) are only mutated during their initial |
536 | | // creation, then for the lifetime of the a process they are constant. Swift |
537 | | // 5.10 flags the generated `_protobuf_nameMap` usages as a problem |
538 | | // (https://github.com/apple/swift-protobuf/issues/1560) so this silences those |
539 | | // warnings since the usage has been deemed safe. |
540 | | // |
541 | | // https://github.com/apple/swift-protobuf/issues/1561 is also opened to revisit |
542 | | // the `_NameMap` generally as it dates back to the days before Swift perferred |
543 | | // the UTF-8 internal encoding. |
544 | | extension _NameMap: Sendable {} |
545 | | extension _NameMap.Name: @unchecked Sendable {} |
546 | | extension InternPool: @unchecked Sendable {} |