ICU-21134 Copy additional data when toNumberFormatter is used

See #1156
This commit is contained in:
Shane F. Carr 2020-05-28 02:13:07 +00:00
parent ec7e29f2b6
commit 3ff6627ce6
5 changed files with 131 additions and 29 deletions

View File

@ -13,6 +13,7 @@
#include "number_asformat.h"
#include "number_utils.h"
#include "number_utypes.h"
#include "number_mapper.h"
#include "util.h"
#include "fphdlimp.h"
@ -400,7 +401,8 @@ LocalizedNumberFormatter::LocalizedNumberFormatter(const LNF& other)
LocalizedNumberFormatter::LocalizedNumberFormatter(const NFS<LNF>& other)
: NFS<LNF>(other) {
// No additional fields to assign (let call count and compiled formatter reset to defaults)
UErrorCode localStatus = U_ZERO_ERROR; // Can't bubble up the error
lnfCopyHelper(static_cast<const LNF&>(other), localStatus);
}
LocalizedNumberFormatter::LocalizedNumberFormatter(LocalizedNumberFormatter&& src) U_NOEXCEPT
@ -408,38 +410,25 @@ LocalizedNumberFormatter::LocalizedNumberFormatter(LocalizedNumberFormatter&& sr
LocalizedNumberFormatter::LocalizedNumberFormatter(NFS<LNF>&& src) U_NOEXCEPT
: NFS<LNF>(std::move(src)) {
// For the move operators, copy over the compiled formatter.
// Note: if the formatter is not compiled, call count information is lost.
if (static_cast<LNF&&>(src).fCompiled != nullptr) {
lnfMoveHelper(static_cast<LNF&&>(src));
}
lnfMoveHelper(std::move(static_cast<LNF&&>(src)));
}
LocalizedNumberFormatter& LocalizedNumberFormatter::operator=(const LNF& other) {
NFS<LNF>::operator=(static_cast<const NFS<LNF>&>(other));
// Reset to default values.
clear();
UErrorCode localStatus = U_ZERO_ERROR; // Can't bubble up the error
lnfCopyHelper(other, localStatus);
return *this;
}
LocalizedNumberFormatter& LocalizedNumberFormatter::operator=(LNF&& src) U_NOEXCEPT {
NFS<LNF>::operator=(static_cast<NFS<LNF>&&>(src));
// For the move operators, copy over the compiled formatter.
// Note: if the formatter is not compiled, call count information is lost.
if (static_cast<LNF&&>(src).fCompiled != nullptr) {
// Formatter is compiled
lnfMoveHelper(static_cast<LNF&&>(src));
} else {
clear();
}
lnfMoveHelper(std::move(src));
return *this;
}
void LocalizedNumberFormatter::clear() {
// Reset to default values.
void LocalizedNumberFormatter::resetCompiled() {
auto* callCount = reinterpret_cast<u_atomic_int32_t*>(fUnsafeCallCount);
umtx_storeRelease(*callCount, 0);
delete fCompiled;
fCompiled = nullptr;
}
@ -447,19 +436,56 @@ void LocalizedNumberFormatter::lnfMoveHelper(LNF&& src) {
// Copy over the compiled formatter and set call count to INT32_MIN as in computeCompiled().
// Don't copy the call count directly because doing so requires a loadAcquire/storeRelease.
// The bits themselves appear to be platform-dependent, so copying them might not be safe.
delete fCompiled;
if (src.fCompiled != nullptr) {
auto* callCount = reinterpret_cast<u_atomic_int32_t*>(fUnsafeCallCount);
umtx_storeRelease(*callCount, INT32_MIN);
delete fCompiled;
fCompiled = src.fCompiled;
// Reset the source object to leave it in a safe state.
auto* srcCallCount = reinterpret_cast<u_atomic_int32_t*>(src.fUnsafeCallCount);
umtx_storeRelease(*srcCallCount, 0);
src.fCompiled = nullptr;
src.resetCompiled();
} else {
resetCompiled();
}
// Unconditionally move the warehouse
delete fWarehouse;
fWarehouse = src.fWarehouse;
src.fWarehouse = nullptr;
}
void LocalizedNumberFormatter::lnfCopyHelper(const LNF&, UErrorCode& status) {
// When copying, always reset the compiled formatter.
delete fCompiled;
resetCompiled();
// If MacroProps has a reference to AffixPatternProvider, we need to copy it.
// If MacroProps has a reference to PluralRules, copy that one, too.
delete fWarehouse;
if (fMacros.affixProvider || fMacros.rules) {
LocalPointer<DecimalFormatWarehouse> warehouse(new DecimalFormatWarehouse(), status);
if (U_FAILURE(status)) {
fWarehouse = nullptr;
return;
}
if (fMacros.affixProvider) {
warehouse->affixProvider.setTo(fMacros.affixProvider, status);
fMacros.affixProvider = &warehouse->affixProvider.get();
}
if (fMacros.rules) {
warehouse->rules.adoptInsteadAndCheckErrorCode(
new PluralRules(*fMacros.rules), status);
fMacros.rules = warehouse->rules.getAlias();
}
fWarehouse = warehouse.orphan();
} else {
fWarehouse = nullptr;
}
}
LocalizedNumberFormatter::~LocalizedNumberFormatter() {
delete fCompiled;
delete fWarehouse;
}
LocalizedNumberFormatter::LocalizedNumberFormatter(const MacroProps& macros, const Locale& locale) {

View File

@ -136,6 +136,16 @@ class AutoAffixPatternProvider {
}
}
inline void setTo(const AffixPatternProvider* provider, UErrorCode& status) {
if (auto ptr = dynamic_cast<const PropertiesAffixPatternProvider*>(provider)) {
propertiesAPP = *ptr;
} else if (auto ptr = dynamic_cast<const CurrencyPluralInfoAffixProvider*>(provider)) {
currencyPluralInfoAPP = *ptr;
} else {
status = U_INTERNAL_PROGRAM_ERROR;
}
}
inline const AffixPatternProvider& get() const {
if (!currencyPluralInfoAPP.isBogus()) {
return currencyPluralInfoAPP;
@ -153,9 +163,9 @@ class AutoAffixPatternProvider {
/**
* A struct for ownership of a few objects needed for formatting.
*/
struct DecimalFormatWarehouse {
struct DecimalFormatWarehouse : public UMemory {
AutoAffixPatternProvider affixProvider;
LocalPointer<PluralRules> rules;
};

View File

@ -157,6 +157,7 @@ struct RangeMacroProps;
struct UFormattedNumberImpl;
class MutablePatternModifier;
class ImmutablePatternModifier;
struct DecimalFormatWarehouse;
/**
* Used for NumberRangeFormatter and implemented in numrange_fluent.cpp.
@ -2385,6 +2386,10 @@ class U_I18N_API LocalizedNumberFormatter
const impl::NumberFormatterImpl* fCompiled {nullptr};
char fUnsafeCallCount[8] {}; // internally cast to u_atomic_int32_t
// Owned pointer to a DecimalFormatWarehouse, used when copying a LocalizedNumberFormatter
// from a DecimalFormat.
const impl::DecimalFormatWarehouse* fWarehouse {nullptr};
explicit LocalizedNumberFormatter(const NumberFormatterSettings<LocalizedNumberFormatter>& other);
explicit LocalizedNumberFormatter(NumberFormatterSettings<LocalizedNumberFormatter>&& src) U_NOEXCEPT;
@ -2393,10 +2398,12 @@ class U_I18N_API LocalizedNumberFormatter
LocalizedNumberFormatter(impl::MacroProps &&macros, const Locale &locale);
void clear();
void resetCompiled();
void lnfMoveHelper(LocalizedNumberFormatter&& src);
void lnfCopyHelper(const LocalizedNumberFormatter& src, UErrorCode& status);
/**
* @return true if the compiled formatter is available.
*/

View File

@ -245,6 +245,7 @@ void NumberFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &n
TESTCASE_AUTO(Test13735_GroupingSizeGetter);
TESTCASE_AUTO(Test13734_StrictFlexibleWhitespace);
TESTCASE_AUTO(Test20961_CurrencyPluralPattern);
TESTCASE_AUTO(Test21134_ToNumberFormatter);
TESTCASE_AUTO_END;
}
@ -9835,4 +9836,61 @@ void NumberFormatTest::Test20961_CurrencyPluralPattern() {
}
}
void NumberFormatTest::Test21134_ToNumberFormatter() {
IcuTestErrorCode status(*this, "Test21134_ToNumberFormatter");
LocalizedNumberFormatter outer1;
LocalizedNumberFormatter outer2;
LocalPointer<LocalizedNumberFormatter> outer3;
{
// Case 1: new formatter object
DecimalFormat inner(u"a0b", {"en", status}, status);
if (auto ptr = inner.toNumberFormatter(status)) {
// Copy assignment
outer1 = *ptr;
} else {
status.errIfFailureAndReset();
return;
}
}
{
// Case 2: compiled formatter object (used at least 3 times)
DecimalFormat inner(u"c0d", {"en", status}, status);
UnicodeString dummy;
inner.format(100, dummy);
inner.format(100, dummy);
inner.format(100, dummy);
if (auto ptr = inner.toNumberFormatter(status)) {
// Copy assignment
outer2 = *ptr;
} else {
status.errIfFailureAndReset();
return;
}
}
{
// Case 3: currency plural info (different code path)
LocalPointer<DecimalFormat> inner(static_cast<DecimalFormat*>(
DecimalFormat::createInstance("en-US", UNUM_CURRENCY_PLURAL, status)));
if (auto ptr = inner->toNumberFormatter(status)) {
// Copy constructor
outer3.adoptInsteadAndCheckErrorCode(new LocalizedNumberFormatter(*ptr), status);
} else {
status.errIfFailureAndReset();
return;
}
}
auto result1 = outer1.formatDouble(99, status);
assertEquals("Using NumberFormatter from DecimalFormat, new version",
u"a99b",
result1.toTempString(status));
auto result2 = outer2.formatDouble(99, status);
assertEquals("Using NumberFormatter from DecimalFormat, compiled version",
u"c99d",
result2.toTempString(status));
auto result3 = outer3->formatDouble(99, status);
assertEquals("Using NumberFormatter from DecimalFormat, compiled version",
u"99.00 US dollars",
result3.toTempString(status));
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View File

@ -301,6 +301,7 @@ class NumberFormatTest: public CalendarTimeZoneTest {
void Test13735_GroupingSizeGetter();
void Test13734_StrictFlexibleWhitespace();
void Test20961_CurrencyPluralPattern();
void Test21134_ToNumberFormatter();
private:
UBool testFormattableAsUFormattable(const char *file, int line, Formattable &f);