From 1b33f5e30bf504bfad09eaac7809ff974e69cdae Mon Sep 17 00:00:00 2001 From: Rich Gillam Date: Mon, 16 Sep 2024 17:36:20 -0700 Subject: [PATCH] ICU-22889 Implemented a recursion limit in the RBNF parsing code to match the one already present in the RBNF formatting code. --- icu4c/source/i18n/nfrs.cpp | 12 ++++-- icu4c/source/i18n/nfrs.h | 2 +- icu4c/source/i18n/nfrule.cpp | 6 +++ icu4c/source/i18n/nfrule.h | 2 + icu4c/source/i18n/nfsubs.cpp | 21 ++++++--- icu4c/source/i18n/nfsubs.h | 1 + icu4c/source/i18n/rbnf.cpp | 2 +- icu4c/source/test/intltest/itrbnf.cpp | 34 +++++++++++++++ icu4c/source/test/intltest/itrbnf.h | 1 + .../com/ibm/icu/dev/test/format/RbnfTest.java | 43 +++++++++++++++++++ .../main/java/com/ibm/icu/text/NFRule.java | 12 +++--- .../main/java/com/ibm/icu/text/NFRuleSet.java | 11 +++-- .../java/com/ibm/icu/text/NFSubstitution.java | 22 +++++----- .../ibm/icu/text/RuleBasedNumberFormat.java | 2 +- 14 files changed, 138 insertions(+), 33 deletions(-) diff --git a/icu4c/source/i18n/nfrs.cpp b/icu4c/source/i18n/nfrs.cpp index 5f0d6b3c1da3..be2ab2932e7a 100644 --- a/icu4c/source/i18n/nfrs.cpp +++ b/icu4c/source/i18n/nfrs.cpp @@ -689,7 +689,7 @@ static void dumpUS(FILE* f, const UnicodeString& us) { #endif UBool -NFRuleSet::parse(const UnicodeString& text, ParsePosition& pos, double upperBound, uint32_t nonNumericalExecutedRuleMask, Formattable& result) const +NFRuleSet::parse(const UnicodeString& text, ParsePosition& pos, double upperBound, uint32_t nonNumericalExecutedRuleMask, int32_t recursionCount, Formattable& result) const { // try matching each rule in the rule set against the text being // parsed. Whichever one matches the most characters is the one @@ -697,6 +697,12 @@ NFRuleSet::parse(const UnicodeString& text, ParsePosition& pos, double upperBoun result.setLong(0); + // dump out if we've reached the recursion limit + if (recursionCount >= RECURSION_LIMIT) { + // stop recursion + return false; + } + // dump out if there's no text to parse if (text.length() == 0) { return 0; @@ -720,7 +726,7 @@ NFRuleSet::parse(const UnicodeString& text, ParsePosition& pos, double upperBoun nonNumericalExecutedRuleMask |= 1 << i; Formattable tempResult; - UBool success = nonNumericalRules[i]->doParse(text, workingPos, 0, upperBound, nonNumericalExecutedRuleMask, tempResult); + UBool success = nonNumericalRules[i]->doParse(text, workingPos, 0, upperBound, nonNumericalExecutedRuleMask, recursionCount + 1, tempResult); if (success && (workingPos.getIndex() > highWaterMark.getIndex())) { result = tempResult; highWaterMark = workingPos; @@ -759,7 +765,7 @@ NFRuleSet::parse(const UnicodeString& text, ParsePosition& pos, double upperBoun continue; } Formattable tempResult; - UBool success = rules[i]->doParse(text, workingPos, fIsFractionRuleSet, upperBound, nonNumericalExecutedRuleMask, tempResult); + UBool success = rules[i]->doParse(text, workingPos, fIsFractionRuleSet, upperBound, nonNumericalExecutedRuleMask, recursionCount + 1, tempResult); if (success && workingPos.getIndex() > highWaterMark.getIndex()) { result = tempResult; highWaterMark = workingPos; diff --git a/icu4c/source/i18n/nfrs.h b/icu4c/source/i18n/nfrs.h index a1beedda17d7..5e448395f7b6 100644 --- a/icu4c/source/i18n/nfrs.h +++ b/icu4c/source/i18n/nfrs.h @@ -55,7 +55,7 @@ class NFRuleSet : public UMemory { void format(int64_t number, UnicodeString& toAppendTo, int32_t pos, int32_t recursionCount, UErrorCode& status) const; void format(double number, UnicodeString& toAppendTo, int32_t pos, int32_t recursionCount, UErrorCode& status) const; - UBool parse(const UnicodeString& text, ParsePosition& pos, double upperBound, uint32_t nonNumericalExecutedRuleMask, Formattable& result) const; + UBool parse(const UnicodeString& text, ParsePosition& pos, double upperBound, uint32_t nonNumericalExecutedRuleMask, int32_t recursionCount, Formattable& result) const; void appendRules(UnicodeString& result) const; // toString diff --git a/icu4c/source/i18n/nfrule.cpp b/icu4c/source/i18n/nfrule.cpp index e7908ecab912..264e8d79e2d9 100644 --- a/icu4c/source/i18n/nfrule.cpp +++ b/icu4c/source/i18n/nfrule.cpp @@ -918,6 +918,7 @@ NFRule::doParse(const UnicodeString& text, UBool isFractionRule, double upperBound, uint32_t nonNumericalExecutedRuleMask, + int32_t recursionCount, Formattable& resVal) const { // internally we operate on a copy of the string being parsed @@ -1021,6 +1022,7 @@ NFRule::doParse(const UnicodeString& text, double partialResult = matchToDelimiter(workText, start, tempBaseValue, temp, pp, sub1, nonNumericalExecutedRuleMask, + recursionCount, upperBound); // if we got a successful match (or were trying to match a @@ -1042,6 +1044,7 @@ NFRule::doParse(const UnicodeString& text, partialResult = matchToDelimiter(workText2, 0, partialResult, temp, pp2, sub2, nonNumericalExecutedRuleMask, + recursionCount, upperBound); // if we got a successful match on this second @@ -1179,6 +1182,7 @@ NFRule::matchToDelimiter(const UnicodeString& text, ParsePosition& pp, const NFSubstitution* sub, uint32_t nonNumericalExecutedRuleMask, + int32_t recursionCount, double upperBound) const { UErrorCode status = U_ZERO_ERROR; @@ -1213,6 +1217,7 @@ NFRule::matchToDelimiter(const UnicodeString& text, formatter->isLenient(), #endif nonNumericalExecutedRuleMask, + recursionCount, result); // if the substitution could match all the text up to @@ -1267,6 +1272,7 @@ NFRule::matchToDelimiter(const UnicodeString& text, formatter->isLenient(), #endif nonNumericalExecutedRuleMask, + recursionCount, result); if (success && (tempPP.getIndex() != 0)) { // if there's a successful match (or it's a null diff --git a/icu4c/source/i18n/nfrule.h b/icu4c/source/i18n/nfrule.h index dd604085aecd..284d915c9424 100644 --- a/icu4c/source/i18n/nfrule.h +++ b/icu4c/source/i18n/nfrule.h @@ -77,6 +77,7 @@ class NFRule : public UMemory { UBool isFractional, double upperBound, uint32_t nonNumericalExecutedRuleMask, + int32_t recursionCount, Formattable& result) const; UBool shouldRollBack(int64_t number) const; @@ -98,6 +99,7 @@ class NFRule : public UMemory { double matchToDelimiter(const UnicodeString& text, int32_t startPos, double baseValue, const UnicodeString& delimiter, ParsePosition& pp, const NFSubstitution* sub, uint32_t nonNumericalExecutedRuleMask, + int32_t recursionCount, double upperBound) const; void stripPrefix(UnicodeString& text, const UnicodeString& prefix, ParsePosition& pp) const; diff --git a/icu4c/source/i18n/nfsubs.cpp b/icu4c/source/i18n/nfsubs.cpp index 7dfba6f369fd..e7f2c6335048 100644 --- a/icu4c/source/i18n/nfsubs.cpp +++ b/icu4c/source/i18n/nfsubs.cpp @@ -175,6 +175,7 @@ class ModulusSubstitution : public NFSubstitution { double upperBound, UBool lenientParse, uint32_t nonNumericalExecutedRuleMask, + int32_t recursionCount, Formattable& result) const override; virtual double composeRuleValue(double newRuleValue, double oldRuleValue) const override { @@ -242,6 +243,7 @@ class FractionalPartSubstitution : public NFSubstitution { double upperBound, UBool lenientParse, uint32_t nonNumericalExecutedRuleMask, + int32_t recursionCount, Formattable& result) const override; virtual double composeRuleValue(double newRuleValue, double oldRuleValue) const override { return newRuleValue + oldRuleValue; } @@ -314,6 +316,7 @@ class NumeratorSubstitution : public NFSubstitution { double upperBound, UBool /*lenientParse*/, uint32_t nonNumericalExecutedRuleMask, + int32_t recursionCount, Formattable& result) const override; virtual double composeRuleValue(double newRuleValue, double oldRuleValue) const override { return newRuleValue / oldRuleValue; } @@ -706,6 +709,7 @@ NFSubstitution::doParse(const UnicodeString& text, double upperBound, UBool lenientParse, uint32_t nonNumericalExecutedRuleMask, + int32_t recursionCount, Formattable& result) const { #ifdef RBNF_DEBUG @@ -726,7 +730,7 @@ NFSubstitution::doParse(const UnicodeString& text, // on), then also try parsing the text using a default- // constructed NumberFormat if (ruleSet != nullptr) { - ruleSet->parse(text, parsePosition, upperBound, nonNumericalExecutedRuleMask, result); + ruleSet->parse(text, parsePosition, upperBound, nonNumericalExecutedRuleMask, recursionCount, result); if (lenientParse && !ruleSet->isFractionRuleSet() && parsePosition.getIndex() == 0) { UErrorCode status = U_ZERO_ERROR; NumberFormat* fmt = NumberFormat::createInstance(status); @@ -949,18 +953,19 @@ ModulusSubstitution::doParse(const UnicodeString& text, double upperBound, UBool lenientParse, uint32_t nonNumericalExecutedRuleMask, + int32_t recursionCount, Formattable& result) const { // if this isn't a >>> substitution, we can just use the // inherited parse() routine to do the parsing if (ruleToUse == nullptr) { - return NFSubstitution::doParse(text, parsePosition, baseValue, upperBound, lenientParse, nonNumericalExecutedRuleMask, result); + return NFSubstitution::doParse(text, parsePosition, baseValue, upperBound, lenientParse, nonNumericalExecutedRuleMask, recursionCount, result); // but if it IS a >>> substitution, we have to do it here: we // use the specific rule's doParse() method, and then we have to // do some of the other work of NFRuleSet.parse() } else { - ruleToUse->doParse(text, parsePosition, false, upperBound, nonNumericalExecutedRuleMask, result); + ruleToUse->doParse(text, parsePosition, false, upperBound, nonNumericalExecutedRuleMask, recursionCount, result); if (parsePosition.getIndex() != 0) { UErrorCode status = U_ZERO_ERROR; @@ -1136,12 +1141,13 @@ FractionalPartSubstitution::doParse(const UnicodeString& text, double /*upperBound*/, UBool lenientParse, uint32_t nonNumericalExecutedRuleMask, + int32_t recursionCount, Formattable& resVal) const { // if we're not in byDigits mode, we can just use the inherited // doParse() if (!byDigits) { - return NFSubstitution::doParse(text, parsePosition, baseValue, 0, lenientParse, nonNumericalExecutedRuleMask, resVal); + return NFSubstitution::doParse(text, parsePosition, baseValue, 0, lenientParse, nonNumericalExecutedRuleMask, recursionCount, resVal); // if we ARE in byDigits mode, parse the text one digit at a time // using this substitution's owning rule set (we do this by setting @@ -1160,7 +1166,7 @@ FractionalPartSubstitution::doParse(const UnicodeString& text, while (workText.length() > 0 && workPos.getIndex() != 0) { workPos.setIndex(0); Formattable temp; - getRuleSet()->parse(workText, workPos, 10, nonNumericalExecutedRuleMask, temp); + getRuleSet()->parse(workText, workPos, 10, nonNumericalExecutedRuleMask, recursionCount, temp); UErrorCode status = U_ZERO_ERROR; digit = temp.getLong(status); // digit = temp.getType() == Formattable::kLong ? @@ -1271,6 +1277,7 @@ NumeratorSubstitution::doParse(const UnicodeString& text, double upperBound, UBool /*lenientParse*/, uint32_t nonNumericalExecutedRuleMask, + int32_t recursionCount, Formattable& result) const { // we don't have to do anything special to do the parsing here, @@ -1289,7 +1296,7 @@ NumeratorSubstitution::doParse(const UnicodeString& text, while (workText.length() > 0 && workPos.getIndex() != 0) { workPos.setIndex(0); - getRuleSet()->parse(workText, workPos, 1, nonNumericalExecutedRuleMask, temp); // parse zero or nothing at all + getRuleSet()->parse(workText, workPos, 1, nonNumericalExecutedRuleMask, recursionCount, temp); // parse zero or nothing at all if (workPos.getIndex() == 0) { // we failed, either there were no more zeros, or the number was formatted with digits // either way, we're done @@ -1311,7 +1318,7 @@ NumeratorSubstitution::doParse(const UnicodeString& text, } // we've parsed off the zeros, now let's parse the rest from our current position - NFSubstitution::doParse(workText, parsePosition, withZeros ? 1 : baseValue, upperBound, false, nonNumericalExecutedRuleMask, result); + NFSubstitution::doParse(workText, parsePosition, withZeros ? 1 : baseValue, upperBound, false, nonNumericalExecutedRuleMask, recursionCount, result); if (withZeros) { // any base value will do in this case. is there a way to diff --git a/icu4c/source/i18n/nfsubs.h b/icu4c/source/i18n/nfsubs.h index d252f8649931..a14c04459f1a 100644 --- a/icu4c/source/i18n/nfsubs.h +++ b/icu4c/source/i18n/nfsubs.h @@ -192,6 +192,7 @@ class NFSubstitution : public UObject { double upperBound, UBool lenientParse, uint32_t nonNumericalExecutedRuleMask, + int32_t recursionCount, Formattable& result) const; /** diff --git a/icu4c/source/i18n/rbnf.cpp b/icu4c/source/i18n/rbnf.cpp index 099e636b1ea7..c4e8ff73a7cc 100644 --- a/icu4c/source/i18n/rbnf.cpp +++ b/icu4c/source/i18n/rbnf.cpp @@ -1362,7 +1362,7 @@ RuleBasedNumberFormat::parse(const UnicodeString& text, ParsePosition working_pp(0); Formattable working_result; - rp->parse(workingText, working_pp, kMaxDouble, 0, working_result); + rp->parse(workingText, working_pp, kMaxDouble, 0, 0, working_result); if (working_pp.getIndex() > high_pp.getIndex()) { high_pp = working_pp; high_result = working_result; diff --git a/icu4c/source/test/intltest/itrbnf.cpp b/icu4c/source/test/intltest/itrbnf.cpp index c0d00ec8c348..293d8e41df94 100644 --- a/icu4c/source/test/intltest/itrbnf.cpp +++ b/icu4c/source/test/intltest/itrbnf.cpp @@ -81,6 +81,7 @@ void IntlTestRBNF::runIndexedTest(int32_t index, UBool exec, const char* &name, TESTCASE(29, TestNumberingSystem); TESTCASE(30, TestDFRounding); TESTCASE(31, TestMemoryLeak22899); + TESTCASE(32, TestInfiniteRecursion); #else TESTCASE(0, TestRBNFDisabled); #endif @@ -2614,6 +2615,39 @@ IntlTestRBNF::TestNumberingSystem() { } } +void +IntlTestRBNF::TestInfiniteRecursion() { + UnicodeString badRules[] = { + ">>", + "<<", + "<<<", + ">>>", + "%foo: x=%foo=", + "%one: x>%two>; %two: y>%one>;" + }; + + for (int32_t i = 0; i < UPRV_LENGTHOF(badRules); i++) { + UErrorCode err = U_ZERO_ERROR; + UParseError parseErr; + RuleBasedNumberFormat rbnf(badRules[i], parseErr, err); + + if (U_SUCCESS(err)) { + UnicodeString result; + rbnf.format(5, result); + // we don't actually care about the result and the function doesn't return an error code; + // we just want to make sure the function returns + + Formattable pResult; + rbnf.parse("foo", pResult, err); + assertTrue("rbnf.parse() didn't return U_INVALID_FORMAT_ERROR!", err == U_INVALID_FORMAT_ERROR); + } else { + // eventually it'd be nice to statically analyze the rules for (at least) the most common + // causes of infinite recursion, in which case we'd end up down here and need to check + // the error code. But for now, we probably won't end up here and don't care if we do + } + } +} + /* U_HAVE_RBNF */ #else diff --git a/icu4c/source/test/intltest/itrbnf.h b/icu4c/source/test/intltest/itrbnf.h index 3c8c4f1e024a..d05aadd5397a 100644 --- a/icu4c/source/test/intltest/itrbnf.h +++ b/icu4c/source/test/intltest/itrbnf.h @@ -162,6 +162,7 @@ class IntlTestRBNF : public IntlTest { void TestMinMaxIntegerDigitsIgnored(); void TestNumberingSystem(); void TestMemoryLeak22899(); + void TestInfiniteRecursion(); protected: virtual void doTest(RuleBasedNumberFormat* formatter, const char* const testData[][2], UBool testParsing); diff --git a/icu4j/main/common_tests/src/test/java/com/ibm/icu/dev/test/format/RbnfTest.java b/icu4j/main/common_tests/src/test/java/com/ibm/icu/dev/test/format/RbnfTest.java index dbcbc789f49d..551a4dea0993 100644 --- a/icu4j/main/common_tests/src/test/java/com/ibm/icu/dev/test/format/RbnfTest.java +++ b/icu4j/main/common_tests/src/test/java/com/ibm/icu/dev/test/format/RbnfTest.java @@ -1908,4 +1908,47 @@ public void TestNumberingSystem() { rbnf.setDefaultRuleSet("%ethiopic"); assertEquals("Wrong result with Ethiopic rule set", "፻፳፫", rbnf.format(123)); } + + @Test + public void TestInfiniteRecursion() { + final String[] badRules = { + ">>", + "<<", + "<<<", + ">>>", + "%foo: x=%foo=", + "%one: x>%two>; %two: y>%one>;" + }; + + for (String badRule : badRules) { + try { + RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(badRule); + try { + rbnf.format(5); + // we don't actually care about the result; we just want to make sure the function returns + } catch (IllegalStateException e) { + // we're supposed to get an IllegalStateException; swallow it and continue + } + + try { + rbnf.parse("foo"); + errln("Parse test didn't throw an exception!"); + } catch (IllegalStateException e) { + // we're supposed to get an IllegalStateException; swallow it and continue + } catch (ParseException e) { + // if we don't hit the recursion limit, we'll still fail to parse the number, + // so also swallow this exception and continue + } + } catch (IllegalArgumentException e) { + // ">>>" generates a parse exception when you try to create the formatter (so we expect that rather + // than re-throwing and triggering a test failure) + // (eventually it'd be nice to statically analyze the rules for (at least) the most common + // causes of infinite recursion, in which case we'd end up down here and need to check + // the error code. But for now, we probably won't end up here and don't care if we do) + if (!badRule.equals("<<<")) { + throw e; + } + } + } + } } diff --git a/icu4j/main/core/src/main/java/com/ibm/icu/text/NFRule.java b/icu4j/main/core/src/main/java/com/ibm/icu/text/NFRule.java index f93b4d950831..bd32b5c92a6f 100644 --- a/icu4j/main/core/src/main/java/com/ibm/icu/text/NFRule.java +++ b/icu4j/main/core/src/main/java/com/ibm/icu/text/NFRule.java @@ -913,7 +913,7 @@ public boolean shouldRollBack(long number) { * result is an integer and Double otherwise. The result is never null. */ public Number doParse(String text, ParsePosition parsePosition, boolean isFractionRule, - double upperBound, int nonNumericalExecutedRuleMask) { + double upperBound, int nonNumericalExecutedRuleMask, int recursionCount) { // internally we operate on a copy of the string being parsed // (because we're going to change it) and use our own ParsePosition @@ -986,7 +986,7 @@ public Number doParse(String text, ParsePosition parsePosition, boolean isFracti pp.setIndex(0); double partialResult = matchToDelimiter(workText, start, tempBaseValue, ruleText.substring(sub1Pos, sub2Pos), rulePatternFormat, - pp, sub1, upperBound, nonNumericalExecutedRuleMask).doubleValue(); + pp, sub1, upperBound, nonNumericalExecutedRuleMask, recursionCount).doubleValue(); // if we got a successful match (or were trying to match a // null substitution), pp is now pointing at the first unmatched @@ -1004,7 +1004,7 @@ public Number doParse(String text, ParsePosition parsePosition, boolean isFracti // a real result partialResult = matchToDelimiter(workText2, 0, partialResult, ruleText.substring(sub2Pos), rulePatternFormat, pp2, sub2, - upperBound, nonNumericalExecutedRuleMask).doubleValue(); + upperBound, nonNumericalExecutedRuleMask, recursionCount).doubleValue(); // if we got a successful match on this second // matchToDelimiter() call, update the high-water mark @@ -1132,7 +1132,7 @@ private String stripPrefix(String text, String prefix, ParsePosition pp) { */ private Number matchToDelimiter(String text, int startPos, double baseVal, String delimiter, PluralFormat pluralFormatDelimiter, ParsePosition pp, NFSubstitution sub, - double upperBound, int nonNumericalExecutedRuleMask) { + double upperBound, int nonNumericalExecutedRuleMask, int recursionCount) { // if "delimiter" contains real (i.e., non-ignorable) text, search // it for "delimiter" beginning at "start". If that succeeds, then // use "sub"'s doParse() method to match the text before the @@ -1155,7 +1155,7 @@ private Number matchToDelimiter(String text, int startPos, double baseVal, String subText = text.substring(0, dPos); if (subText.length() > 0) { tempResult = sub.doParse(subText, tempPP, baseVal, upperBound, - formatter.lenientParseEnabled(), nonNumericalExecutedRuleMask); + formatter.lenientParseEnabled(), nonNumericalExecutedRuleMask, recursionCount); // if the substitution could match all the text up to // where we found "delimiter", then this function has @@ -1203,7 +1203,7 @@ else if (sub == null) { Number result = ZERO; // try to match the whole string against the substitution Number tempResult = sub.doParse(text, tempPP, baseVal, upperBound, - formatter.lenientParseEnabled(), nonNumericalExecutedRuleMask); + formatter.lenientParseEnabled(), nonNumericalExecutedRuleMask, recursionCount); if (tempPP.getIndex() != 0) { // if there's a successful match (or it's a null // substitution), update pp to point to the first diff --git a/icu4j/main/core/src/main/java/com/ibm/icu/text/NFRuleSet.java b/icu4j/main/core/src/main/java/com/ibm/icu/text/NFRuleSet.java index f4c079ec9a94..7a21a6f01f58 100644 --- a/icu4j/main/core/src/main/java/com/ibm/icu/text/NFRuleSet.java +++ b/icu4j/main/core/src/main/java/com/ibm/icu/text/NFRuleSet.java @@ -751,7 +751,7 @@ private static long lcm(long x, long y) { * this function returns Long.valueOf(0), and the parse position is * left unchanged. */ - public Number parse(String text, ParsePosition parsePosition, double upperBound, int nonNumericalExecutedRuleMask) { + public Number parse(String text, ParsePosition parsePosition, double upperBound, int nonNumericalExecutedRuleMask, int recursionCount) { // try matching each rule in the rule set against the text being // parsed. Whichever one matches the most characters is the one // that determines the value we return. @@ -760,6 +760,11 @@ public Number parse(String text, ParsePosition parsePosition, double upperBound, Number result = NFRule.ZERO; Number tempResult; + // dump out if we've reached the recursion limit + if (recursionCount >= RECURSION_LIMIT) { + throw new IllegalStateException("Recursion limit exceeded when applying ruleSet " + name); + } + // dump out if there's no text to parse if (text.length() == 0) { return result; @@ -772,7 +777,7 @@ public Number parse(String text, ParsePosition parsePosition, double upperBound, // Mark this rule as being executed so that we don't try to execute it again. nonNumericalExecutedRuleMask |= 1 << nonNumericalRuleIdx; - tempResult = nonNumericalRule.doParse(text, parsePosition, false, upperBound, nonNumericalExecutedRuleMask); + tempResult = nonNumericalRule.doParse(text, parsePosition, false, upperBound, nonNumericalExecutedRuleMask, recursionCount + 1); if (parsePosition.getIndex() > highWaterMark.getIndex()) { result = tempResult; highWaterMark.setIndex(parsePosition.getIndex()); @@ -799,7 +804,7 @@ public Number parse(String text, ParsePosition parsePosition, double upperBound, continue; } - tempResult = rules[i].doParse(text, parsePosition, isFractionRuleSet, upperBound, nonNumericalExecutedRuleMask); + tempResult = rules[i].doParse(text, parsePosition, isFractionRuleSet, upperBound, nonNumericalExecutedRuleMask, recursionCount + 1); if (parsePosition.getIndex() > highWaterMark.getIndex()) { result = tempResult; highWaterMark.setIndex(parsePosition.getIndex()); diff --git a/icu4j/main/core/src/main/java/com/ibm/icu/text/NFSubstitution.java b/icu4j/main/core/src/main/java/com/ibm/icu/text/NFSubstitution.java index e97570786215..e0e4d9db53cf 100644 --- a/icu4j/main/core/src/main/java/com/ibm/icu/text/NFSubstitution.java +++ b/icu4j/main/core/src/main/java/com/ibm/icu/text/NFSubstitution.java @@ -420,7 +420,7 @@ public void doSubstitution(double number, StringBuilder toInsertInto, int positi * is left unchanged. */ public Number doParse(String text, ParsePosition parsePosition, double baseValue, - double upperBound, boolean lenientParse, int nonNumericalExecutedRuleMask) { + double upperBound, boolean lenientParse, int nonNumericalExecutedRuleMask, int recursionCount) { Number tempResult; // figure out the highest base value a rule can have and match @@ -438,7 +438,7 @@ public Number doParse(String text, ParsePosition parsePosition, double baseValue // on), then also try parsing the text using a default- // constructed NumberFormat if (ruleSet != null) { - tempResult = ruleSet.parse(text, parsePosition, upperBound, nonNumericalExecutedRuleMask); + tempResult = ruleSet.parse(text, parsePosition, upperBound, nonNumericalExecutedRuleMask, recursionCount); if (lenientParse && !ruleSet.isFractionSet() && parsePosition.getIndex() == 0) { tempResult = ruleSet.owner.getDecimalFormat().parse(text, parsePosition); } @@ -1012,17 +1012,17 @@ public double transformNumber(double number) { */ @Override public Number doParse(String text, ParsePosition parsePosition, double baseValue, - double upperBound, boolean lenientParse, int nonNumericalExecutedRuleMask) { + double upperBound, boolean lenientParse, int nonNumericalExecutedRuleMask, int recursionCount) { // if this isn't a >>> substitution, we can just use the // inherited parse() routine to do the parsing if (ruleToUse == null) { - return super.doParse(text, parsePosition, baseValue, upperBound, lenientParse, nonNumericalExecutedRuleMask); + return super.doParse(text, parsePosition, baseValue, upperBound, lenientParse, nonNumericalExecutedRuleMask, recursionCount); } else { // but if it IS a >>> substitution, we have to do it here: we // use the specific rule's doParse() method, and then we have to // do some of the other work of NFRuleSet.parse() - Number tempResult = ruleToUse.doParse(text, parsePosition, false, upperBound, nonNumericalExecutedRuleMask); + Number tempResult = ruleToUse.doParse(text, parsePosition, false, upperBound, nonNumericalExecutedRuleMask, recursionCount); if (parsePosition.getIndex() != 0) { double result = tempResult.doubleValue(); @@ -1317,11 +1317,11 @@ public double transformNumber(double number) { */ @Override public Number doParse(String text, ParsePosition parsePosition, double baseValue, - double upperBound, boolean lenientParse, int nonNumericalExecutedRuleMask) { + double upperBound, boolean lenientParse, int nonNumericalExecutedRuleMask, int recursionCount) { // if we're not in byDigits mode, we can just use the inherited // doParse() if (!byDigits) { - return super.doParse(text, parsePosition, baseValue, 0, lenientParse, nonNumericalExecutedRuleMask); + return super.doParse(text, parsePosition, baseValue, 0, lenientParse, nonNumericalExecutedRuleMask, recursionCount); } else { // if we ARE in byDigits mode, parse the text one digit at a time @@ -1337,7 +1337,7 @@ public Number doParse(String text, ParsePosition parsePosition, double baseValue int totalDigits = 0; while (workText.length() > 0 && workPos.getIndex() != 0) { workPos.setIndex(0); - digit = ruleSet.parse(workText, workPos, 10, nonNumericalExecutedRuleMask).intValue(); + digit = ruleSet.parse(workText, workPos, 10, nonNumericalExecutedRuleMask, recursionCount).intValue(); if (lenientParse && workPos.getIndex() == 0) { Number n = ruleSet.owner.getDecimalFormat().parse(workText, workPos); if (n != null) { @@ -1640,7 +1640,7 @@ public double transformNumber(double number) { */ @Override public Number doParse(String text, ParsePosition parsePosition, double baseValue, - double upperBound, boolean lenientParse, int nonNumericalExecutedRuleMask) { + double upperBound, boolean lenientParse, int nonNumericalExecutedRuleMask, int recursionCount) { // we don't have to do anything special to do the parsing here, // but we have to turn lenient parsing off-- if we leave it on, // it SERIOUSLY messes up the algorithm @@ -1655,7 +1655,7 @@ public Number doParse(String text, ParsePosition parsePosition, double baseValue while (workText.length() > 0 && workPos.getIndex() != 0) { workPos.setIndex(0); - /*digit = */ruleSet.parse(workText, workPos, 1, nonNumericalExecutedRuleMask).intValue(); // parse zero or nothing at all + /*digit = */ruleSet.parse(workText, workPos, 1, nonNumericalExecutedRuleMask, recursionCount).intValue(); // parse zero or nothing at all if (workPos.getIndex() == 0) { // we failed, either there were no more zeros, or the number was formatted with digits // either way, we're done @@ -1676,7 +1676,7 @@ public Number doParse(String text, ParsePosition parsePosition, double baseValue } // we've parsed off the zeros, now let's parse the rest from our current position - Number result = super.doParse(text, parsePosition, withZeros ? 1 : baseValue, upperBound, false, nonNumericalExecutedRuleMask); + Number result = super.doParse(text, parsePosition, withZeros ? 1 : baseValue, upperBound, false, nonNumericalExecutedRuleMask, recursionCount); if (withZeros) { // any base value will do in this case. is there a way to diff --git a/icu4j/main/core/src/main/java/com/ibm/icu/text/RuleBasedNumberFormat.java b/icu4j/main/core/src/main/java/com/ibm/icu/text/RuleBasedNumberFormat.java index 41a2d5e6269b..60f2f30b3999 100644 --- a/icu4j/main/core/src/main/java/com/ibm/icu/text/RuleBasedNumberFormat.java +++ b/icu4j/main/core/src/main/java/com/ibm/icu/text/RuleBasedNumberFormat.java @@ -1329,7 +1329,7 @@ public Number parse(String text, ParsePosition parsePosition) { // try parsing the string with the rule set. If it gets past the // high-water mark, update the high-water mark and the result - tempResult = ruleSets[i].parse(workingText, workingPos, Double.MAX_VALUE, 0); + tempResult = ruleSets[i].parse(workingText, workingPos, Double.MAX_VALUE, 0, 0); if (workingPos.getIndex() > highWaterMark.getIndex()) { result = tempResult; highWaterMark.setIndex(workingPos.getIndex());