Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions java/src/org/openqa/selenium/json/NumberCoercer.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

import java.io.StringReader;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
Expand Down Expand Up @@ -80,7 +82,53 @@ public BiFunction<JsonInput, PropertySetting, T> apply(Type ignored) {
default:
throw new JsonException("Unable to coerce to a number: " + jsonInput.peek());
}
validateIntegralRange(number, stereotype);
return mapper.apply(number);
};
}

private static void validateIntegralRange(Number number, Class<?> stereotype) {
// Prevent silent overflow when JSON numbers are coerced to integral types.
// Java's Number.intValue()/longValue() silently wraps values outside the
// valid range, which previously produced incorrect results instead of errors.
if (!(stereotype == Byte.class
|| stereotype == Short.class
|| stereotype == Integer.class
|| stereotype == Long.class)) {
return;
}

final BigInteger value;
try {
// Use BigDecimal so we can reject fractional values and validate range before coercion.
BigDecimal bd =
(number instanceof BigDecimal) ? (BigDecimal) number : new BigDecimal(number.toString());
value = bd.toBigIntegerExact();
} catch (RuntimeException e) {
throw new JsonException(
"Expected an integer value for " + stereotype.getSimpleName() + ": " + number, e);
}

BigInteger min;
BigInteger max;

if (stereotype == Byte.class) {
min = BigInteger.valueOf(Byte.MIN_VALUE);
max = BigInteger.valueOf(Byte.MAX_VALUE);
} else if (stereotype == Short.class) {
min = BigInteger.valueOf(Short.MIN_VALUE);
max = BigInteger.valueOf(Short.MAX_VALUE);
} else if (stereotype == Integer.class) {
min = BigInteger.valueOf(Integer.MIN_VALUE);
max = BigInteger.valueOf(Integer.MAX_VALUE);
} else { // Long.class
min = BigInteger.valueOf(Long.MIN_VALUE);
max = BigInteger.valueOf(Long.MAX_VALUE);
}

if (value.compareTo(min) < 0 || value.compareTo(max) > 0) {
throw new JsonException(
"Numeric value out of range for " + stereotype.getSimpleName() + ": " + value);
}
Comment on lines +85 to +132
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Overflow fix lacks tests 📘 Rule violation ⛯ Reliability

The new integral range validation changes coercion behavior (now throwing JsonException instead of
silently overflowing) but no new/updated unit tests are included to lock in the expected behavior.
This risks regressions and leaves key edge cases (fractional, out-of-range) unverified.
Agent Prompt
## Issue description
Behavior was changed to reject fractional/out-of-range coercions and to alter numeric representations, but unit tests were not updated/added to enforce the new expectations.

## Issue Context
The change is intended to prevent silent overflow for `Byte`/`Short`/`Integer`/`Long` coercions and to fail fast with `JsonException`.

## Fix Focus Areas
- java/src/org/openqa/selenium/json/NumberCoercer.java[85-131]
- java/src/org/openqa/selenium/json/JsonInput.java[257-269]
- java/test/org/openqa/selenium/json/JsonInputTest.java[74-79]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

}
}
20 changes: 20 additions & 0 deletions java/test/org/openqa/selenium/json/JsonTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,26 @@ void canReadANumber() {
assertThat((Double) new Json().toType("4.2e-1", Double.class)).isEqualTo(0.42);
}

@Test
void shouldRejectIntegerOverflow() {
Json json = new Json();

assertThatExceptionOfType(JsonException.class)
.isThrownBy(() -> json.toType("2147483648", Integer.class))
.withMessageContaining("out of range")
.withMessageContaining("Integer");
}

@Test
void shouldRejectFractionalValueForInteger() {
Json json = new Json();

assertThatExceptionOfType(JsonException.class)
.isThrownBy(() -> json.toType("1.2", Integer.class))
.withMessageContaining("Expected an integer value")
.withMessageContaining("Integer");
}

@Test
void canRoundTripNumbers() {
Map<String, Object> original = Map.of("options", Map.of("args", List.of(1L, "hello")));
Expand Down