Bengali writes the conjunct ক্ষ (ক + ্ + ষ), found in everyday words such as ক্ষমা, as one joined letter; the paper the normalizer cites says it sounds like খ at the start of a word. Lucene's Bengali normalizer has a rule to rewrite it as খ, but the rule checks for the wrong third character.
ক্ষমা and খমা get different terms, and the one-character fix. Select the still to open it at full size. Generated with AI, checked against the code.- Index Bengali text with the stock
new BengaliAnalyzer()(orBengaliNormalizationFilter). Many words contain the conjunct ক্ষ, which is KA + HASANTA + SSA:ক্ষমা(forgiveness),পরীক্ষা(exam),শিক্ষা(education). - The normalizer's rule
// Khio (Ka + Hoshonto + Murdorno Sh)follows the paper its javadoc cites: ক্ষ becomes খ at the start of a word and কখ elsewhere. You expectক্ষমাto becomeখমাandপরীক্ষাto becomeপরিকখা, soক্ষমাandখমাboth index asখম. - You get
ক্ষমা→ক্সমাandপরীক্ষা→পরিক্সা. The condition compares the third character with U+09BF (vowel sign ি) instead of U+09B7 (ষ), one hex digit off. So the rule never fires, and only the separate rule ষ → স applies. The analyzer indexesক্সমforক্ষমাbutখমforখমা. - Instead, the rule fires on KA + HASANTA + VOWEL SIGN I, a sequence that well-formed text never contains:
ক্িমাbecomesখমা. There is no error, identical spellings still match each other (same analyzer version), and the result is the same with assertions on and off.
Every application that indexes Bengali with the stock BengaliAnalyzer, BengaliNormalizationFilter or the bengaliNormalization factory, on every release since 7.1.0. Every word with ক্ষ gets a term the rule would not produce, so the same word spelled with খ does not match it, and বক্ষ (chest) shares a term with বক্স (box). Identical spellings still match (same analyzer version).
Mechanism, terms and numbers481 words
BengaliNormalizer (package-private; used by BengaliNormalizationFilter, by BengaliAnalyzer at index and query time and by the bengaliNormalization factory) says it implements the algorithm of the paper "A Double Metaphone encoding for Bangla and its application in spelling checker" (UzZaman and Khan). Its rule // Khio (Ka + Hoshonto + Murdorno Sh) rewrites the conjunct ক্ষ (KA + HASANTA + SSA) as খ at the start of a word and as কখ elsewhere, mirroring the paper's two khio cases. But the condition tests the third character against '\u09BF' (BENGALI VOWEL SIGN I) instead of '\u09B7' (BENGALI LETTER SSA). So the rule never fires on correctly encoded ক্ষ: ক্ষমা normalizes to ক্সমা (the separate "All sh" rule turns ষ into স), and BengaliAnalyzer indexes it as ক্সম, while খমা is indexed as খম. The rule fires instead on the malformed KA + HASANTA + VOWEL SIGN I (ক্িমা becomes খমা). Every release from 7.1.0 to 10.5.2 and main has it (63 release tags read, 16 releases run). The fix is one character, and it changes the indexed term of every word with ক্ষ, so affected fields need a reindex; deleting the rule is the alternative that keeps today's terms.
- ক্ষ (khio): the conjunct KA U+0995 + HASANTA U+09CD + SSA U+09B7. A hasanta (virama) joins two consonants; a vowel sign such as ি (U+09BF) follows a consonant, never a hasanta, in well-formed text.
BengaliNormalizer: a left-to-right rewrite of each token, one rule per character (switch (s[i])). Its javadoc says it implements the cited paper. It is package-private and is reached throughBengaliNormalizationFilter, whichBengaliAnalyzeruses at index time and innormalize(), and through thebengaliNormalizationfactory.
- The rule is
case '\u0995':under// Khio (Ka + Hoshonto + Murdorno Sh)(BengaliNormalizer.java:56-68). "Murdorno Sh" is ষ, U+09B7. - Its condition is
s[i + 1] == '\u09CD' && s[i + 2] == '\u09BF'(:58). U+09BF is BENGALI VOWEL SIGN I, the code point that the DirghoI rule writes two cases earlier (:48). - Its branches are right: word-initial gives খ, otherwise কখ (
:59-66), the paper's two cases. - On correctly encoded ক্ষ the branch never matches, so the later "All sh" rule turns ষ into স (
:125-129): ক্ষ ends as ক্স. On the malformed ক্ি the branch does fire and folds it to খ (or কখ), dropping the vowel sign.
| input | as documented | stock filter | stock BengaliAnalyzer term |
|---|---|---|---|
ক্ষমা | খমা | ক্সমা | ক্সম (খমা gives খম) |
পরীক্ষা | পরিকখা | পরিক্সা | পরিক্স |
শিক্ষা | সিকখা | সিক্সা | সিক্স |
বক্ষ (chest) | বকখ | বক্স | বক্স, the same as বক্স (box) |
ক্িমা (malformed) | unchanged (not a khio input) | খমা | খম |
The hunt's bounded check (all 551,881 strings of length 0 to 4 over 27 rule characters) found the khio rule violated on all 28 inputs that start with ক্ষ.
Exposure and likelihood364 words
- Who is exposed.
BengaliAnalyzer(the default constructor included),BengaliNormalizationFilter, and thebengaliNormalizationfactory, for example in aCustomAnalyzer. No setting switches this rule alone; only keyword-marked tokens skip it, together with the whole normalizer (BengaliNormalizationFilter.java:48-49), for example words inBengaliAnalyzer's stem-exclusion set (BengaliAnalyzer.java:123-125) or after aSetKeywordMarkerFilter, as the filter javadoc suggests. - Ordinary input. ক্ষ occurs in everyday words:
ক্ষমা,পরীক্ষা,শিক্ষা,রক্ষা,বৃক্ষ,দক্ষিণand many more. Every such word gets a term the rule would not produce, at index and query time alike. - What the damage looks like. Silent: no error, and identical spellings still match (with the same analyzer version at index and query time). What fails is the match the rule exists for. In a six-document index (
ক্ষমা, খমা, ক্সমা, ক্িমা, বক্ষ, বক্স) the term query forক্ষমাmatchesক্ষমাandক্সমা, the one forখমাmatchesখমাandক্িমা, andবক্ষmatchesবক্ষandবক্স. With the fix,ক্ষমাandখমাboth matchক্ষমাandখমা, andবক্ষmatches only itself. - Both directions. Over 1,508,597 generated short strings (a synthetic set, not a corpus), the bug loses 36,270 conflation pairs that the rule would create and keeps 40,223 that the fix removes; the fix changes the output of 1,804 strings, each containing ক্ষ (902) or the malformed ক্ি (902).
- Scale. One word reproduces it; assertions on and off behave the same.
- Reindex. A fix that makes the rule fire changes the term of every ক্ষ word, so fields analyzed before the fix keep the old terms until they are reindexed. Until then even identical text misses: the document
ক্ষমাindexed by the old code has the termক্সম, and the queryক্ষমাanalyzed with the fix looks forখম. - What is not shown. The evidence is a mismatch between the code and its own comment and cited paper. No corpus evaluation or native-speaker check was done, so nothing here shows that the fold improves Bengali search, or that
খমাis a spelling in actual use forক্ষমাrather than the one the paper's phonetic code equates with it.
The test shows that BengaliNormalizer's "Khio (Ka + Hoshonto + Murdorno Sh)" rule checks U+09BF (vowel sign I) instead of U+09B7 (SSA), so it never folds the conjunct ক্ষ.
/*
* 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.analysis.bn;
import org.apache.lucene.tests.analysis.BaseTokenStreamTestCase;
public class TestBengaliKhioRule extends BaseTokenStreamTestCase {
public void testWordInitialKhio() throws Exception {
// Khio rule: KA HASANTA SSA MA AA must become KHA MA AA; actual: KA HASANTA SA MA AA
assertTokenStreamContents(
new BengaliNormalizationFilter(whitespaceMockTokenizer("ক্ষমা")),
new String[] {"খমা"});
}
}
# in a copy of this finding's repro folder (repro/LFV-667/, which holds src/)
# Lucene checkout at the tested commit, built with: ./gradlew --no-daemon assemble (JDK 25)
LUCENE=/path/to/lucene; V=12.0.0-SNAPSHOT
CP=$LUCENE/lucene/core/build/libs/lucene-core-$V.jar
CP=$CP:$LUCENE/lucene/codecs/build/libs/lucene-codecs-$V.jar
CP=$CP:$LUCENE/lucene/test-framework/build/libs/lucene-test-framework-$V.jar
CP=$CP:$LUCENE/lucene/analysis/common/build/libs/lucene-analysis-common-$V.jar
CP=$CP:randomizedtesting-runner-2.9.2.jar:junit-4.13.2.jar:hamcrest-3.0.jar # test-framework dependencies (Maven Central)
CP=$CP:junit-platform-console-standalone-6.1.3.jar # JUnit Platform console launcher (Maven Central)
javac -d classes -cp "$CP" $(find src -name '*.java')
# run 1: assertions on, seed DEADBEEF
java -ea -esa --add-modules jdk.incubator.vector --enable-native-access=ALL-UNNAMED -Dtests.seed=DEADBEEF \
-cp "classes:$CP" org.junit.platform.console.ConsoleLauncher \
execute --select-class org.apache.lucene.analysis.bn.TestBengaliKhioRule
- Expected vs actual
- Expected: ক্ষমা (U+0995 U+09CD U+09B7 U+09AE U+09BE) normalizes to খমা (U+0996 U+09AE U+09BE). Actual (-ea and -da): ক্সমা (U+0995 U+09CD U+09B8 U+09AE U+09BE).
More files, environment, full output38 words
| lucene | Lucene main e1faeba08bde (2026-11-02) |
|---|---|
| jdk | 25.0.3+9-LTS |
# Lucene main 645d4fe14a20, assertions on (-ea), seed DEADBEEF: 1 test(s) run, 1 failed
=> org.junit.ComparisonFailure: term 0 expected:<[খ]মা> but was:<[ক্স]মা>
org.junit.Assert.assertEquals(Assert.java:117)
org.apache.lucene.tests.analysis.BaseTokenStreamTestCase.assertTokenStreamContents(BaseTokenStreamTestCase.java:262)
org.apache.lucene.tests.analysis.BaseTokenStreamTestCase.assertTokenStreamContents(BaseTokenStreamTestCase.java:513)
org.apache.lucene.tests.analysis.BaseTokenStreamTestCase.assertTokenStreamContents(BaseTokenStreamTestCase.java:598)
org.apache.lucene.tests.analysis.BaseTokenStreamTestCase.assertTokenStreamContents(BaseTokenStreamTestCase.java:622)
org.apache.lucene.tests.analysis.BaseTokenStreamTestCase.assertTokenStreamContents(BaseTokenStreamTestCase.java:689)
org.apache.lucene.analysis.bn.TestBengaliKhioRule.testWordInitialKhio(TestBengaliKhioRule.java:25)- The rule's comment names KA + HASANTA + SSA, and its two branches are the two khio cases of the paper the class cites.
- U+09BF is a vowel sign, which never follows a hasanta in well-formed text: the rule can only fire on malformed input.
- No test, CHANGES entry or discussion mentions khio; since 2017 the line was only reformatted.
The full argument323 words
- The contract. The comment at
BengaliNormalizer.java:56names the rule, and the class javadoc (:24-26) says the class implements the cited paper. The paper lists the khio conjunct as\u0995 \u09CD \u09B7with "Case 1: In the initial position of a word ... it is given the same code as" KHA and "Case 2: In the middle or at the end of words, it is similar to" KA KHA. The code's branches mirror these two cases (the paper assigns phonetic codes for a spelling checker; the code applies them as character rewrites); only the third code point differs. The finder, the independent reproducer and the devil's advocate each extracted the paper text. - No contract names the coded sequence. Neither the comment nor the paper, nor any test, CHANGES entry or discussion, names KA + HASANTA + VOWEL SIGN I; the only mention found is the outside wiki page (see the prior reports), which transcribes the code. The sequence does not occur in well-formed Bengali, and the reviewed history and discussions identify no damage pattern that rewriting it as খ (dropping the vowel sign) would repair.
- Tests.
TestBengaliNormalizertests every other commented rule and has no khio case (nor one for the uncommented ৎ-to-ত rule,BengaliNormalizer.java:142-144), so no test pins the current khio output. The 4 Bengali suites pass with either fix. - History. The rule came with the contributor's first commit
1bca06b8a938(2017-08-24, LUCENE-7940, now gh#8989; apache/lucene-solr PR #238); later commits on these lines (8ef6a0da568,f99deb58b74) only reformat. The review never discussed khio. - Precedent. Lucene changes analysis output when it is wrong and asks users to reindex (MIGRATE.md, "Romanian analysis", 10.0), and fixed missing Telugu normalization as a bug (GITHUB#13059).
- No public javadoc for the mapping. The normalizer is package-private; the public filter says only that it normalizes "the orthography" and that "the normalization may cause unrelated terms to conflate". The contract is the comment plus the cited paper.
The devil's-advocate check227 words
The strongest "intended" case (the devil's-advocate verifier and the intended-lens review by another model family, rated moderate): no public javadoc names the mapping; no test covers khio; the output has been stable for 9 years, and the original review measured relevance with this code; a maintainer, rmuir, defends the Indic folds in gh#14659 as intended for damaged text from legacy fonts and PDF extraction ("The filter here is working as documented ... Just don't use the filter if you dont want the transformations that it does."); and another contributor's wiki page reports that a native-speaker reviewer rejected the filter's phonetic rules for search, so a dead khio rule may be "accidentally better".
Why it fails: a comment that names the rule plus a javadoc that cites the paper defining it are a stated contract, and both say KA + HASANTA + SSA. Nothing explains why a branch so labelled matches a vowel sign. Long life is not evidence that anyone chose it: no comment, test, CHANGES entry or discussion reviewed mentions khio. And "working as documented" cuts the other way, because the code does not do what is documented. Verdict: a defect (adjudication: confirmed, high). What the counter-case gets right: the search value of the fold is debatable, the paper describes phonetic codes for a spelling checker rather than search normalization, and a fix changes indexed terms.
Recommended Test U+09B7 (SSA), as the comment and the paper say
Test SSA, as the comment says: change '\u09BF' to '\u09B7' in BengaliNormalizer.java:58. The repro and all 1,912 tests of the module pass. The fix changes the term of every ক্ষ word, so affected fields need a reindex; deleting the rule is the alternative that keeps today's terms.
// Khio (Ka + Hoshonto + Murdorno Sh)
case '\u0995':
if (i + 2 < len && s[i + 1] == '\u09CD' && s[i + 2] == '\u09B7') {All options and trade-offs3 options · 481 words
Two options were built and checked; maintainers may prefer either. Add a khio case to TestBengaliNormalizer with whichever is chosen.
One character at BengaliNormalizer.java:58. Then ক্ষমা → খমা, পরীক্ষা → পরিকখা, বক্ষ → বকখ (no longer the same as বক্স), and the malformed ক্ি is left alone.
// Khio (Ka + Hoshonto + Murdorno Sh)
case '\u0995':
if (i + 2 < len && s[i + 1] == '\u09CD' && s[i + 2] == '\u09B7') {Trade-offs
- Changes the indexed term of every word with ক্ষ: needs a CHANGES entry and a MIGRATE note (reindex fields analyzed with
BengaliAnalyzer,BengaliNormalizationFilteror thebengaliNormalizationfactory), as the Romanian change did in 10.0. Main is the natural target; a 10.x backport is debatable. - The filter folds more: word-initial ক্ষ words merge with খ words, as the paper intends and as the filter javadoc's conflation warning covers.
- Same complexity and branch body, but the work moves from ক্ি inputs to ক্ষ inputs: on one repeated 96,000-character token (far beyond
StandardTokenizer's default 255-character limit) stock takes about 0 ms on ক্ষ and 62 ms on ক্ি, and the fix the reverse; negligible for word-length tokens.
What the checks validated
- Scratch worktree at
a9779ef3c193, gradle, JDK 25: the new test fails 3 of 3 on stock (assertions on and off) and passes with the fix;org.apache.lucene.analysis.bn.*: 5 suites, 25 tests (22 upstream + 3 new), 0 failures, seed 5EED0667 with assertions on and 5EED0668 with them off;:lucene:analysis:common:checkGoogleJavaFormatpasses; the whole:lucene:analysis:common:test: 340 suites, 1,912 tests, 18 skipped, 0 failures. - Standalone runner with the fix shadowed: the new test 3 of 3 pass with
-eaand-da; earlier, the finder's test 3 of 3 and the 4 upstream suites 22 of 22; the release probe passes on 7.1.0 with the fix.
Remove BengaliNormalizer.java:56-69. Well-formed text keeps today's terms (ক্ষমা stays ক্সমা); only the malformed ক্ি stops being folded.
Trade-offs
- No reindex for well-formed text (only an indexed malformed
ক্িgets a new term), and the code no longer claims a rule that it does not implement. - Gives up the paper's khio fold and keeps the
বক্ষ/বক্সcollision. Fits the view that the Indic folds are already aggressive (gh#14659).
What the checks validated
The 4 upstream Bengali suites, 22 of 22, pass with it (assertions on and off); the khio test fails under it, as designed.
Describe what the code does. Not run.
Trade-offs
Keeps every indexed term, so it is the most compatible option; but it documents a rule for a sequence that well-formed text does not contain, keeps the vowel-sign deletion and leaves the paper's khio case unimplemented.
A bounded-exhaustive check of the stock Bengali filter against an oracle for each rule its comments document: all 551,881 strings of up to 4 characters over 27 rule characters. The khio oracle failed on all 28 inputs that start with ক্ষ, and reading the condition found the wrong code point.
The method in full156 words
Method: bounded-exhaustive translation validation on the stock main jars (the study's "semi-formal" tier). A hunt unit, "script normalizers against their documented rules", 2026-10-27.
- For every string of length 0 to 4 over a 27-character alphabet built from the rule characters (consonants, vowel signs, hasanta and the other signs; 551,881 inputs, the empty one included), the output of
BengaliNormalizationFilterwas checked against a directed oracle per documented rule. The khio oracle (a word-initial ক্ষ must become খ, from the rule comment) failed on all 28 inputs that start with the conjunct. - A second relation (ী is documented to map to ি, so swapping one for the other in the input must not change the output) failed on ক্ী (→ ক্ি) versus ক্ি (→ খ), which pointed at U+09BF in the khio condition; reading the code confirmed it.
- Then an AI finder wrote the public-API JUnit, and independent AI tools reproduced, reviewed and judged it (see the checks).
No prior report: 89 tool queries over GitHub, commits, mailing lists, JIRA and the web (106 hits), manual GitHub searches and all 409 open PRs (2026-10-27). Another contributor's wiki page copies the coded rule without flagging it.
Searches4 searches
No issue, PR, commit, JIRA entry, list thread or web page reports that the khio rule tests U+09BF or that ক্ষ is never folded. Upstream main, branch_10x and branch_10_5 still have the condition.
Near-miss, to disclose: another contributor's wiki page of Bengali analysis notes (mediawiki.org, 2022, linked from a comment in gh#14659) lists the coded rule as "ক্ি (ka + virama + i)" under the label "Khio (Ka + Hoshonto + Murdorno Sh)" without noting that the two disagree, and recommends disabling the filter as too aggressive. It describes the code, not a defect.
| Where | When | How | Found |
|---|---|---|---|
| GitHub issues and PRs (apache/lucene), commits | 2026-10-27 | 40 issue/PR and 3 commit queries through a scripted prior-report search; a manual search for Bengali (all 12 results read); gh#8989 (30 comments) and lucene-solr PR #238 with review comments | no report; the introducing review never discusses khio |
| Open PRs | 2026-10-27 | the changed files of all 409 open apache/lucene PRs | none touches analysis/bn; #14699 (Telugu IndicNormalizer) is related, not the same |
| Mailing lists and JIRA | 2026-10-27 | 32 queries over dev@, java-user@, issues@ and commits@; solr-user@; 4 JQL queries | no report |
| Downstream repositories, all of GitHub and the web | 2026-10-27 | 2 downstream queries plus manual searches of Solr, Elasticsearch and OpenSearch; issue search for BengaliNormalizer and khio; code search for the comment word Murdorno (about 35 copies, 4 opened); 8 web queries | no report; the 4 copies opened carry the same condition; the wiki page above |
Present since the contributor's first commit 1bca06b8a938 (2017-08-24, LUCENE-7940), first released in 7.1.0 and never changed except for reformatting. It fails on all 16 releases run from 7.1.0 to 10.5.2 and on main, and the 63 release tags read have the same condition; 7.0.1 has no Bengali API.
Versions tested7 rows
Introduced by 1bca06b8a938 "LUCENE-7490: Added bengali language analyzer" (a typo for LUCENE-7940, now gh#8989; apache/lucene-solr PR #238), 2017-08-24. It was merged on 2017-09-05 into master (08128f712fc) and branch_7x (e18532cb687); CHANGES lists "LUCENE-7940: Add BengaliAnalyzer." under 7.1.0. git blame -w and git log -L put the rule on this commit; later commits only reformat (8ef6a0da568, LUCENE-9570; f99deb58b74, #13661). Not a regression. All 63 release tags in the history clone that have the file contain the same condition, and the 8 releases that have no tag there (8.8.2-8.11.3) fail when run, so every release from 7.1.0 to 10.5.2 is affected.
| Version or commit | Verdict | Result |
|---|---|---|
main a9779ef3c193 | affected | the brief's test 3 of 3 fail (gradle and the standalone runner, assertions on and off) |
| 10.5.2 | affected | the brief's test 3 of 3 fail; the finder's test 3 of 3; the release probe 3 of 3 |
| 7.1.0, 7.7.3, 8.0.0, 8.11.4, 9.0.0, 9.12.3, 10.0.0 | affected | the release probe fails 3 of 3 on each (filter and BengaliAnalyzer.normalize); the finder's test also on 9.12.3 |
| 8.8.2, 8.9.0, 8.10.0, 8.10.1, 8.11.0, 8.11.1, 8.11.2, 8.11.3 | affected | the release probe fails 3 of 3 on each, assertions on (no tags in the history clone; run after the fact checks) |
| 7.1.0-7.7.3, 8.0.0-8.8.1, 9.0.0-10.5.2 (63 tags) | read only | the same condition |
| 7.0.1 | API absent | the probe does not compile: no org.apache.lucene.analysis.bn package |
| main + fix, 7.1.0 + fix | not affected | all repros pass; main: the 5 Bengali suites, 25 tests, 0 failed |
An independent reproducer, a devil's advocate, an adjudication and reviews by another model family (which ran their own tests) all found it real; the adjudication confirmed it with high confidence. Registry re-runs and a final audit reproduced it with new seeds.
Checks and verdicts427 words
Every check wrote or ran its own test against the stock jars; all but the intended-lens review also ran with assertions off. This page was then fact-checked independently and by a reviewer from another model family.
| Check | Verdict | What it found |
|---|---|---|
| finder | candidate | Public-API JUnit fails 3 of 3 on main (-ea, -da), 10.5.2 and 9.12.3; the one-character fix passes 3 of 3 and the upstream Bengali suites 22 of 22. |
| cross-model review, refute lens | real (high) | An independent reviewer from another model family (OpenAI GPT-5.6) ran its own JUnit: 6 of 12 fail over 3 iterations with -ea, 2 of 4 with -da (ক্ষত gives ক্সত, not খত); passes with the fix. |
| Independent reproducer | real | Own 6-test JUnit fails 4 of 6 on main, 10.5.2 and 9.12.3 (index-time terms included); its 2 controls pass; re-extracted the paper's khio entry. |
| Cross-model review, intended lens | real (high) | OpenAI GPT-5.6: the intended-behaviour argument is moderate and rejected; its expected-semantics test fails 3 of 3 on main and passes with the fix. |
| Devil's advocate | real | Re-ran the finder's test; its own probe on main and 10.5.2 shows ক্ষ never folded at the start, middle or end, while the malformed ক্ি is folded. |
| adjudication | confirmed (high) | Re-ran the independent reproduction's test (4 of 6 fail, -ea and -da), the finder's (3 of 3) and the finder's with the fix (3 of 3 pass); class minor. |
| security assessment | none | 0 exceptions over 1,508,597 generated strings; no output longer than its input; ordinary term normalization, outside Lucene's SECURITY.md scope. |
| registry re-run and audit | keep | The finder's test re-run on main with new seeds (3 of 3 fail, -ea and -da); with the fix 3 of 3 pass. |
| brief runs | reproduced | The brief's test through gradle and the standalone runner on main and 10.5.2; fix A and option B validated; after the fact checks, the release probe on the 8 releases 8.8.2-8.11.3 (3 of 3 fail on each). |
| fact checks of this doc | reproduced; corrections applied | An independent fact check re-ran the doc's gradle command (3 of 3 fail, assertions on and off), fix A with the 5 Bengali suites (25 tests pass), the standalone runner on main and 10.5.2 and the options probe. A second one, by another model family (OpenAI GPT-5.6, read-only), re-read the sources and logs. A diagram check re-ran the diagram's probe. All found the bug as described; their wording and precision corrections are applied. |
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 410 KB
Finding record, Lucene and upstream sources, repro files and diagram notes1,287 words
- Registry title
- BengaliNormalizer's khio rule tests U+09BF (vowel sign I) instead of U+09B7 (SSA), so the common conjunct ক্ষ (KA+HASANTA+SSA) is never folded to KHA / KA+KHA as the rule comment and the cited paper say, and the malformed KA+HASANTA+VOWEL SIGN I is folded instead
- Area
- analysis-common / Bengali normalizer (BengaliNormalizer.normalize; BengaliNormalizationFilter, BengaliAnalyzer and the 'bengaliNormalization' factory)
- Priority
- Low, rank 296: A relevance-only normalization typo (impact class minor) in one language analyzer, whose fix changes indexed terms.
- Impact class
- minor
- Impact note
- Relevance quality only, with no error: every application that analyzes Bengali text with BengaliAnalyzer (default constructor included), BengaliNormalizationFilter or the 'bengaliNormalization' factory, on every release from 7.1.0 through 10.5.2 and on main, indexes and queries every word that contains the very common conjunct ক্ষ (শিক্ষা, পরীক্ষা, ক্ষমা, রক্ষা) with a term the documented khio rule would not produce, so the ক্ষ spelling and the খ spelling of a word do not match each other and বক্ষ ('chest') gets the same term as বক্স ('box'), while identical spellings still match; no scale is needed, -ea and -da behave the same, the filter javadoc already warns that normalization may conflate terms, and because a fix changes indexed terms, affected fields need reindexing after it.
- Summary
- You analyze Bengali text with the stock BengaliAnalyzer or BengaliNormalizationFilter. Its normalizer has a rule labelled 'Khio (Ka + Hoshonto + Murdorno Sh)' and says it implements a cited paper, which folds the conjunct ক্ষ (KA+HASANTA+SSA) to খ (KHA) at the start of a word and to কখ (KA+KHA) elsewhere, so ক্ষমা should normalize to খমা. Instead the rule's condition compares the third character with U+09BF (BENGALI VOWEL SIGN I) instead of U+09B7 (BENGALI LETTER SSA), one hex digit off, so the rule never fires on real text: ক্ষমা becomes ক্সমা and পরীক্ষা becomes পরিক্সা (only the separate 'All sh' rule applies, SSA to SA), while the malformed sequence KA+HASANTA+VOWEL SIGN I is folded instead (ক্িমা becomes খমা), with no error.
- Introduced
- 1bca06b8a938bfe89b288877399942d217c8453d (the contributor's first commit of apache/lucene-solr PR #238, LUCENE-7940 'Bengali Analyzer for Lucene', now apache/lucene#8989; its message says 'LUCENE-7490', a typo for LUCENE-7940) [read: git blame -w, git log -L and git log -G 09BF on the file in the history clone]: the rule comment '// Khio (Ka + Hoshonto + Murdorno Sh)' and the condition s[i+2] == '\u09BF' arrive together. It landed on master (8.0 line) through the merge 08128f712fc and on branch_7x through the squashed backport e18532cb687 (both by rmuir, 2017-09-05). Not a regression: the only later commits on these lines (8ef6a0da568 LUCENE-9570 reformat, f99deb58b74 #13661 spotless) are formatting. (2017-08-24 (authored; landed on master and branch_7x 2017-09-05; first released in 7.1.0))
- First affected version
- 7.1.0 [exec] (the first release with the org.apache.lucene.analysis.bn package; the history probe fails 3 of 3 there and passes with the one-character fix shadowed). 7.0.1, the last release before it, has no Bengali analysis API [exec: the probe does not compile], so no release ever shipped a working khio rule.
- Affected versions
- Every release that ships the Bengali analysis API, 7.1.0 through 10.5.2, and main a9779ef3c193; not fixed. [exec] a version-independent public-API probe (BengaliNormalizationFilter over KeywordTokenizer and BengaliAnalyzer.normalize, 3 khio checks) fails on 7.1.0, 7.7.3, 8.0.0, 8.11.4, 9.0.0, 9.12.3, 10.0.0, 10.5.2 and main (-ea and -da), and passes on 7.1.0 and main with the fix shadowed; the finder's JUnit fails 3 of 3 on 9.12.3, 10.5.2 and main (-ea and -da) (it needs org.apache.lucene.tests.analysis, so it does not compile on 9.0.0). [read] all 63 release tags in the history clone that have the file (7.1.0-7.7.3, 8.0.0-8.8.1, 9.0.0-10.5.2) contain the same condition; upstream main a95c754ee00 (2026-10-26), branch_10x and branch_10_5 still test '\u09BF' (read 2026-10-27).
- Status
- documented (brief, checks and a per-finding doc exist)
- Verification
- Confirmed by AI-run checks: an independent repro, a cross-model review that agreed, an intended-behaviour check, a prior-report search and a skeptical audit; not yet triaged by a person.
- Found by
- semi-formal: exhaustive check
- Upstream and prior reports
- none found (searched 2026-10-27, read-only; the prior-report record, verdict new): a scripted prior-report search (40 GitHub issue/PR queries on apache/lucene, 3 commit queries, 2 downstream-repository queries, 32 mailing-list queries over the dev, java-user, issues and commits lists, 4 JIRA queries, 8 web queries; 106 distinct hits, 0 high), the solr-user list, manual GitHub searches (apache/lucene 'Bengali' with all 12 results read, apache/lucene-solr, apache/solr, Elasticsearch, OpenSearch, all of GitHub), the changed files of all 409 open apache/lucene PRs (none under analysis/bn/), the introducing issue gh#8989 (LUCENE-7940, 30 comments) and lucene-solr PR #238 with its review comments (no khio discussion), and the file's full git history. No issue, PR, JIRA ticket, list thread, commit or web page reports the wrong code point or that ক্ষ is never folded. Near-miss, not a report: another contributor's wiki page of Bengali analysis notes (mediawiki.org, 2022, linked from a comment in gh#14659) transcribes the coded rule as 'ক্ি (ka + virama + i)' under the label 'Khio (Ka + Hoshonto + Murdorno Sh)' without noting that the two disagree, and recommends disabling the filter as too aggressive. Related, not the same bug: gh#14659 and PR #14699 (Telugu IndicNormalizer, open), where a maintainer defends the Indic folds as deliberately aggressive. Gaps: no code search for the literal code point; 4 of about 35 copies and ports were opened (all carry the same condition).
- Lucene commit tested
- main
a9779ef3c193(commit of 2026-10-26; tested 2026-10-27) through gradle in a scratch worktree and through the standalone JUnit runner, with assertions on and off; the 10.5.2 release jars (the brief's test); a version-independent probe on the release jars 7.1.0, 7.7.3, 8.0.0, 8.8.2, 8.9.0, 8.10.0, 8.10.1, 8.11.0, 8.11.1, 8.11.2, 8.11.3, 8.11.4, 9.0.0, 9.12.3, 10.0.0 and 10.5.2 (7.0.1 has no Bengali API). Fix A was validated in the scratch worktree (the new test, the 5 Bengali suites with assertions on and off,checkGoogleJavaFormat, the whole module's 1,912 tests); option B with the 4 upstream Bengali suites.
- Finding record
lucene/analysis/common/src/java/org/apache/lucene/analysis/bn/BengaliNormalizer.java:21-28 (at a9779ef3c193) (class javadoc: 'Implements the Bengali-language specific algorithm specified in: A Double Metaphone encoding for Bangla and its application in spelling checker'; package-private class), :48 (DirghoI kar writes '\u09BF'), :56 (comment '// Khio (Ka + Hoshonto + Murdorno Sh)'), :57-68 (case '\u0995': condition s[i + 2] == '\u09BF' at :58; i == 0 -> KHA, else -> KA KHA), :125-129 ('All sh': SHA and SSA -> SA)lucene/analysis/common/src/java/org/apache/lucene/analysis/bn/BengaliNormalizationFilter.java:29-31 (at a9779ef3c193) (javadoc: 'the normalization may cause unrelated terms to conflate'); BengaliNormalizationFilterFactory.java:40, :55-61 (SPI name 'bengaliNormalization'); BengaliAnalyzer.java:104, :119-130, :134-138 (default constructor, createComponents, normalize)GitHub, read-only (2026-10-27): apache/lucene #8989 (LUCENE-7940 comments), apache/lucene-solr PR #238 (conversation and review comments), apache/lucene #14659 and PR #14699, apache/lucene main a95c754ee00 BengaliNormalizer.java (condition unchanged)mediawiki.org 'User:TJones (WMF)/Notes/Unpacking Notes/Bengali' (raw page, read 2026-10-27: the khio label followed by 'ক্ি (ka + virama + i)', no remark on the mismatch)
- The finding in 30 seconds
lucene/analysis/common/src/java/org/apache/lucene/analysis/bn/BengaliNormalizer.java:21-28 (at a9779ef3c193) (class javadoc, package-private), :36-39, :46-49, :56-68, :125-129lucene/analysis/common/src/java/org/apache/lucene/analysis/bn/BengaliNormalizationFilter.java:26-53; (at a9779ef3c193) BengaliAnalyzer.java:119-140; BengaliNormalizationFilterFactory.java:40, :54-62
- Who is affected
lucene/analysis/common/src/java/org/apache/lucene/analysis/bn/BengaliAnalyzer.java:103-106, (at a9779ef3c193) :119-140 (:123-125 keyword marker); BengaliNormalizationFilter.java:29-31, :48-49; BengaliNormalizationFilterFactory.java:40
- Why it looks unintended
lucene/analysis/common/src/java/org/apache/lucene/analysis/bn/BengaliNormalizer.java:24-26, (at a9779ef3c193) :48, :56-68, :142-144; BengaliNormalizationFilter.java:27-31lucene/analysis/common/src/test/org/apache/lucene/analysis/bn/TestBengaliNormalizer.java:30-69 (at a9779ef3c193)lucene/MIGRATE.md:837, (at a9779ef3c193) :871-873; lucene/CHANGES.txt:3048, :7861
- Suggested fix
lucene/MIGRATE.md:871-873 (at a9779ef3c193)
- History
lucene/CHANGES.txt:7861 (at a9779ef3c193)
- TestBengaliKhioRule.java (JUnit test):
repro/LFV-667/src/org/apache/lucene/analysis/bn/TestBengaliKhioRule.java(29 lines)
A light, step-by-step picture of the repro (the words ক্ষমা and খমা), made in parallel with this doc from the executed runs on main a9779ef3c193 and the code.
assets/LFV-667/bug.png(410 KB)
