Skip to content

Commit 1f95076

Browse files
committed
Merge pull request #613 from PatrickPijnappel/int-parse-fix
[stdlib] Fix Int(_:radix:) accepting unintentional cases (SR-187)
2 parents 6aa0e0f + 17a5845 commit 1f95076

File tree

2 files changed

+37
-26
lines changed

2 files changed

+37
-26
lines changed

stdlib/public/core/IntegerParsing.swift.gyb

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -67,21 +67,21 @@ internal func _parseUnsignedAsciiAsUIntMax(
6767
/// non-negative number <= `maximum`, return that number. Otherwise,
6868
/// return `nil`.
6969
///
70-
/// - Note: If `text` begins with `"+"` or `"-"`, even if the rest of
71-
/// the characters are `"0"`, the result is `nil`.
70+
/// - Note: For text matching the regular expression "-0+", the result
71+
/// is `0`, not `nil`.
7272
internal func _parseAsciiAsUIntMax(
73-
u16: String.UTF16View, _ radix: Int, _ maximum: UIntMax
73+
utf16: String.UTF16View, _ radix: Int, _ maximum: UIntMax
7474
) -> UIntMax? {
75-
if u16.isEmpty { return nil }
76-
let c = u16.first
77-
if _fastPath(c != _ascii16("-")) {
78-
let unsignedText
79-
= c == _ascii16("+") ? u16.dropFirst() : u16
80-
return _parseUnsignedAsciiAsUIntMax(unsignedText, radix, maximum)
81-
}
82-
else {
83-
return _parseAsciiAsIntMax(u16, radix, 0) == 0 ? 0 : nil
84-
}
75+
if utf16.isEmpty { return nil }
76+
// Parse (optional) sign.
77+
let (digitsUTF16, hasMinus) = _parseOptionalAsciiSign(utf16)
78+
// Parse digits.
79+
guard let result = _parseUnsignedAsciiAsUIntMax(digitsUTF16, radix, maximum)
80+
else { return nil }
81+
// Disallow < 0.
82+
if hasMinus && result != 0 { return nil }
83+
// Return.
84+
return result
8585
}
8686

8787
/// If text is an ASCII representation in the given `radix` of a
@@ -91,23 +91,30 @@ internal func _parseAsciiAsUIntMax(
9191
/// - Note: For text matching the regular expression "-0+", the result
9292
/// is `0`, not `nil`.
9393
internal func _parseAsciiAsIntMax(
94-
u16: String.UTF16View, _ radix: Int, _ maximum: IntMax
94+
utf16: String.UTF16View, _ radix: Int, _ maximum: IntMax
9595
) -> IntMax? {
9696
_sanityCheck(maximum >= 0, "maximum should be non-negative")
97+
if utf16.isEmpty { return nil }
98+
// Parse (optional) sign.
99+
let (digitsUTF16, hasMinus) = _parseOptionalAsciiSign(utf16)
100+
// Parse digits. +1 for because e.g. Int8's range is -128...127.
101+
let absValueMax = UIntMax(bitPattern: maximum) + (hasMinus ? 1 : 0)
102+
guard let absValue =
103+
_parseUnsignedAsciiAsUIntMax(digitsUTF16, radix, absValueMax)
104+
else { return nil }
105+
// Return signed result.
106+
return IntMax(bitPattern: hasMinus ? 0 &- absValue : absValue)
107+
}
97108

98-
if u16.isEmpty { return nil }
99-
100-
// Drop any leading "-"
101-
let negative = u16.first == _ascii16("-")
102-
let absResultText = negative ? u16.dropFirst() : u16
103-
104-
let absResultMax = UIntMax(bitPattern: maximum) + (negative ? 1 : 0)
105-
106-
// Parse the result as unsigned
107-
if let absResult = _parseAsciiAsUIntMax(absResultText, radix, absResultMax) {
108-
return IntMax(bitPattern: negative ? 0 &- absResult : absResult)
109+
/// Strip an optional single leading ASCII plus/minus sign from `utf16`.
110+
private func _parseOptionalAsciiSign(
111+
utf16: String.UTF16View
112+
) -> (digitsUTF16: String.UTF16View, isMinus: Bool) {
113+
switch utf16.first {
114+
case _ascii16("-")?: return (utf16.dropFirst(), true)
115+
case _ascii16("+")?: return (utf16.dropFirst(), false)
116+
default: return (utf16, false)
109117
}
110-
return nil
111118
}
112119

113120
//===--- Loop over all integer types --------------------------------------===//

test/1_stdlib/NumericParsing.swift.gyb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ tests.test("${Self}/success") {
9292
expectEqual(nil, ${Self}("${minValue - 1}"))
9393
expectEqual(nil, ${Self}("\u{1D7FF}")) // MATHEMATICAL MONOSPACE DIGIT NINE
9494

95+
// Cases that should fail to parse
96+
expectEqual(nil, ${Self}("--0")) // Zero w/ repeated plus
97+
expectEqual(nil, ${Self}("-+5")) // Non-zero with -+
98+
9599
// Do more exhaustive testing
96100
% for radix in radices_to_test:
97101
% for n in required_values + range(

0 commit comments

Comments
 (0)