For a field with an Integer PointsConfig, the parser converts every parsed literal to an int with Number.intValue() and never checks that it fits. A literal outside the int range keeps only its low 32 bits, so the bound of the range moves somewhere else: 2147483648 becomes -2147483648.
- The flexible
StandardQueryParser(org.apache.lucene.queryparser.flexible.standard) treats a field as numeric only if you register aPointsConfigfor it withsetPointsConfigMap: aNumberFormatthat parses the query text, and the type the field was indexed with (Integer,Long,FloatorDouble). - For such a field,
i:[5 TO *],i>=5andi:5become point queries (IntPoint.newRangeQuery) instead of term queries. An open end*becomes the type's limit, hereInteger.MAX_VALUE. - The
NumberFormatreturns a generalNumber(for2147483648and the integer format, aLong); the parser then converts it to the configured type.
intValue() turning it into -2147483648, the built query i:[-2147483648 TO 2147483647] and 1 hit where 0 are expected. Select the still to open it at full size. Generated with AI, checked against the code.- Configure the flexible
StandardQueryParserthe way its own test does: anIntegerPointsConfigwithNumberFormat.getIntegerInstance(Locale.ROOT)for fieldi. Index one document withIntPoint("i", 0). - Parse
i:[2147483648 TO *]: "i is at least 2,147,483,648".Integer.MAX_VALUEis 2,147,483,647, so no int qualifies: you expect 0 hits, or a parse error like Lucene's XML query parser gives for the same bound. - You get the query
i:[-2147483648 TO 2147483647], the whole int range, and 1 hit: the filter matches every document with the field. No error, no warning. - Why: the parser reads the literal as a
Longand converts it withNumber.intValue(), which keeps only the low 32 bits. 2147483648 becomes -2147483648 (Integer.MIN_VALUE); nothing checks the range. - Other forms are wrong too:
i:4294967296matches the document with 0, andi:[0 TO 3000000000]becomesi:[0 TO -1294967296]and drops every in-range document. A literal beyond the long range saturates at the int limit instead, which is wrong too. In the forms tested,Longfields saturate, so only a value at exactlyLong.MIN_VALUEorLong.MAX_VALUEis wrong.
Users of the flexible StandardQueryParser who register an Integer PointsConfig (opt-in; Luke's search tab builds one from its type map) and whose query strings can carry a number outside the int range, such as a millisecond timestamp or a 64-bit id typed into an int field. One document is enough, and the wrong result is silent.
Mechanism, terms and numbers538 words
With a PointsConfig of type Integer, the flexible StandardQueryParser parses each point literal with the configured NumberFormat and converts it with Number.intValue() without checking that it fits in an int (PointRangeQueryNodeProcessor.java:109-111, PointQueryNodeProcessor.java:94-95 on main 645d4fe14a20). For 2147483648 the NumberFormat returns a Long, and intValue() keeps its low 32 bits: -2147483648. So i:[2147483648 TO *], which no int satisfies, parses to i:[-2147483648 TO 2147483647] and matches every document with the field, silently. i:4294967296 matches the document with 0, and i:[0 TO 3000000000] becomes i:[0 TO -1294967296] and returns none of the in-range documents. A literal beyond the long range saturates at the int limit instead, and in the forms tested Long fields saturate too, so there only a document at exactly the limit is wrong. The unchecked narrowing was written in 2011 (LUCENE-1768, first released in 3.4.0) and copied into the points processors in 2016 (LUCENE-7089, 6.0.0); every release up to 10.6.0 and main are affected. Rejecting a literal outside the type's range, as Lucene's XML query parser already does, fixes it; clamping the bound instead keeps the range semantics.
All lines are on main 645d4fe14a20, under lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/.
- Pipeline.
StandardQueryNodeProcessorPipeline.java:53-55runsOpenRangeQueryNodeProcessor(turns*into an empty bound), thenPointQueryNodeProcessorandPointRangeQueryNodeProcessor. - Parse.
PointRangeQueryNodeProcessor.java:79-107parses each non-empty bound with the configuredNumberFormat(:82,:97); an unparseable literal throwsQueryNodeParseException(:84-91,:99-106). For2147483648the integerDecimalFormatreturns aLong; the JDK documents that it returns most integer values as aLongand a value that does not fit in a long as aDouble(DecimalFormat.java:2202-2208). - Narrow, unchecked. For an
Integertype,:109-111doupperNumber = upperNumber.intValue(); lowerNumber = lowerNumber.intValue();.Long.intValue()keeps the low 32 bits: 2147483648 becomes -2147483648, 4294967296 becomes 0, 3000000000 becomes -1294967296. A literal beyond the long range is aDouble, andDouble.intValue()saturates:i:[99999999999999999999 TO *]becomesi:[2147483647 TO 2147483647]and matches a document at exactlyInteger.MAX_VALUE. - Build.
builders/PointRangeQueryNodeBuilder.java:63-79casts toInteger, maps the empty upper bound toInteger.MAX_VALUE(:73-75) and callsIntPoint.newRangeQuery("i", -2147483648, 2147483647)(:79): the whole int range. - Single value.
PointQueryNodeProcessor.java:83parses and:94-95narrow the same way;:115builds[v TO v]. - Long fields.
PointRangeQueryNodeProcessor.java:112-114andPointQueryNodeProcessor.java:96-97calllongValue(). Beyond the long range theNumberFormatreturns aDouble, andDouble.longValue()saturates:l:[2^63 TO *]becomes[Long.MAX_VALUE TO Long.MAX_VALUE]and matches only a document at exactly that value. Executed with the integer format in fourLongforms (three ranges and one single value); other formats were not tested.
Five documents with i = Integer.MIN_VALUE, -5, 0, 5, Integer.MAX_VALUE (and l at the long equivalents), main 645d4fe14a20, assertions on and off:
| query | parses to | hits | expected |
|---|---|---|---|
i:[2147483648 TO *], i>=2147483648 | i:[-2147483648 TO 2147483647] | 5 | 0 |
i:[* TO -2147483649], i<="-2147483649" | i:[-2147483648 TO 2147483647] | 5 | 0 |
i:2147483648 | i:[-2147483648 TO -2147483648] | 1 | 0 |
i:4294967296 | i:[0 TO 0] | 1 | 0 |
i:[0 TO 3000000000] | i:[0 TO -1294967296] | 0 | 3 |
i<=3000000000 | i:[-2147483648 TO -1294967296] | 1 | 5 |
l:[2^63 TO *] (written in full) | l:[Long.MAX_VALUE TO Long.MAX_VALUE] | 1 | 0 |
In-range literals, including the limits themselves (i:2147483647, i:"-2147483648", i:[-2147483648 TO 2147483647]), are correct.
Exposure and likelihood203 words
- Who is hit. Callers of
setPointsConfigMapwith anIntegertype (or, in the narrow saturating form,Long). The parser is opt-in, but the queryparser overview calls it "probably a good place to start" for new users (overview.html:38-40). Lucene's index browser Luke builds exactly thisPointsConfigfrom its type map and parses the user's query string (SearchImpl.java:221-244). One downstream application, IPED, usesPointsConfig(its issue #510). The study found noPointsConfiguse in Elasticsearch or Solr. - What goes wrong. Silent wrong results in both directions: a bound past the near end of the int range fails open and matches every document with the field; a bound past the far end drops in-range documents (
i:[0 TO 3000000000]returns 0 of 3); a single out-of-range value matches an unrelated value (i:4294967296matches 0). Deterministic, with assertions on or off. - What it takes. One document, one query string, the default codec. The literal must be outside the int range: realistic accidents are a value from another domain typed into an int field (a 13-digit millisecond timestamp, a 64-bit id, a phone number) or an upper bound picked "big enough".
- How likely: rare. Normal queries use values inside the field's domain.
One JUnit method on the public API, in the parser's package like the upstream tests: one document with IntPoint("i", 0), the upstream test's Integer PointsConfig, and parse("i:[2147483648 TO *]"). The assertion message prints the parsed query. Fails with assertions on and off.
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.queryparser.flexible.standard;
import java.text.NumberFormat;
import java.util.Locale;
import java.util.Map;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.IntPoint;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.queryparser.flexible.standard.config.PointsConfig;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.store.ByteBuffersDirectory;
import org.apache.lucene.store.Directory;
import org.apache.lucene.tests.util.LuceneTestCase;
public class TestPointRangeOutOfIntRange extends LuceneTestCase {
public void testLowerBoundAboveIntMax() throws Exception {
try (Directory dir = new ByteBuffersDirectory()) {
try (IndexWriter w = new IndexWriter(dir, new IndexWriterConfig())) {
Document doc = new Document();
doc.add(new IntPoint("i", 0));
w.addDocument(doc);
}
StandardQueryParser parser = new StandardQueryParser();
parser.setPointsConfigMap(
Map.of("i", new PointsConfig(NumberFormat.getIntegerInstance(Locale.ROOT), Integer.class)));
Query query = parser.parse("i:[2147483648 TO *]", "i");
try (DirectoryReader reader = DirectoryReader.open(dir)) {
// expected: no int value is >= 2^31, so 0 hits; actual: 2147483648 wraps to Integer.MIN_VALUE
assertEquals(query.toString(), 0, new IndexSearcher(reader).count(query));
}
}
}
}
cp $REPRO/TestPointRangeOutOfIntRange.java $LUCENE/lucene/queryparser/src/test/org/apache/lucene/queryparser/flexible/standard/
cd $LUCENE && ./gradlew :lucene:queryparser:test --tests org.apache.lucene.queryparser.flexible.standard.TestPointRangeOutOfIntRange -Ptests.seed=DEADBEEF
- Expected
- 0 hits: no int is at least 2147483648 (or a clean parse error).
- Observed
- 1 hit:
AssertionError: i:[-2147483648 TO 2147483647] expected:<0> but was:<1>at line 49.
More files, environment, full output235 words
TestPointRangeOutOfIntRange (53 lines with the license header, one test method, public API only). It fails at the count assertion on line 49; the message is the parsed query, which shows the wrap before any search runs, so the test framework's random codec and locale do not matter (the NumberFormat uses Locale.ROOT).
As a regression test this method, with the same configuration and query, can go into lucene/queryparser/src/test/org/apache/lucene/queryparser/flexible/standard/ unchanged if the fix keeps the range semantics. With the recommended fix (reject the literal) its count assertion becomes expectThrows(QueryNodeParseException.class, () -> parser.parse("i:[2147483648 TO *]", "i")), and the document, index and searcher are no longer needed (35 lines).
The same test fails on the 10.6.0, 10.5.2 and 9.12.3 release jars (standalone runner), and on 9.1.0, the oldest release whose test framework it compiles against.
| lucene | main 645d4fe14a20 (11.0.0-SNAPSHOT): fails; also fails on the 10.6.0, 10.5.2, 9.12.3 and 9.1.0 release jars |
|---|---|
| jdk | OpenJDK 25.0.3 (build 25.0.3+9-LTS) |
| os | Linux aarch64 (plain integer conversion, nothing platform-specific) |
cp $REPRO/TestPointRangeOutOfIntRange.java $LUCENE/lucene/queryparser/src/test/org/apache/lucene/queryparser/flexible/standard/
cd $LUCENE && ./gradlew :lucene:queryparser:test --tests org.apache.lucene.queryparser.flexible.standard.TestPointRangeOutOfIntRange -Ptests.seed=DEADBEEF
# gradle, main 645d4fe14a20, -Ptests.seed=DEADBEEF
TestPointRangeOutOfIntRange > testLowerBoundAboveIntMax FAILED
java.lang.AssertionError: i:[-2147483648 TO 2147483647] expected:<0> but was:<1>
at org.apache.lucene.queryparser.flexible.standard.TestPointRangeOutOfIntRange.testLowerBoundAboveIntMax(TestPointRangeOutOfIntRange.java:49)
# standalone runner, stock jars, seed DEADBEEF
main 645d4fe14a20 -ea / -da tests_run=1 failures=1 same message
10.6.0 -ea / -da tests_run=1 failures=1 same message
10.5.2 -ea / -da tests_run=1 failures=1 same message
9.12.3 -da tests_run=1 failures=1 same message- The parser documents a range by the values it selects; no int is at least 2147483648, yet every document matches.
- The same processors reject an unparseable literal with
QueryNodeParseException; Lucene's XML query parser rejects this exact bound. - No javadoc, test or changes entry allows narrowing; upstream tests use only in-range literals.
The full argument325 words
- Contract.
StandardQueryParser.java:145-158: "inclusive range; selects documents whosenamefield has any value between Jones and Smith, including boundaries" and "one-sided range; selects documents whosescorefield is larger than 2.5".PointsConfig.java:39,:57: the type is "the numeric type used to index the numeric values", with no precondition on literals. No javadoc, package doc, CHANGES or MIGRATE entry mentions narrowing or out-of-range literals. Equally, nothing says what should happen to a literal the type cannot represent: the javadoc rules out the observed result but does not choose between an error and clamping. - The same code rejects a literal it cannot parse. A literal from which the configured
NumberFormatcannot read a number throwsQueryNodeParseException(COULD_NOT_PARSE_NUMBER)(PointRangeQueryNodeProcessor.java:84-91,:99-106;PointQueryNodeProcessor.java:85-92); an empty one throwsNUMERIC_CANNOT_BE_EMPTY(PointQueryNodeProcessor.java:104-108). Otherwise the processors are as lenient as the format:NumberFormat.parse(String)throws only when no number can be read at the start of the text, and the integer format stops at a decimal point, soi:12abcparses toi:[12 TO 12],i:1.5toi:[1 TO 1]andi:[1.5 TO 3.9]toi:[1 TO 3](executed on main). A maintainer may see the narrowing as part of that leniency. - The sibling XML query parser rejects this bound.
xml/builders/PointRangeQueryBuilder.java:69-70: "AParserExceptionwill be thrown if an error occurs parsing the suppliedlowerTermorupperTerminto the numeric type specified bytype"; it usesInteger.parseInt(:86-87). On main,<PointRangeQuery fieldName="i" type="int" lowerTerm="2147483648"/>throws "Could not parse lowerTerm or upperTerm into a number". - Tests. Upstream
TestPointQueryParser.java:33-84uses only 1, 3, 1.5 and 3.6, with exactly the repro's configuration. No queryparser test uses 2147483648, 4294967296,MAX_VALUEor "overflow" with aPointsConfig. - History. The unchecked
intValue()/longValue()came with LUCENE-1768 (#2842, 2011) for the legacy numeric processors and was copied by LUCENE-7089 (#8145, 2016), which added points "the exact same way legacy numerics works". Neither discussion mentions out-of-range literals, overflow orintValue.
The devil's-advocate check183 words
The strongest case for "not a bug": the literal lies outside the field's int domain, so Integer.class implicitly limits the input and the result is unspecified, a validation gap rather than a contract violation. A verifier of the sibling bug LFV-050 read it this way first, and the reviewer from another model family rated the argument moderate. Add to it that the processors inherit the format's general leniency (above), and that PointsConfig takes any caller-supplied NumberFormat, so an application can plug in a stricter one; the 2011 design discussion already left conversions to a caller-supplied NumberFormat (#2842).
Why it still fails: the parser accepts the literal without error and returns a query that matches values the query text excludes; the same processors reject a literal they cannot parse, and the XML parser rejects this one; with the stock configuration (upstream's test, Luke) an application can only avoid it by validating literals itself or writing a stricter NumberFormat; and the under-match form is a meaningful int query: i:[0 TO 3000000000] contains the documents 0, 5 and Integer.MAX_VALUE and returns none of them.
Recommended Reject a parsed literal outside the type's range
Before the narrowing, reject a parsed literal outside the Integer or Long range with QueryNodeParseException, as the XML query parser does (+34 lines in the two processors). In-range literals are unchanged; the whole :lucene:queryparser suite passes. Trade-off: i:[0 TO 3000000000] then fails to parse instead of returning the in-range documents.
All options and trade-offs3 options · 728 words
Two fixes were validated with gradle in a scratch worktree at 645d4fe14a20 (JDK 25, -Ptests.seed=DEADBEEF); the third option is the candidate step's causal check.
PointRangeQueryNodeProcessor.java checks both parsed bounds before :109; PointQueryNodeProcessor.java calls the same helper before :94., +34 lines, two files (with one code comment reworded after the fact check).
static void checkFitsType(Number number, Class<? extends Number> type, String text)
throws QueryNodeParseException {
final double value = number.doubleValue();
final boolean fits;
if (Integer.class.equals(type)) {
fits = value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE;
} else if (Long.class.equals(type)) {
// a DecimalFormat returns a value that does not fit in a long as a Double
fits = (number instanceof Double == false && number instanceof Float == false)
|| (value > -0x1p63 && value < 0x1p63);
} else {
fits = true;
}
if (fits == false) {
throw new QueryNodeParseException(
new MessageImpl(QueryParserMessages.COULD_NOT_PARSE_NUMBER, text, type.getName()));
}
}Trade-offs
- Smallest change; in-range literals, including the limits, are unchanged; matches the XML query parser and the processors' own error for unparseable numbers; independent of LFV-050.
- A range with one bound past the far end, such as
i:[0 TO 3000000000], now fails to parse instead of returning the in-range documents. Applications that send such queries get an error instead of a wrong answer. - The check covers what a
DecimalFormatreturns (the configuration of upstream's test and of Luke): onIntegerfields it comparesdoubleValue()with the int limits, which decides correctly for any integral value; onLongfields it range-checks onlyDoubleandFloatresults, so a customNumberFormatthat returns aBigDecimalorBigIntegerbeyond the long range is not caught. It is not a general literal validation: the format's own leniency (trailing text, a dropped fraction) stays. ReusingCOULD_NOT_PARSE_NUMBERwith the type name is a choice; a dedicated "out of range" message may read better.
What the checks validated
- Gradle:
:lucene:queryparser:checkGoogleJavaFormatpasses; the whole:lucene:queryparser:testsuite passes, 534 tests in 31 suites, 1 skipped, 0 failures, with the regression test (the minimal test withexpectThrows) andTestPointQueryParser(4 tests). - An independent verifier's five-test literal matrix (its oracle accepts a clean parse error): 5 of 5 pass; every in-range control is unchanged and every out-of-range row is rejected, for example
Could not parse text "2147483648" using java.lang.Integer. - The unchanged minimal test then fails with that exception, by design, hence the
expectThrowsform of the regression test. - Re-run with an unpublished file on a fresh scratch worktree of
645d4fe14a20:checkGoogleJavaFormatand the whole:lucene:queryparser:testsuite pass, 534 tests in 31 suites, 1 skipped, 0 failures; the unchanged minimal test fails with the rejection.
Keep the range semantics: clamp the bound, match nothing beyond the limit (with LFV-050's builder fix) (validated by the checks)
In the processors, a bound beyond the type's range is clamped to the limit, and the limit is excluded when no value of the type can satisfy the bound: i:[2147483648 TO *] becomes i:{2147483647 TO *], i:[0 TO 3000000000] becomes i:[0 TO 2147483647]. The builder must then turn an exclusive bound at the limit into MatchNoDocsQuery, which is the fix of the sibling bug LFV-050 (it also makes an open bound inclusive). +54/-4 in the processors, +13 in the builder.
Trade-offs
- Correct answers instead of an error, the unchanged minimal test is the regression test, and it fixes LFV-050 as well.
- Larger, couples the two bugs, and changes the parsed query of every out-of-range literal. Without the builder part it does not work: the builder's
lower + 1wrapsInteger.MAX_VALUEand the minimal test still fails with the same message.
What the checks validated
- Gradle, same worktree: the unchanged minimal test passes; the literal matrix gives the exact count on every row (
i:[2147483648 TO *]andi:4294967296buildMatchNoDocsQuery;i:[0 TO 3000000000]buildsi:[0 TO 2147483647], 3 hits);checkGoogleJavaFormatand the whole:lucene:queryparser:testsuite pass, 534 tests, 0 failures. The processor part alone: the minimal test still fails.
Replace intValue() with a clamp to [Integer.MIN_VALUE, Integer.MAX_VALUE].
Trade-offs
Incomplete: i:[2147483648 TO *] becomes i:[2147483647 TO 2147483647] and matches a document at exactly Integer.MAX_VALUE, the gap the Long sites already have; the one-document repro passes only because its value is 0. The single-value processor is not covered either.
An application that builds restriction filters from user-supplied query strings with this parser fails open: a number outside the int range, such as i:[2147483648 TO *], makes the filter match every document with the field.
Code reading. A verifier checking the sibling bug LFV-050 (an exclusive bound at the int limit wraps in the builder) noticed that the processors narrow the parsed number without a range check; a follow-up executed it. The hunt then reproduced it on main and old releases, with an independent tie-break and a history sweep.
The method in full135 words
Method: non-formal. The 2026-10-22 study verified LFV-050, a finding of its BKD and points proof experiment, on main 7cecf79523. While reading PointRangeQueryNodeProcessor, its verifier noted that the parsed Number is narrowed with intValue() without a range check, and called it "a validation gap rather than a clean contract violation".
- Study follow-up 4 executed it with its own four-literal test, found all four wrong, and showed that LFV-050's fixes do not change it: a separate root cause.
- The hunt's candidate step reduced it to the one-method test and ran it on main and three releases; a review by another model family could not run code; a tie-break wrote its own literal matrix and
main()probe, confirmed the root cause at both processor sites, and found the under-match form; a history sweep ran 136 releases.
No prior report found (2026-10-30, re-checked) in GitHub issues, PRs and commits including the JIRA archive, the mailing lists, downstream projects, Stack Overflow or the web. Related: LUCENE-1768 (#2842) wrote the narrowing and LUCENE-7089 (#8145) copied it; neither discusses out-of-range literals.
Searches4 searches
Closest items, none about this bug: #2842 (LUCENE-1768), #8145 (LUCENE-7089), #7909 (any Format in NumericConfig), #7865 / #14697 (toQueryString of range nodes), #4411, #4416, #5207 (open ranges, the >= syntax), IPED #510 (an application's thread-safety bug around PointsConfig). Context: open #16820 (2026-10-12) lists the flexible and XML query parsers among possible deprecations for Lucene 11. Limits: private reports to the security list are not visible; GraphQL search does not cover PR review comments.
| Where | When | How | Found |
|---|---|---|---|
| Search tool (GitHub issues/PRs/commits incl. the JIRA archive, dev@, java-user@, issues@, commits@, downstream, web) | 2026-10-30 | 109 queries: the processor, builder and config class names (also the legacy NumericConfig ones), PointsConfig or StandardQueryParser with overflow, out of range or intValue, 2147483648, matches all documents | 252 distinct hits, 0 high and 12 medium overlap, none about this bug |
| GitHub GraphQL search (apache/lucene, lucene-solr, Lucene.NET, Solr, Elasticsearch, OpenSearch, all of GitHub) | 2026-10-30 | 124 phrasings, including recent-activity queries | no report |
| Open PRs and file history | 2026-10-30 | 410 open PRs by changed files; commits on both processors | no PR touches the point processors, the builder or PointsConfig |
| GitHub, re-check | 2026-10-30 | issue search for PointRangeQueryNodeProcessor; open PRs mentioning PointsConfig; last commits of both processors | 0 and 0; both files unchanged on upstream main 716d6f216f52 |
Written in LUCENE-1768 (c178c8eb1a81, 2011; first released in 3.4.0) for the legacy numeric config and copied into the points processors by LUCENE-7089 (89cc676f2bf5, 2016; 6.0.0). Every release from 3.4.0 to 10.6.0 (136 run) and main is affected.
Versions tested7 rows
Introduced by c178c8eb1a81, LUCENE-1768 "NumericRange support for new query parser" (trunk, 2011-07-09), shipped first through the branch_3x port f028bdb3eb20 (2011-08-18) in 3.4.0. Copied into PointRangeQueryNodeProcessor and PointQueryNodeProcessor by 89cc676f2bf5, LUCENE-7089 (2016-03-09; ports 306d3fe85240 on branch_6_0 and c8543efacf43 on branch_6x), first released in 6.0.0. The legacy path was removed in 7.0.0 (LUCENE-7418); the LUCENE-9570 reformat and #15889 left the narrowing as is. Old releases ran on JDK 25.
| Version or commit | Verdict | Result |
|---|---|---|
main 645d4fe14a20 | affected | the minimal test fails (gradle; standalone -ea and -da) |
| 10.6.0, 10.5.2 | affected | the minimal test fails (-ea and -da) |
| 9.12.3, 9.1.0 | affected | the minimal test fails |
| 6.0.0 to 10.6.0 | PointsConfig | a main() port of the same case fails on every release |
| 3.4.0 to 6.6.6 | legacy NumericConfig (LegacyNumericConfig in 6.x) | i:[2147483648 TO 4294967296] parses to i:[-2147483648 TO 0], 1 hit instead of 0 |
| 3.3.0 and older | not affected | no numeric field configuration in the flexible parser |
fixes A and B (scratch worktree at 645d4fe14a20) | not affected | A rejects the literal, B returns 0 hits; the whole queryparser suite passes with either |
A study follow-up, the candidate reproducer, an independent tie-break with its own tests and a 136-release history sweep all reproduce it; the reviewer from another model family could not run code and agreed on the root cause by reading.
Checks and verdicts276 words
The cross-model review returned a procedural "not real" because it could not execute anything. On substance it agreed on the root cause, found the inputs in contract and listed 9 of 10 evidence items for real; the tie-break answered its one counter-argument (an implicit int domain) with the under-match rows.
| Check | Verdict | What it found |
|---|---|---|
| study follow-up 4 | new, separate root cause | Its four-literal test on main 7cecf79523: all wrong; LFV-050's fixes do not change it. |
| candidate reproducer | real | The minimal test on main (-ea, -da), 10.5.2 and 9.12.3; a probe of 11 literal forms; a causal clamp. |
| cross-model review, refute lens (OpenAI GPT-5.6) | procedural "not real", low | Could not run code; by reading: root cause agrees, inputs in contract, 9 of 10 evidence items for real. |
| Second adjudication | real, high | Own five-test matrix on main (-ea with two seeds, -da), 10.6.0 (-ea, -da), 10.5.2 and 9.12.3 (-ea); main() probe on 6.0.0 to 10.6.0; root cause at both sites; independent of LFV-050 in both directions. |
| history sweep | affected since 3.4.0 | 136 releases on both configuration paths. |
| this doc | gradle repro and two fixes | Ran the minimal test with gradle on a clean 645d4fe14a20 (fails) and validated fixes A and B with the whole queryparser suite. |
| fact checks of this doc, one from another model family (OpenAI GPT-5.6, read-only) | sound; wording corrected | One re-ran the gradle repro (-ea, -da), fix A with the whole queryparser suite and a probe of the saturate-only option; both found the bug real as described. |
The diagram as a still image, generated with AI and checked against the code. It is under 10 MB, the limit for a GitHub attachment.
- PNG 475 KB
Finding record, Lucene and upstream sources, repro files and diagram notes599 words
- Registry title
- Flexible StandardQueryParser narrows out-of-range int literals with intValue(), so i:[2147483648 TO *] matches every document with the field
- Area
- queryparser / flexible
- Priority
- Low, rank 533: Moved down from Medium: with the opt-in flexible parser and an Integer PointsConfig, an out-of-range literal (a timestamp typed into an int field) wraps so a range filter fails open and matches every document, silent, but the parser config is rare and no Elasticsearch or Solr path uses it.
- Impact class
- silent wrong results
- Impact note
- Users of the opt-in flexible StandardQueryParser with an Integer PointsConfig whose query string carries a literal outside the int range; ordinary scale; the same lines are in the 10.5.2 source (main executed).
- Summary
- You parse a point query whose literal lies outside the field's int range, such as i:[2147483648 TO *], i>=2147483648 or i:2147483648, with the flexible StandardQueryParser and an Integer PointsConfig. You expect no match, because no int value reaches that bound. Instead PointRangeQueryNodeProcessor and PointQueryNodeProcessor narrow the parsed number with Number.intValue(), which wraps it, so the range queries match all 5 documents of the repro and i:2147483648 matches the Integer.MIN_VALUE document.
- Introduced
- unknown (not yet bisected) (unknown (not yet bisected))
- First affected version
- unknown (not yet bisected)
- Affected versions
- main [exec]; the same lines are in the 10.5.2 source [read, registry build]
- Status
- documented (brief, checks and a per-finding doc exist)
- Verification
- Confirmed by AI-run checks: a cross-model review that could only read the code (its low-confidence “not real” is procedural), an adjudication that ruled it real and a prior-report search; not yet triaged by a person.
- Found by
- non-formal: code reading
- Upstream and prior reports
- none found (follow-up 4 read-only gh searches, 2026-10-22)
- Lucene commit tested
- main
645d4fe14a20: the minimal test through gradle (fails) and the standalone runner (-ea,-da); the 10.6.0, 10.5.2 (-ea,-da) and 9.12.3 release jars; an independent verifier's own five-test literal matrix on main, 10.6.0, 10.5.2 and 9.12.3 and itsmain()probe on 6.0.0 to 10.6.0; a history sweep of 136 releases (3.4.0 to 10.6.0); two fixes in a scratch worktree at645d4fe14a20with the whole:lucene:queryparsertest suite.
- Finding record
a Lucene clone tag releases/lucene/10.5.2: lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/processors/PointRangeQueryNodeProcessor.java:111-112SECURITY.md:38, 50 (threat model)
- The finding in 30 seconds
a Lucene main checkout (645d4fe14a20) lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/processors/StandardQueryNodeProcessorPipeline.java:53-55a Lucene main checkout (645d4fe14a20) lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/processors/PointRangeQueryNodeProcessor.java:79-114 (parse :82, :97; errors :84-91, :99-106; intValue :109-111; longValue :112-114)a Lucene main checkout (645d4fe14a20) lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/processors/PointQueryNodeProcessor.java:83-97, :104-108, :115a Lucene main checkout (645d4fe14a20) lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/builders/PointRangeQueryNodeBuilder.java:63-79
- Who is affected
a Lucene main checkout (645d4fe14a20) lucene/queryparser/src/java/overview.html:38-40a Lucene main checkout (645d4fe14a20) lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchImpl.java:199-249 (PointsConfig :221-240, parse :244)
- Why it looks unintended
a Lucene main checkout (645d4fe14a20) lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/StandardQueryParser.java:145-158a Lucene main checkout (645d4fe14a20) lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/config/PointsConfig.java:35-40, :56-63a Lucene main checkout (645d4fe14a20) lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/processors/PointRangeQueryNodeProcessor.java:84-91, :99-106; processors/PointQueryNodeProcessor.java:85-92, :104-108a Lucene main checkout (645d4fe14a20) lucene/queryparser/src/java/org/apache/lucene/queryparser/xml/builders/PointRangeQueryBuilder.java:69-70, :83-87, :106-108a Lucene main checkout (645d4fe14a20) lucene/queryparser/src/test/org/apache/lucene/queryparser/flexible/standard/TestPointQueryParser.java:33-84a Lucene main checkout (645d4fe14a20) lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/config/PointsConfig.java:38-45, :84-94 (any caller-supplied NumberFormat)OpenJDK 25.0.3 src java/text/NumberFormat.java:460-475, :499-507, :509-515 (parse(String) reads a leading number; integer-only parsing stops at the decimal point)
- Diagrams
a Lucene main checkout (645d4fe14a20) lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/processors/PointRangeQueryNodeProcessor.java:109-111
- TestPointRangeOutOfIntRange.java (lucene/queryparser, package org.apache.lucene.queryparser.flexible.standard, 1 test):
repro/LFV-082/src/org/apache/lucene/queryparser/flexible/standard/TestPointRangeOutOfIntRange.java(53 lines)
A summary still, made in parallel with this doc from the code at 645d4fe14a20 and the executed runs.
assets/LFV-082/bug.png(475 KB)
![Summary of the bug: the query i:[2147483648 TO *] on a field with an Integer PointsConfig; the NumberFormat returns the Long 2147483648, intValue() turns it into -2147483648 (Integer.MIN_VALUE); the built query is i:[-2147483648 TO 2147483647] and the one document with i = 0 matches, where 0 hits are expected.](../assets/LFV-082/bug.png)