DoubleParser.swift 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. // Sources/SwiftProtobuf/DoubleParser.swift - Generally useful mathematical functions
  2. //
  3. // Copyright (c) 2014 - 2019 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. /// Numeric parsing helper for float and double strings
  12. ///
  13. // -----------------------------------------------------------------------------
  14. import Foundation
  15. /// Support parsing float/double values from UTF-8
  16. internal class DoubleParser {
  17. // Temporary buffer so we can null-terminate the UTF-8 string
  18. // before calling the C standard library to parse it.
  19. //
  20. // In theory, JSON writers should be able to represent any IEEE Double
  21. // in at most 25 bytes, but many writers will emit more digits than
  22. // necessary, so we size this generously; but we could still fail to
  23. // parse if someone crafts something really long (especially for
  24. // TextFormat due to overflows (see below)).
  25. private var work =
  26. UnsafeMutableBufferPointer<Int8>.allocate(capacity: 128)
  27. deinit {
  28. work.deallocate()
  29. }
  30. func utf8ToDouble(
  31. bytes: UnsafeRawBufferPointer,
  32. start: UnsafeRawBufferPointer.Index,
  33. end: UnsafeRawBufferPointer.Index
  34. ) -> Double? {
  35. utf8ToDouble(bytes: UnsafeRawBufferPointer(rebasing: bytes[start..<end]))
  36. }
  37. func utf8ToDouble(bytes: UnsafeRawBufferPointer, finiteOnly: Bool = true) -> Double? {
  38. // Reject unreasonably long or short UTF8 number
  39. if work.count <= bytes.count || bytes.count < 1 {
  40. return nil
  41. }
  42. UnsafeMutableRawBufferPointer(work).copyMemory(from: bytes)
  43. work[bytes.count] = 0
  44. // Use C library strtod() to parse it
  45. var e: UnsafeMutablePointer<Int8>? = work.baseAddress
  46. let d = strtod(work.baseAddress!, &e)
  47. // Fail if strtod() did not consume everything we expected.
  48. guard e == work.baseAddress! + bytes.count else {
  49. return nil
  50. }
  51. // If strtod() thought the number was out of range, it will return
  52. // a non-finite number...
  53. //
  54. // TextFormat specifically calls out handling for overflows for
  55. // float/double fields:
  56. // https://protobuf.dev/reference/protobuf/textformat-spec/#value
  57. //
  58. // > Overflows are treated as infinity or -infinity.
  59. //
  60. // But the JSON protobuf spec doesn't mention anything:
  61. // https://protobuf.dev/programming-guides/proto3/#json
  62. if finiteOnly && !d.isFinite {
  63. return nil
  64. }
  65. return d
  66. }
  67. }