Skip to content

Commit bc69af5

Browse files
paulirwinclaude
andcommitted
Enforce minimumLength >= 1 in JapaneseKatakanaStemFilter (LUCENE-10352 backport), #1072
Backport the upstream LUCENE-10352 constructor guard so JapaneseKatakanaStemFilter rejects a minimumLength less than 1, matching apache/lucene (JapaneseKatakanaStemFilter.java: "minimumLength must be >=1"). This replaces the prior Stem() length==0 guard from the previous commit with the upstream approach. A minimumLength of 0 let a zero-length token (e.g. the single empty token a KeywordTokenizer emits for empty input) skip the "length < minimumKatakanaLength" check in Stem(), after which the term[length - 1] access read term[-1] and threw IndexOutOfRangeException. Enforcing minimumLength >= 1 in the constructor prevents that configuration from being built at all, which is how upstream resolved it. This was the residual TestRandomChains failure reported on PR #1348 (seeds 0x951afd480395f9b7:0x63fc16274945f1a3 / es-419 and 0xa4b2f7f450b03294:0x9fdf3b0c216c251e / lt-LT): the random arg producer can yield minimumLength of 0, and TestRandomChains already discards constructors that throw ArgumentOutOfRangeException, so the offending chain is simply skipped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent a4cac05 commit bc69af5

2 files changed

Lines changed: 15 additions & 29 deletions

File tree

src/Lucene.Net.Analysis.Kuromoji/JapaneseKatakanaStemFilter.cs

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,11 @@ public sealed class JapaneseKatakanaStemFilter : TokenFilter
5151
public JapaneseKatakanaStemFilter(TokenStream input, int minimumLength)
5252
: base(input)
5353
{
54-
// LUCENENET: Added guard clause
55-
if (minimumLength < 0)
56-
throw new ArgumentOutOfRangeException(nameof(minimumLength), "Minimum length must be a non-negative integer.");
54+
// LUCENENET: Backport of the LUCENE-10352 guard clause. Enforcing minimumLength >= 1
55+
// prevents a zero-length term (e.g. the single empty token a KeywordTokenizer emits for
56+
// empty input) from reaching the term[length - 1] read in Stem() with length == 0.
57+
if (minimumLength < 1)
58+
throw new ArgumentOutOfRangeException(nameof(minimumLength), "minimumLength must be >=1");
5759

5860
this.minimumKatakanaLength = minimumLength;
5961
this.termAttr = AddAttribute<ICharTermAttribute>();
@@ -83,18 +85,6 @@ public override bool IncrementToken()
8385

8486
private int Stem(char[] term, int length)
8587
{
86-
// LUCENENET specific (#1072): guard against a zero-length term. When
87-
// minimumKatakanaLength == 0 a zero-length token (e.g. the single empty token a
88-
// KeywordTokenizer emits for empty input) passes the length check below, IsKatakana
89-
// vacuously returns true, and the term[length - 1] access below reads term[-1].
90-
// Upstream Java has the same latent term[-1] read, but its TestRandomChains never
91-
// exercises the kuromoji filters (separate module), so it never surfaces there.
92-
// There is nothing to stem in a zero-length term, so return it unchanged.
93-
if (length == 0)
94-
{
95-
return length;
96-
}
97-
9888
if (length < minimumKatakanaLength)
9989
{
10090
return length;

src/Lucene.Net.Tests.Analysis.Kuromoji/TestJapaneseKatakanaStemFilter.cs

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -101,25 +101,21 @@ public void TestEmptyTerm()
101101
/// <summary>
102102
/// LUCENENET-specific regression test for <a href="https://github.com/apache/lucenenet/issues/1072">#1072</a>.
103103
/// <para/>
104-
/// With a <c>minimumLength</c> of <c>0</c>, a zero-length token (such as the single empty token a
105-
/// <see cref="KeywordTokenizer"/> emits for empty input) would skip the length check in <c>Stem()</c>,
106-
/// <c>IsKatakana</c> would vacuously return <c>true</c>, and the <c>term[length - 1]</c> access would
107-
/// read <c>term[-1]</c>, throwing <see cref="IndexOutOfRangeException"/>. This was a residual
108-
/// <c>TestRandomChains</c> failure reported on PR #1348 (e.g. seed
109-
/// <c>0x951afd480395f9b7:0x63fc16274945f1a3</c>); upstream Java has the same latent read but never
110-
/// exercises the kuromoji filters from its own random-chains test.
104+
/// Verifies the LUCENE-10352 guard: the constructor must reject a <c>minimumLength</c> less than
105+
/// <c>1</c>. A <c>minimumLength</c> of <c>0</c> would let a zero-length token (such as the single empty
106+
/// token a <see cref="KeywordTokenizer"/> emits for empty input) skip the length check in <c>Stem()</c>,
107+
/// after which the <c>term[length - 1]</c> access reads <c>term[-1]</c> and throws. This surfaced as a
108+
/// residual <c>TestRandomChains</c> failure reported on PR #1348 (e.g. seed
109+
/// <c>0x951afd480395f9b7:0x63fc16274945f1a3</c>).
111110
/// </summary>
112111
[Test]
113112
[LuceneNetSpecific] // Issue #1072
114-
public void TestEmptyTermWithZeroMinimumLengthDoesNotThrow()
113+
public void TestMinimumLengthLessThanOneThrows()
115114
{
116-
Analyzer a = Analyzer.NewAnonymous(createComponents: (fieldName, reader) =>
117-
{
118-
Tokenizer tokenizer = new KeywordTokenizer(reader);
119-
return new TokenStreamComponents(tokenizer, new JapaneseKatakanaStemFilter(tokenizer, minimumLength: 0));
120-
});
115+
Tokenizer tokenizer = new KeywordTokenizer(new System.IO.StringReader(""));
121116

122-
CheckOneTerm(a, "", "");
117+
Assert.Throws<ArgumentOutOfRangeException>(() => new JapaneseKatakanaStemFilter(tokenizer, minimumLength: 0));
118+
Assert.Throws<ArgumentOutOfRangeException>(() => new JapaneseKatakanaStemFilter(tokenizer, minimumLength: -1));
123119
}
124120
}
125121
}

0 commit comments

Comments
 (0)