Skip to content

[java] Fix silent overflow when coercing JSON numbers to integral types#17177

Open
seethinajayadileep wants to merge 4 commits intoSeleniumHQ:trunkfrom
seethinajayadileep:fix-numbercoercer-overflow
Open

[java] Fix silent overflow when coercing JSON numbers to integral types#17177
seethinajayadileep wants to merge 4 commits intoSeleniumHQ:trunkfrom
seethinajayadileep:fix-numbercoercer-overflow

Conversation

@seethinajayadileep
Copy link
Contributor

@seethinajayadileep seethinajayadileep commented Mar 5, 2026

💥 What does this PR do?

This PR fixes a silent overflow issue when coercing JSON numbers into Java integral types (Byte, Short, Integer, Long).

Previously, NumberCoercer relied on Number#byteValue, shortValue, intValue, and longValue through the mapper. These methods silently overflow when the numeric value is outside the target type range.

Examples of the previous incorrect behavior:

  • "2147483648" coerced to Integer resulted in -2147483648
  • "100000" coerced to Short resulted in an incorrect wrapped value

This change adds validation before applying the mapper:

  • Convert the parsed number to BigDecimal
  • Use toBigIntegerExact() to ensure the value is an exact integer
  • Validate that the value is within the bounds of the target type
  • Throw a JsonException if the value is fractional or out of range

This prevents silent numeric corruption during JSON deserialization.


🔧 Implementation Notes

The validation logic was added inside NumberCoercer before applying the numeric mapper.

Implementation steps:

  1. Convert the parsed Number to BigDecimal
  2. Use toBigIntegerExact() to reject fractional values
  3. Check the numeric range against the target type (Byte, Short, Integer, Long)
  4. Throw JsonException if the value is outside the allowed bounds

🧪 Tests

Unit tests were added to verify the new behavior:

  • Out-of-range values for integral types throw JsonException
  • Fractional values cannot be coerced to integral types

These tests ensure the overflow validation remains enforced.


🔄 Compatibility

JsonInput#nextNumber() behavior was preserved to continue returning Double values,
avoiding changes to existing callers that rely on the current runtime type.

This change applies to the Java binding. Other Selenium bindings may handle
numeric coercion differently and can be aligned in follow-up changes if needed.


💡 Additional Considerations

This change only affects invalid numeric inputs that previously produced incorrect wrapped values. Valid numeric inputs continue to behave the same.


🔄 Types of changes

  • Bug fix (backwards compatible)

@qodo-code-review
Copy link
Contributor

Review Summary by Qodo

Fix silent overflow when coercing JSON numbers to integral types

🐞 Bug fix

Grey Divider

Walkthroughs

Description
• Prevents silent overflow when coercing JSON numbers to integral types
• Validates numeric values are within target type bounds before coercion
• Rejects fractional values for integral type targets
• Returns BigDecimal instead of double from nextNumber() for precision

Grey Divider

File Changes

1. java/src/org/openqa/selenium/json/JsonInput.java 🐞 Bug fix +1/-1

Return BigDecimal instead of double for precision

• Changed nextNumber() to return BigDecimal instead of converting to double
• Preserves numeric precision for downstream validation and coercion

java/src/org/openqa/selenium/json/JsonInput.java


2. java/src/org/openqa/selenium/json/NumberCoercer.java 🐞 Bug fix +46/-0

Add integral range validation before numeric coercion

• Added validateIntegralRange() method to validate numeric bounds before coercion
• Converts numbers to BigDecimal and uses toBigIntegerExact() to reject fractional values
• Checks if value is within min/max bounds for target integral type (Byte, Short, Integer,
 Long)
• Throws JsonException for out-of-range or fractional values instead of silent overflow
• Added imports for BigDecimal and BigInteger

java/src/org/openqa/selenium/json/NumberCoercer.java


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Mar 5, 2026

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (4) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Overflow fix lacks tests 📘 Rule violation ⛯ Reliability
Description
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.
Code

java/src/org/openqa/selenium/json/NumberCoercer.java[R85-130]

+      validateIntegralRange(number, stereotype);
   return mapper.apply(number);
 };
}
+
+  private static void validateIntegralRange(Number number, Class<?> stereotype) {
+    // Only for integral targets
+    if (!(stereotype == Byte.class
+        || stereotype == Short.class
+        || stereotype == Integer.class
+        || stereotype == Long.class)) {
+      return;
+    }
+
+    final BigInteger value;
+    try {
+      // Parses "2147483648", "1e3", "1.0" safely; rejects "1.2"
+      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);
+    }
Evidence
Compliance requires adding/updating tests for behavior changes. The PR introduces a new validation
path that throws on fractional/out-of-range values, and existing tests indicate behavior
expectations around numeric parsing that will need updating and additional coverage.

AGENTS.md
java/src/org/openqa/selenium/json/NumberCoercer.java[85-130]
java/test/org/openqa/selenium/json/JsonInputTest.java[74-79]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## 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



Remediation recommended

2. nextNumber() no longer Double📘 Rule violation ✓ Correctness
Description
JsonInput#nextNumber() now returns a BigDecimal for decimal numbers, changing the concrete
runtime type and breaking callers/tests that rely on a Double. This is a user-observable behavior
change that may break compatibility for public JSON parsing APIs.
Code

java/src/org/openqa/selenium/json/JsonInput.java[266]

+      return new BigDecimal(builder.toString());
Evidence
Compliance requires avoiding breaking changes to public interfaces/behavior. The PR changes
nextNumber() from returning doubleValue() (a Double) to returning a BigDecimal, and existing
tests/callers demonstrate reliance on Double for decimal inputs.

AGENTS.md
java/src/org/openqa/selenium/json/JsonInput.java[266-266]
java/test/org/openqa/selenium/json/JsonInputTest.java[74-79]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`JsonInput#nextNumber()` now returns `BigDecimal` for decimal values, which can break callers that expect a `Double` (including existing tests casting to `Double`).
## Issue Context
This is a user-visible behavior change in JSON parsing; compliance requires avoiding breaking changes unless explicitly justified and handled.
## Fix Focus Areas
- 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


3. No cross-binding parity note 📘 Rule violation ⛯ Reliability
Description
This PR changes user-visible JSON number coercion behavior in the Java binding but does not indicate
any comparison with at least one other Selenium binding or note parity follow-up work. This risks
inconsistent behavior across bindings for similar inputs.
Code

java/src/org/openqa/selenium/json/NumberCoercer.java[R85-130]

+      validateIntegralRange(number, stereotype);
   return mapper.apply(number);
 };
}
+
+  private static void validateIntegralRange(Number number, Class<?> stereotype) {
+    // Only for integral targets
+    if (!(stereotype == Byte.class
+        || stereotype == Short.class
+        || stereotype == Integer.class
+        || stereotype == Long.class)) {
+      return;
+    }
+
+    final BigInteger value;
+    try {
+      // Parses "2147483648", "1e3", "1.0" safely; rejects "1.2"
+      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);
+    }
Evidence
The compliance item requires cross-binding comparison/notes for user-visible behavior changes. The
PR description focuses on Java-only coercion changes and provides no mention of parity checks or
follow-up work for other bindings.

AGENTS.md
java/src/org/openqa/selenium/json/NumberCoercer.java[85-130]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Java JSON number coercion behavior changed (fail-fast on fractional/out-of-range), but there is no indication of cross-binding parity review or follow-up.
## Issue Context
Selenium aims for consistent user-visible behavior across language bindings where feasible.
## Fix Focus Areas
- java/src/org/openqa/selenium/json/NumberCoercer.java[85-130]

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


4. Direct nextNumber casts bypass validation 🐞 Bug ⛯ Reliability
Description
Some code still calls nextNumber().intValue()/longValue() directly, bypassing the new
overflow/fractional validation added to NumberCoercer. These paths can still silently
overflow/truncate and are now more likely to operate on BigDecimal values.
Code

java/src/org/openqa/selenium/json/NumberCoercer.java[R85-86]

+      validateIntegralRange(number, stereotype);
   return mapper.apply(number);
Evidence
NumberCoercer now validates integral ranges before applying mappers, but only when values are parsed
via coercion (e.g., input.read(Integer.class)). Call sites using input.nextNumber().intValue()
bypass this validation entirely.

java/src/org/openqa/selenium/json/NumberCoercer.java[60-87]
java/src/org/openqa/selenium/grid/data/CapabilityCount.java[63-90]
java/src/org/openqa/selenium/json/NumberCoercer.java[90-131]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
There are call sites converting numbers via `input.nextNumber().intValue()` / `.longValue()` directly. These conversions bypass the newly added `NumberCoercer` validation and can still silently overflow/truncate.
### Issue Context
The PR’s validation is implemented in `NumberCoercer` and is only applied when coercing to integral targets through the coercer pipeline (e.g., `input.read(Integer.class)`). Direct `nextNumber()` conversions do not benefit from this safeguard.
### Fix Focus Areas
- java/src/org/openqa/selenium/grid/data/CapabilityCount.java[63-90]
- java/src/org/openqa/selenium/json/NumberCoercer.java[60-131]

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



Advisory comments

5. validateIntegralRange comments are “what”📘 Rule violation ✓ Correctness
Description
New comments primarily restate what the code does (integral-only and parsing examples) instead of
capturing rationale/constraints. This reduces long-term maintainability and conflicts with the
guideline to document "why" rather than "what".
Code

java/src/org/openqa/selenium/json/NumberCoercer.java[R91-104]

+    // Only for integral targets
+    if (!(stereotype == Byte.class
+        || stereotype == Short.class
+        || stereotype == Integer.class
+        || stereotype == Long.class)) {
+      return;
+    }
+
+    final BigInteger value;
+    try {
+      // Parses "2147483648", "1e3", "1.0" safely; rejects "1.2"
+      BigDecimal bd =
+          (number instanceof BigDecimal) ? (BigDecimal) number : new BigDecimal(number.toString());
+      value = bd.toBigIntegerExact();
Evidence
The compliance item asks that comments explain intent/rationale. The added comments mostly describe
behavior already evident from the code, rather than why this validation approach is needed (e.g.,
preventing Number#intValue() overflow wrapping).

AGENTS.md
java/src/org/openqa/selenium/json/NumberCoercer.java[91-104]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New comments in `validateIntegralRange` mainly describe what the code does instead of why this validation exists.
## Issue Context
The motivation is to prevent silent overflow/wraparound when coercing JSON numbers to integral Java types.
## Fix Focus Areas
- java/src/org/openqa/selenium/json/NumberCoercer.java[91-104]

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


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@selenium-ci selenium-ci added the C-java Java Bindings label Mar 5, 2026
Comment on lines +85 to +130
validateIntegralRange(number, stereotype);
return mapper.apply(number);
};
}

private static void validateIntegralRange(Number number, Class<?> stereotype) {
// Only for integral targets
if (!(stereotype == Byte.class
|| stereotype == Short.class
|| stereotype == Integer.class
|| stereotype == Long.class)) {
return;
}

final BigInteger value;
try {
// Parses "2147483648", "1e3", "1.0" safely; rejects "1.2"
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);
}
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

@seethinajayadileep seethinajayadileep force-pushed the fix-numbercoercer-overflow branch from 69eff0d to 5ad714b Compare March 5, 2026 07:55
@seethinajayadileep seethinajayadileep changed the title Fix silent overflow when coercing JSON numbers to integral types [java] Fix silent overflow when coercing JSON numbers to integral types Mar 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C-java Java Bindings

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants