diff --git a/icu4c/source/common/cmemory.h b/icu4c/source/common/cmemory.h index bc1c91c1500..ac36d10f7ab 100644 --- a/icu4c/source/common/cmemory.h +++ b/icu4c/source/common/cmemory.h @@ -50,6 +50,7 @@ #define UPRV_LENGTHOF(array) (int32_t)(sizeof(array)/sizeof((array)[0])) #define uprv_memset(buffer, mark, size) U_STANDARD_CPP_NAMESPACE memset(buffer, mark, size) #define uprv_memcmp(buffer1, buffer2, size) U_STANDARD_CPP_NAMESPACE memcmp(buffer1, buffer2,size) +#define uprv_memchr(ptr, value, num) U_STANDARD_CPP_NAMESPACE memchr(ptr, value, num) U_CAPI void * U_EXPORT2 uprv_malloc(size_t s) U_MALLOC_ATTR U_ALLOC_SIZE_ATTR(1); diff --git a/icu4c/source/i18n/currunit.cpp b/icu4c/source/i18n/currunit.cpp index 7f3490d406b..a5b039fcab2 100644 --- a/icu4c/source/i18n/currunit.cpp +++ b/icu4c/source/i18n/currunit.cpp @@ -18,8 +18,10 @@ #include "unicode/ustring.h" #include "cstring.h" #include "uinvchar.h" +#include "charstr.h" static constexpr char16_t kDefaultCurrency[] = u"XXX"; +static constexpr char kDefaultCurrency8[] = "XXX"; U_NAMESPACE_BEGIN @@ -50,6 +52,30 @@ CurrencyUnit::CurrencyUnit(ConstChar16Ptr _isoCode, UErrorCode& ec) { initCurrency(simpleIsoCode); } +CurrencyUnit::CurrencyUnit(StringPiece _isoCode, UErrorCode& ec) { + // Note: unlike the old constructor, reject empty arguments with an error. + char isoCodeBuffer[4]; + const char* isoCodeToUse; + // uprv_memchr checks that the string contains no internal NULs + if (_isoCode.length() != 3 || uprv_memchr(_isoCode.data(), 0, 3) != nullptr) { + isoCodeToUse = kDefaultCurrency8; + ec = U_ILLEGAL_ARGUMENT_ERROR; + } else if (!uprv_isInvariantString(_isoCode.data(), 3)) { + // TODO: Perform a more strict ASCII check like in ICU4J isAlpha3Code? + isoCodeToUse = kDefaultCurrency8; + ec = U_INVARIANT_CONVERSION_ERROR; + } else { + // Have to use isoCodeBuffer to ensure the string is NUL-terminated + uprv_strncpy(isoCodeBuffer, _isoCode.data(), 3); + isoCodeBuffer[3] = 0; + isoCodeToUse = isoCodeBuffer; + } + // TODO: Perform uppercasing here like in ICU4J Currency.getInstance()? + u_charsToUChars(isoCodeToUse, isoCode, 3); + isoCode[3] = 0; + initCurrency(isoCodeToUse); +} + CurrencyUnit::CurrencyUnit(const CurrencyUnit& other) : MeasureUnit(other) { u_strcpy(isoCode, other.isoCode); } diff --git a/icu4c/source/i18n/number_formatimpl.cpp b/icu4c/source/i18n/number_formatimpl.cpp index c64703699cd..de6ec65486b 100644 --- a/icu4c/source/i18n/number_formatimpl.cpp +++ b/icu4c/source/i18n/number_formatimpl.cpp @@ -178,7 +178,7 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, bool isAccounting = macros.sign == UNUM_SIGN_ACCOUNTING || macros.sign == UNUM_SIGN_ACCOUNTING_ALWAYS || macros.sign == UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO; - CurrencyUnit currency(nullptr, status); + CurrencyUnit currency(u"", status); if (isCurrency) { currency = CurrencyUnit(macros.unit, status); // Restore CurrencyUnit from MeasureUnit } diff --git a/icu4c/source/i18n/unicode/currunit.h b/icu4c/source/i18n/unicode/currunit.h index ac3bfedd7b3..bf6bd9ac323 100644 --- a/icu4c/source/i18n/unicode/currunit.h +++ b/icu4c/source/i18n/unicode/currunit.h @@ -44,6 +44,7 @@ class U_I18N_API CurrencyUnit: public MeasureUnit { /** * Construct an object with the given ISO currency code. + * * @param isoCode the 3-letter ISO 4217 currency code; must have * length 3 and need not be NUL-terminated. If NULL, the currency * is initialized to the unknown currency XXX. @@ -53,6 +54,17 @@ class U_I18N_API CurrencyUnit: public MeasureUnit { */ CurrencyUnit(ConstChar16Ptr isoCode, UErrorCode &ec); + /** + * Construct an object with the given ISO currency code. + * + * @param isoCode the 3-letter ISO 4217 currency code; must have + * length 3. If invalid, the currency is initialized to XXX. + * @param ec input-output error code. If the isoCode is invalid, + * then this will be set to a failing value. + * @draft ICU 64 + */ + CurrencyUnit(StringPiece isoCode, UErrorCode &ec); + /** * Copy constructor * @stable ICU 3.0 diff --git a/icu4c/source/test/depstest/dependencies.txt b/icu4c/source/test/depstest/dependencies.txt index 7bdcf66a509..a54f024f1ff 100644 --- a/icu4c/source/test/depstest/dependencies.txt +++ b/icu4c/source/test/depstest/dependencies.txt @@ -57,7 +57,7 @@ group: c_strings __ctype_b_loc # for # We must not use tolower and toupper because they are system-locale-sensitive (Turkish i). strlen strchr strrchr strstr strcmp strncmp strcpy strncpy strcat strncat - memcmp memcpy memmove memset + memchr memcmp memcpy memmove memset # Additional symbols in an optimized build. __strcpy_chk __strncpy_chk __strcat_chk __strncat_chk __rawmemchr __memcpy_chk __memmove_chk __memset_chk diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp index 3cb3ea9af17..c3533039923 100644 --- a/icu4c/source/test/intltest/numfmtst.cpp +++ b/icu4c/source/test/intltest/numfmtst.cpp @@ -2119,12 +2119,63 @@ void NumberFormatTest::TestCurrencyUnit(void){ static const UChar BAD2[] = u"??A"; static const UChar XXX[] = u"XXX"; static const char XXX8[] = "XXX"; + static const UChar INV[] = u"{$%"; + static const char INV8[] = "{$%"; + static const UChar ZZZ[] = u"zz"; + static const char ZZZ8[] = "zz"; + + UChar* EUR = (UChar*) malloc(6); + EUR[0] = u'E'; + EUR[1] = u'U'; + EUR[2] = u'R'; + char* EUR8 = (char*) malloc(3); + EUR8[0] = 'E'; + EUR8[1] = 'U'; + EUR8[2] = 'R'; + CurrencyUnit cu(USD, ec); assertSuccess("CurrencyUnit", ec); assertEquals("getISOCurrency()", USD, cu.getISOCurrency()); assertEquals("getSubtype()", USD8, cu.getSubtype()); + CurrencyUnit inv(INV, ec); + assertEquals("non-invariant", U_INVARIANT_CONVERSION_ERROR, ec); + assertEquals("non-invariant", XXX, inv.getISOCurrency()); + ec = U_ZERO_ERROR; + + CurrencyUnit zzz(ZZZ, ec); + assertEquals("too short", U_ILLEGAL_ARGUMENT_ERROR, ec); + assertEquals("too short", XXX, zzz.getISOCurrency()); + ec = U_ZERO_ERROR; + + CurrencyUnit eur(EUR, ec); + assertEquals("non-nul-terminated", u"EUR", eur.getISOCurrency()); + assertEquals("non-nul-terminated", "EUR", eur.getSubtype()); + + // Test StringPiece constructor + CurrencyUnit cu8(USD8, ec); + assertEquals("StringPiece constructor", USD, cu8.getISOCurrency()); + + CurrencyUnit inv8(INV8, ec); + assertEquals("non-invariant 8", U_INVARIANT_CONVERSION_ERROR, ec); + assertEquals("non-invariant 8", XXX, inv8.getISOCurrency()); + ec = U_ZERO_ERROR; + + CurrencyUnit zzz8(ZZZ8, ec); + assertEquals("too short 8", U_ILLEGAL_ARGUMENT_ERROR, ec); + assertEquals("too short 8", XXX, zzz8.getISOCurrency()); + ec = U_ZERO_ERROR; + + CurrencyUnit zzz8b({ZZZ8, 3}, ec); + assertEquals("too short 8b", U_ILLEGAL_ARGUMENT_ERROR, ec); + assertEquals("too short 8b", XXX, zzz8b.getISOCurrency()); + ec = U_ZERO_ERROR; + + CurrencyUnit eur8({EUR8, 3}, ec); + assertEquals("non-nul-terminated 8", u"EUR", eur8.getISOCurrency()); + assertEquals("non-nul-terminated 8", "EUR", eur8.getSubtype()); + CurrencyUnit cu2(cu); if (!(cu2 == cu)){ errln("CurrencyUnit copy constructed object should be same"); @@ -2177,6 +2228,9 @@ void NumberFormatTest::TestCurrencyUnit(void){ CurrencyUnit failure(*meter, ec); assertEquals("Copying from meter should fail", ec, U_ILLEGAL_ARGUMENT_ERROR); assertEquals("Copying should not give uninitialized ISO code", u"", failure.getISOCurrency()); + + uprv_free(EUR); + uprv_free(EUR8); } void NumberFormatTest::TestCurrencyAmount(void){