Add additional check on float precision after parsing, only compile the

value to a float when the difference between float and double parsed
from same raw string is smaller than 1.

Bug: b/69347762
Test: Verified affected atests pass and added new atest
Change-Id: I25da0baccba580484db39aa2d0a1bb765706635d
This commit is contained in:
Brandon Liu
2023-03-31 22:37:42 +00:00
parent e1e4129f4f
commit c674d38c8c
6 changed files with 104 additions and 18 deletions

View File

@@ -5436,37 +5436,66 @@ bool ResTable::stringToInt(const char16_t* s, size_t len, Res_value* outValue)
return U16StringToInt(s, len, outValue);
}
bool ResTable::stringToFloat(const char16_t* s, size_t len, Res_value* outValue)
{
while (len > 0 && isspace16(*s)) {
s++;
len--;
template <typename T>
bool parseFloatingPoint(const char16_t* inBuf, size_t inLen, char* tempBuf,
const char** outEnd, T& out){
while (inLen > 0 && isspace16(*inBuf)) {
inBuf++;
inLen--;
}
if (len <= 0) {
if (inLen <= 0) {
return false;
}
char buf[128];
int i=0;
while (len > 0 && *s != 0 && i < 126) {
if (*s > 255) {
while (inLen > 0 && *inBuf != 0 && i < 126) {
if (*inBuf > 255) {
return false;
}
buf[i++] = *s++;
len--;
tempBuf[i++] = *inBuf++;
inLen--;
}
if (len > 0) {
if (inLen > 0) {
return false;
}
if ((buf[0] < '0' || buf[0] > '9') && buf[0] != '.' && buf[0] != '-' && buf[0] != '+') {
if ((tempBuf[0] < '0' || tempBuf[0] > '9') && tempBuf[0] != '.' && tempBuf[0] != '-' && tempBuf[0] != '+') {
return false;
}
buf[i] = 0;
const char* end;
float f = strtof(buf, (char**)&end);
tempBuf[i] = 0;
if constexpr(std::is_same_v<T, float>) {
out = strtof(tempBuf, (char**)outEnd);
} else {
out = strtod(tempBuf, (char**)outEnd);
}
return true;
}
bool ResTable::stringToDouble(const char16_t* s, size_t len, double& d){
char buf[128];
const char* end = nullptr;
if (!parseFloatingPoint(s, len, buf, &end, d)) {
return false;
}
while (*end != 0 && isspace((unsigned char)*end)) {
end++;
}
return *end == 0;
}
bool ResTable::stringToFloat(const char16_t* s, size_t len, Res_value* outValue)
{
char buf[128];
const char* end = nullptr;
float f;
if (!parseFloatingPoint(s, len, buf, &end, f)) {
return false;
}
if (*end != 0 && !isspace((unsigned char)*end)) {
// Might be a unit...

View File

@@ -2162,6 +2162,7 @@ public:
static bool stringToInt(const char16_t* s, size_t len, Res_value* outValue);
static bool stringToFloat(const char16_t* s, size_t len, Res_value* outValue);
static bool stringToDouble(const char16_t* s, size_t len, double& outValue);
// Used with stringToValue.
class Accessor

View File

@@ -670,8 +670,26 @@ std::unique_ptr<Item> TryParseItemForAttribute(
// Try parsing this as a float.
auto floating_point = TryParseFloat(value);
if (floating_point) {
// Only check if the parsed result lost precision when the parsed item is
// android::Res_value::TYPE_FLOAT and there is other possible types saved in type_mask, like
// ResTable_map::TYPE_INTEGER.
if (type_mask & AndroidTypeToAttributeTypeMask(floating_point->value.dataType)) {
return std::move(floating_point);
const bool mayOnlyBeFloat = (type_mask & ~float_mask) == 0;
const bool parsedAsFloat = floating_point->value.dataType == android::Res_value::TYPE_FLOAT;
if (!mayOnlyBeFloat && parsedAsFloat) {
float f = reinterpret_cast<float&>(floating_point->value.data);
std::u16string str16 = android::util::Utf8ToUtf16(util::TrimWhitespace(value));
double d;
if (android::ResTable::stringToDouble(str16.data(), str16.size(), d)) {
// Parse as a float only if the difference between float and double parsed from the
// same string is smaller than 1, otherwise return as raw string.
if (fabs(f - d) < 1) {
return std::move(floating_point);
}
}
} else {
return std::move(floating_point);
}
}
}
}

View File

@@ -228,6 +228,26 @@ TEST(ResourceUtilsTest, ItemsWithWhitespaceAreParsedCorrectly) {
Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_FLOAT, expected_float_flattened))));
}
TEST(ResourceUtilsTest, FloatAndBigIntegerParsedCorrectly) {
const float expected_float = 0.125f;
const uint32_t expected_float_flattened = *(uint32_t*)&expected_float;
EXPECT_THAT(ResourceUtils::TryParseItemForAttribute("0.125", ResTable_map::TYPE_FLOAT),
Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_FLOAT, expected_float_flattened))));
const float special_float = 1.0f;
const uint32_t special_float_flattened = *(uint32_t*)&special_float;
EXPECT_THAT(ResourceUtils::TryParseItemForAttribute("1.0", ResTable_map::TYPE_FLOAT),
Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_FLOAT, special_float_flattened))));
EXPECT_EQ(ResourceUtils::TryParseItemForAttribute("1099511627776", ResTable_map::TYPE_INTEGER),
std::unique_ptr<Item>(nullptr));
const float big_float = 1099511627776.0f;
const uint32_t big_flattened = *(uint32_t*)&big_float;
EXPECT_THAT(ResourceUtils::TryParseItemForAttribute("1099511627776", ResTable_map::TYPE_FLOAT),
Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_FLOAT, big_flattened))));
}
TEST(ResourceUtilsTest, ParseSdkVersionWithCodename) {
EXPECT_THAT(ResourceUtils::ParseSdkVersion("Q"), Eq(std::optional<int>(10000)));
EXPECT_THAT(ResourceUtils::ParseSdkVersion("Q.fingerprint"), Eq(std::optional<int>(10000)));

View File

@@ -439,6 +439,21 @@ static std::string ComplexToString(uint32_t complex_value, bool fraction) {
return str;
}
// This function is designed to using different specifier to print different floats,
// which can print more accurate format rather than using %g only.
const char* BinaryPrimitive::DecideFormat(float f) {
// if the float is either too big or too tiny, print it in scientific notation.
// eg: "10995116277760000000000" to 1.099512e+22, "0.00000000001" to 1.000000e-11
if (fabs(f) > std::numeric_limits<int64_t>::max() || fabs(f) < 1e-10) {
return "%e";
// Else if the number is an integer exactly, print it without trailing zeros.
// eg: "1099511627776" to 1099511627776
} else if (int64_t(f) == f) {
return "%.0f";
}
return "%g";
}
void BinaryPrimitive::PrettyPrint(Printer* printer) const {
using ::android::Res_value;
switch (value.dataType) {
@@ -470,7 +485,9 @@ void BinaryPrimitive::PrettyPrint(Printer* printer) const {
break;
case Res_value::TYPE_FLOAT:
printer->Print(StringPrintf("%g", *reinterpret_cast<const float*>(&value.data)));
float f;
f = *reinterpret_cast<const float*>(&value.data);
printer->Print(StringPrintf(DecideFormat(f), f));
break;
case Res_value::TYPE_DIMENSION:

View File

@@ -284,6 +284,7 @@ struct BinaryPrimitive : public TransformableItem<BinaryPrimitive, BaseItem<Bina
bool Equals(const Value* value) const override;
bool Flatten(android::Res_value* out_value) const override;
void Print(std::ostream* out) const override;
static const char* DecideFormat(float f);
void PrettyPrint(text::Printer* printer) const override;
};