ICU-13847 ICU-20381 Improve handling of errors (Out-of-Memory) in DecimalFormat class.

- Use move assignment for fields->formatter (LocalizedNumberFormatter) instead of creating new heap object every time.
- Add test cases for DecimalFormat object in invalid state.
- Protect against self-assignment in assignment operator.
- Fix segmentation fault when attempting to compare valid and invalid DecimalFormat objects.
- Changes based on review feedback from Shane.
- Fix minor typos in the public header file.
This commit is contained in:
Jeff Genovy 2019-02-04 14:29:09 -08:00
parent 48776d132a
commit 00596d3027
7 changed files with 778 additions and 70 deletions

File diff suppressed because it is too large Load diff

View file

@ -21,6 +21,7 @@ char kRawDefaultProperties[sizeof(DecimalFormatProperties)];
icu::UInitOnce gDefaultPropertiesInitOnce = U_INITONCE_INITIALIZER;
void U_CALLCONV initDefaultProperties(UErrorCode&) {
// can't fail, uses placement new into staticly allocated space.
new(kRawDefaultProperties) DecimalFormatProperties(); // set to the default instance
}
@ -142,4 +143,10 @@ bool DecimalFormatProperties::equalsDefaultExceptFastFormat() const {
return _equals(*reinterpret_cast<DecimalFormatProperties*>(kRawDefaultProperties), true);
}
const DecimalFormatProperties& DecimalFormatProperties::getDefault() {
UErrorCode localStatus = U_ZERO_ERROR;
umtx_initOnce(gDefaultPropertiesInitOnce, &initDefaultProperties, localStatus);
return *reinterpret_cast<const DecimalFormatProperties*>(kRawDefaultProperties);
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -155,6 +155,11 @@ struct U_I18N_API DecimalFormatProperties : public UMemory {
*/
bool equalsDefaultExceptFastFormat() const;
/**
* Returns the default DecimalFormatProperties instance.
*/
static const DecimalFormatProperties& getDefault();
private:
bool _equals(const DecimalFormatProperties& other, bool ignoreForFastFormat) const;
};

View file

@ -136,7 +136,7 @@ struct DecimalFormatFields : public UMemory {
* The pre-computed formatter object. Setters cause this to be re-computed atomically. The {@link
* #format} method uses the formatter directly without needing to synchronize.
*/
LocalPointer<const LocalizedNumberFormatter> formatter;
LocalPointer<LocalizedNumberFormatter> formatter;
/** The lazy-computed parser for .parse() */
std::atomic<::icu::numparse::impl::NumberParserImpl*> atomicParser = {};

View file

@ -288,7 +288,7 @@ template class U_I18N_API EnumSet<UNumberFormatAttribute,
* <td>Pad escape, precedes pad character
* </table>
*
* <p>A DecimalFormat pattern contains a postive and negative
* <p>A DecimalFormat pattern contains a positive and negative
* subpattern, for example, "#,##0.00;(#,##0.00)". Each subpattern has a
* prefix, a numeric part, and a suffix. If there is no explicit negative
* subpattern, the negative subpattern is the localized minus sign prefixed to the
@ -423,7 +423,7 @@ template class U_I18N_API EnumSet<UNumberFormatAttribute,
*
* <li>If the number of actual fraction digits is less than the
* <em>minimum fraction digits</em>, then trailing zeros are added.
* For example, 0.125 is formatted as "0.1250" if the mimimum fraction
* For example, 0.125 is formatted as "0.1250" if the minimum fraction
* digits is set to 4.
*
* <li>Trailing fractional zeros are not displayed if they occur
@ -588,9 +588,9 @@ template class U_I18N_API EnumSet<UNumberFormatAttribute,
* count of <code>getMaximumSignificantDigits() - 1</code>. For example, the
* pattern <code>"@@###E0"</code> is equivalent to <code>"0.0###E0"</code>.
*
* <li>If signficant digits are in use, then the integer and fraction
* <li>If significant digits are in use, then the integer and fraction
* digit counts, as set via the API, are ignored. If significant
* digits are not in use, then the signficant digit counts, as set via
* digits are not in use, then the significant digit counts, as set via
* the API, are ignored.
*
* </ul>
@ -644,7 +644,7 @@ template class U_I18N_API EnumSet<UNumberFormatAttribute,
* increment in the pattern itself. "#,#50" specifies a rounding increment of
* 50. "#,##0.05" specifies a rounding increment of 0.05.
*
* <p>In the absense of an explicit rounding increment numbers are
* <p>In the absence of an explicit rounding increment numbers are
* rounded to their formatted width.
*
* <ul>
@ -849,7 +849,7 @@ class U_I18N_API DecimalFormat : public NumberFormat {
* @param pattern a non-localized pattern string
* @param symbolsToAdopt the set of symbols to be used. The caller should not
* delete this object after making this call.
* @param parseError Output param to receive errors occured during parsing
* @param parseError Output param to receive errors occurred during parsing
* @param status Output param set to success/failure code. If the
* pattern is invalid this will be set to a failure code.
* @stable ICU 2.0
@ -1127,7 +1127,7 @@ class U_I18N_API DecimalFormat : public NumberFormat {
* does string comparisons to try to find an optimal match.
* If no object can be parsed, index is unchanged, and NULL is
* returned. The result is returned as the most parsimonious
* type of Formattable that will accomodate all of the
* type of Formattable that will accommodate all of the
* necessary precision. For example, if the result is exactly 12,
* it will be returned as a long. However, if it is 1.5, it will
* be returned as a double.
@ -1464,8 +1464,8 @@ class U_I18N_API DecimalFormat : public NumberFormat {
* Set the character used to pad to the format width. If padding
* is not enabled, then this will take effect if padding is later
* enabled.
* @param padChar a string containing the pad charcter. If the string
* has length 0, then the pad characer is set to ' '. Otherwise
* @param padChar a string containing the pad character. If the string
* has length 0, then the pad character is set to ' '. Otherwise
* padChar.char32At(0) will be used as the pad character.
* @see #setFormatWidth
* @see #getFormatWidth
@ -2117,7 +2117,7 @@ class U_I18N_API DecimalFormat : public NumberFormat {
/** Rebuilds the formatter object from the property bag. */
void touch(UErrorCode& status);
/** Rebuilds the formatter object, hiding the error code. */
/** Rebuilds the formatter object, ignoring any error code. */
void touchNoError();
/**
@ -2156,8 +2156,10 @@ class U_I18N_API DecimalFormat : public NumberFormat {
// INSTANCE FIELDS //
//=====================================================================================//
// Only one instance field: keep all fields inside of an implementation class defined in number_mapper.h
number::impl::DecimalFormatFields* fields;
// One instance field for the implementation, keep all fields inside of an implementation
// class defined in number_mapper.h
number::impl::DecimalFormatFields* fields = nullptr;
// Allow child class CompactDecimalFormat to access fProperties:
friend class CompactDecimalFormat;

View file

@ -95,6 +95,12 @@ void IntlTestDecimalFormatAPI::runIndexedTest( int32_t index, UBool exec, const
testErrorCode();
}
break;
case 9: name = "testInvalidObject";
if(exec) {
logln((UnicodeString) "testInvalidObject ---");
testInvalidObject();
}
break;
default: name = ""; break;
}
}
@ -1077,7 +1083,7 @@ void IntlTestDecimalFormatAPI::testErrorCode() {
}
// Try each DecimalFormat method with an error code parameter, verifying that
// an input error is not altered.
// an input error is not altered, and that no segmentation faults occur.
status = U_INTERNAL_PROGRAM_ERROR;
DecimalFormat dfBogus(status);
@ -1140,4 +1146,238 @@ void IntlTestDecimalFormatAPI::testErrorCode() {
}
}
void IntlTestDecimalFormatAPI::testInvalidObject() {
{
UErrorCode status = U_INTERNAL_PROGRAM_ERROR;
DecimalFormat dfBogus(status);
assertEquals(WHERE, U_INTERNAL_PROGRAM_ERROR, status);
status = U_ZERO_ERROR;
DecimalFormat dfGood(status);
assertSuccess(WHERE, status);
// An invalid object should not be equal to a valid object.
// This also tests that no segmentation fault occurs in the comparison operator due
// to any dangling/nullptr pointers. (ICU-20381).
assertTrue(WHERE, dfGood != dfBogus);
status = U_MEMORY_ALLOCATION_ERROR;
DecimalFormat dfBogus2(status);
assertEquals(WHERE, U_MEMORY_ALLOCATION_ERROR, status);
// Two invalid objects should not be equal.
// (Also verify that nullptr isn't t dereferenced in the comparision operator.)
assertTrue(WHERE, dfBogus != dfBogus2);
// Verify the comparison operator works for two valid objects.
status = U_ZERO_ERROR;
DecimalFormat dfGood2(status);
assertSuccess(WHERE, status);
assertTrue(WHERE, dfGood == dfGood2);
// Verify that the assignment operator sets the object to an invalid state, and
// that no segmentation fault occurs due to any dangling/nullptr pointers.
status = U_INTERNAL_PROGRAM_ERROR;
DecimalFormat dfAssignmentBogus = DecimalFormat(status);
// Verify comparison for the assigned object.
assertTrue(WHERE, dfAssignmentBogus != dfGood);
assertTrue(WHERE, dfAssignmentBogus != dfGood2);
assertTrue(WHERE, dfAssignmentBogus != dfBogus);
// Verify that cloning our original invalid object gives nullptr.
auto dfBogusClone = dfBogus.clone();
assertTrue(WHERE, dfBogusClone == nullptr);
// Verify that cloning our assigned invalid object gives nullptr.
auto dfBogusClone2 = dfAssignmentBogus.clone();
assertTrue(WHERE, dfBogusClone2 == nullptr);
// Verify copy constructing from an invalid object is also invalid.
DecimalFormat dfCopy(dfBogus);
assertTrue(WHERE, dfCopy != dfGood);
assertTrue(WHERE, dfCopy != dfGood2);
assertTrue(WHERE, dfCopy != dfBogus);
DecimalFormat dfCopyAssign = dfBogus;
assertTrue(WHERE, dfCopyAssign != dfGood);
assertTrue(WHERE, dfCopyAssign != dfGood2);
assertTrue(WHERE, dfCopyAssign != dfBogus);
auto dfBogusCopyClone1 = dfCopy.clone();
auto dfBogusCopyClone2 = dfCopyAssign.clone();
assertTrue(WHERE, dfBogusCopyClone1 == nullptr);
assertTrue(WHERE, dfBogusCopyClone2 == nullptr);
}
{
// Try each DecimalFormat class method that lacks an error code parameter, verifying
// we don't crash (segmentation fault) on invalid objects.
UErrorCode status = U_ZERO_ERROR;
const UnicodeString pattern(u"0.###E0");
UParseError pe;
DecimalFormatSymbols symbols(Locale::getUS(), status);
assertSuccess(WHERE, status);
CurrencyPluralInfo currencyPI(status);
assertSuccess(WHERE, status);
status = U_INTERNAL_PROGRAM_ERROR;
DecimalFormat dfBogus1(status);
assertEquals(WHERE, U_INTERNAL_PROGRAM_ERROR, status);
status = U_INTERNAL_PROGRAM_ERROR;
DecimalFormat dfBogus2(pattern, status);
assertEquals(WHERE, U_INTERNAL_PROGRAM_ERROR, status);
status = U_INTERNAL_PROGRAM_ERROR;
DecimalFormat dfBogus3(pattern, new DecimalFormatSymbols(symbols), status);
assertEquals(WHERE, U_INTERNAL_PROGRAM_ERROR, status);
status = U_INTERNAL_PROGRAM_ERROR;
DecimalFormat dfBogus4(pattern, new DecimalFormatSymbols(symbols), UNumberFormatStyle::UNUM_CURRENCY, status);
assertEquals(WHERE, U_INTERNAL_PROGRAM_ERROR, status);
status = U_INTERNAL_PROGRAM_ERROR;
DecimalFormat dfBogus5(pattern, new DecimalFormatSymbols(symbols), pe, status);
assertEquals(WHERE, U_INTERNAL_PROGRAM_ERROR, status);
for (DecimalFormat *df : {&dfBogus1, &dfBogus2, &dfBogus3, &dfBogus4, &dfBogus5})
{
df->setGroupingUsed(true);
df->setParseIntegerOnly(false);
df->setLenient(true);
auto dfClone = df->clone();
assertTrue(WHERE, dfClone == nullptr);
UnicodeString dest;
FieldPosition fp;
df->format(1.2, dest, fp);
df->format(static_cast<int32_t>(1234), dest, fp);
df->format(static_cast<int64_t>(1234), dest, fp);
UnicodeString text("-1,234.00");
Formattable result;
ParsePosition pos(0);
df->parse(text, result, pos);
CurrencyAmount* ca = df->parseCurrency(text, pos);
assertTrue(WHERE, ca == nullptr);
const DecimalFormatSymbols* dfs = df->getDecimalFormatSymbols();
assertTrue(WHERE, dfs == nullptr);
df->adoptDecimalFormatSymbols(nullptr);
df->setDecimalFormatSymbols(symbols);
const CurrencyPluralInfo* cpi = df->getCurrencyPluralInfo();
assertTrue(WHERE, cpi == nullptr);
df->adoptCurrencyPluralInfo(nullptr);
df->setCurrencyPluralInfo(currencyPI);
UnicodeString prefix("-123");
df->getPositivePrefix(dest);
df->setPositivePrefix(prefix);
df->getNegativePrefix(dest);
df->setNegativePrefix(prefix);
df->getPositiveSuffix(dest);
df->setPositiveSuffix(prefix);
df->getNegativeSuffix(dest);
df->setNegativeSuffix(prefix);
df->isSignAlwaysShown();
df->setSignAlwaysShown(true);
df->getMultiplier();
df->setMultiplier(10);
df->getMultiplierScale();
df->setMultiplierScale(2);
df->getRoundingIncrement();
df->setRoundingIncrement(1.2);
df->getRoundingMode();
df->setRoundingMode(DecimalFormat::ERoundingMode::kRoundDown);
df->getFormatWidth();
df->setFormatWidth(0);
UnicodeString pad(" ");
df->getPadCharacterString();
df->setPadCharacter(pad);
df->getPadPosition();
df->setPadPosition(DecimalFormat::EPadPosition::kPadBeforePrefix);
df->isScientificNotation();
df->setScientificNotation(false);
df->getMinimumExponentDigits();
df->setMinimumExponentDigits(1);
df->isExponentSignAlwaysShown();
df->setExponentSignAlwaysShown(true);
df->getGroupingSize();
df->setGroupingSize(3);
df->getSecondaryGroupingSize();
df->setSecondaryGroupingSize(-1);
df->getMinimumGroupingDigits();
df->setMinimumGroupingDigits(-1);
df->isDecimalSeparatorAlwaysShown();
df->setDecimalSeparatorAlwaysShown(true);
df->isDecimalPatternMatchRequired();
df->setDecimalPatternMatchRequired(false);
df->isParseNoExponent();
df->setParseNoExponent(true);
df->isParseCaseSensitive();
df->setParseCaseSensitive(false);
df->isFormatFailIfMoreThanMaxDigits();
df->setFormatFailIfMoreThanMaxDigits(true);
df->toPattern(dest);
df->toLocalizedPattern(dest);
df->setMaximumIntegerDigits(10);
df->setMinimumIntegerDigits(0);
df->setMaximumFractionDigits(2);
df->setMinimumFractionDigits(0);
df->getMinimumSignificantDigits();
df->setMinimumSignificantDigits(0);
df->getMaximumSignificantDigits();
df->setMaximumSignificantDigits(5);
df->areSignificantDigitsUsed();
df->setSignificantDigitsUsed(true);
df->setCurrency(u"USD");
df->getCurrencyUsage();
// TODO: See ICU-20366 and ICU-20380.
// This method should return a pointer, or at least have an error code parameter,
const number::LocalizedNumberFormatter& lnf = df->toNumberFormatter();
(void)lnf; // suppress unused variable warning.
// Note: The existance of a null reference is undefined behavior in C++.
// Attempting to touch/examine a null reference is also undefined. Doing
// so generally will cause a crash or segmentation fault.
}
}
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -34,6 +34,7 @@ public:
void TestBadFastpath();
void TestRequiredDecimalPoint();
void testErrorCode();
void testInvalidObject();
private:
/*Helper functions */
void verify(const UnicodeString& message, const UnicodeString& got, double expected);