Repository navigation
Model parsing numbers from Strings, with NumberFormatException as a branch - #83
Closed
nils-loose wants to merge 2 commits into
Closed
nils-loose wants to merge 2 commits into
nils-loose wants to merge 2 commits into
Conversation
…n as a branch Integer/Long/Short/Byte parse and valueOf(String), with or without a radix, are exact for strings of any length: a string parses iff it is an optional sign and at least one Character.digit digit (Unicode decimal digits and fullwidth letters included), all but the last K of them zeros, and the value in range. The old models took str.to_int of the whole string, which is wrong for signs and non-ASCII digits. Float/Double parse and valueOf(String): whether the string parses is exact (the Double.valueOf grammar, whitespace, hex, NaN, Infinity). The value is exact for decimals without exponent or whitespace whose digits form an integer below 2^p, with at most 10 (22) fraction digits: one correctly rounded division of exact operands. Other parseable strings give a concrete value and record precision loss. The visitor records these branches for static calls on both sides; a NumberFormatException at the branch is explained by the trace instead of recording context loss. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
L0: with the string fixed to an edge sample (empty, sign only, overflow, whitespace, hex, non-ASCII digits) and radix 2..36, the branch holds iff the JDK parses, and the value is the JDK's only; floats on fixed and random strings against Double.parseDouble/Float.parseFloat. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nils-loose
added a commit
that referenced
this pull request
Oct 19, 2026
Combining #83 and #90: #90's library-call branches say whether the call throws when one fails (a check) or both sides return (a split). The NumberFormatException and radix-range branches of #83, and the radix pin, are checks; whether a parsed float is in the exact fragment is a split. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merged
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changes
Models for parsing numbers from Strings. Whether the call throws a
NumberFormatExceptionbecomes a branch on the string. The visitor records it on both sides of a static call, so the solver can ask for a parseable string or for one that is not. A throw at that branch is explained by the trace, so it no longer records the context loss from 5ea26bc.Integer.parseInt,Long.parseLong,Short.parseShort,Byte.parseByte,valueOf(String), with or without a radix: exact for strings of any length, without pinning the length.Character.digit(char, radix)accepts, which includes Unicode decimal digits and fullwidth letters. K is the smallest k with radix^k above the range.str.to_intof the whole string. That is wrong for a sign and for non-ASCII digits.Float.parseFloat,Double.parseDouble,valueOf(String): whether the string parses is exact. This covers the fullDouble.valueOfgrammar: surrounding chars up to U+0020, hex,NaN/Infinity, exponents and suffixes.NaN,Infinity, and plain decimals without exponent or whitespace, where:fp.divof two exact operands. This is the JDK's fast path, and the JDK's result for those inputs.NumberParsing. The changes toIntegerInvocation/LongInvocation/ShortInvocation/ByteInvocationtouch only the parse methods andvalueOf(String); the toString work in another branch should merge without conflicts.FloatInvocation/DoubleInvocationgainparseFloat/parseDoublecases. The visitor reuses the String call-branch recording from Make the String and StringBuilder models sound #75 for static calls.Measured (nosa, 300 s,
--sv-comp --mem15g)There were 114 runs:
parse-float-unmodelledKB tasks,jbmc-regression/calc, and the other tasks callingparse*/valueOf.+3 points vs the integration baseline and vs #75, 0 lost, 0 new wrong verdicts. The 80 regression runs are unchanged.
Known limits
6e0, and these record precision loss, so SAFE is out of reach. The FP queries (string → int → bv →fp.div) take seconds to minutes in Z3. I also triedto_fpfrom a real and an integer rounding characterisation in z3py; neither was faster. The_version2bad variants did not find their violation within 300 s.jbmc-regression/calcis still UNKNOWN, now for another cause. The parse branches are explored, but the solver picks a hugesizefornew String[size], which ends in an OOM (uncaught-oom-huge-nondet-array).StringValueOf08/09still need FP-to-String.parseUnsignedInt/parseUnsignedLong,decode, and theCharSequencerange overloads. They keep the old behaviour.Tests
NumberParsingExactnessTest(L0, 64 cases): with the string fixed to a sample, the branch holds iff the JDK parses it, and the value can only be the JDK's."","-","+","2147483647","2147483648","-2147483648"," 1","1 ","0x1","١", fullwidth digits, long and short bounds), in radix 2..36 for int and long and in a few radixes for short and byte.Double.parseDouble/Float.parseFloat.The full executor test suite passes (9452 tests).
🤖 Generated with Claude Code