Skip to content

Commit 4a0a838

Browse files
committed
Fix dec2frac infinite looping in some edge cases, found by fuzzing.
1 parent 6239645 commit 4a0a838

2 files changed

Lines changed: 61 additions & 6 deletions

File tree

src/tivarslib_utils.cpp

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include "tivarslib_utils.h"
99
#include <cstdlib>
10+
#include <iomanip>
1011
#include <sstream>
1112
#include <cmath>
1213
#include <cstring>
@@ -19,6 +20,8 @@ namespace tivars
1920

2021
namespace
2122
{
23+
constexpr long long dec2fracMaxDenominator = 100000;
24+
2225
void append_le16(data_t& out, uint16_t value)
2326
{
2427
out.push_back(static_cast<uint8_t>(value & 0xFF));
@@ -67,6 +70,15 @@ namespace
6770

6871
return out;
6972
}
73+
74+
std::string format_decimal_value(long double value)
75+
{
76+
std::ostringstream stream;
77+
stream << std::setprecision(std::numeric_limits<long double>::digits10 + 1)
78+
<< std::defaultfloat
79+
<< value;
80+
return stream.str();
81+
}
7082
}
7183

7284
unsigned char hexdec(const std::string& str)
@@ -291,14 +303,18 @@ std::string multiple(long long num, const std::string &var) {
291303
// Adapted from http://stackoverflow.com/a/32903747/378298
292304
std::string dec2frac(double num, const std::string& var, double err)
293305
{
306+
const auto decimal_fallback = [&](long double value) {
307+
return trimZeros(format_decimal_value(value)) + var;
308+
};
309+
294310
if (err <= 0.0 || err >= 1.0)
295311
{
296312
err = 0.001;
297313
}
298314

299315
if (!std::isfinite(num))
300316
{
301-
return trimZeros(std::to_string(num)) + var;
317+
return trimZeros(format_decimal_value(num)) + var;
302318
}
303319

304320
const int sign = ( num > 0 ) ? 1 : ( ( num < 0 ) ? -1 : 0 );
@@ -318,7 +334,7 @@ std::string dec2frac(double num, const std::string& var, double err)
318334
if (num >= static_cast<double>(std::numeric_limits<long long>::max())
319335
|| num <= static_cast<double>(std::numeric_limits<long long>::lowest()))
320336
{
321-
return trimZeros(std::to_string(sign * num)) + var;
337+
return decimal_fallback(static_cast<long double>(sign) * static_cast<long double>(num));
322338
}
323339

324340
const long long n = static_cast<long long>(std::floor(num));
@@ -342,17 +358,36 @@ std::string dec2frac(double num, const std::string& var, double err)
342358
long long upper_n = 1;
343359
long long upper_d = 1;
344360

361+
long long max_denominator = dec2fracMaxDenominator;
362+
const long double err_denominator_bound = std::ceil(1.0L / static_cast<long double>(err));
363+
if (std::isfinite(err_denominator_bound))
364+
{
365+
max_denominator = std::min<long long>(max_denominator, std::max<long long>(1, static_cast<long long>(err_denominator_bound)));
366+
}
367+
368+
size_t iterations = 0;
369+
const size_t max_iterations = static_cast<size_t>(max_denominator) * 2;
370+
345371
while (true)
346372
{
373+
if (++iterations > max_iterations)
374+
{
375+
return decimal_fallback(static_cast<long double>(sign) * (static_cast<long double>(n) + static_cast<long double>(num)));
376+
}
377+
347378
// The middle fraction is (lower_n + upper_n) / (lower_d + upper_d)
348379
if (lower_n > std::numeric_limits<long long>::max() - upper_n
349380
|| lower_d > std::numeric_limits<long long>::max() - upper_d)
350381
{
351-
return trimZeros(std::to_string(sign * (n + num))) + var;
382+
return decimal_fallback(static_cast<long double>(sign) * (static_cast<long double>(n) + static_cast<long double>(num)));
352383
}
353384

354385
const long long middle_n = lower_n + upper_n;
355386
const long long middle_d = lower_d + upper_d;
387+
if (middle_d > max_denominator)
388+
{
389+
return decimal_fallback(static_cast<long double>(sign) * (static_cast<long double>(n) + static_cast<long double>(num)));
390+
}
356391

357392
if (static_cast<long double>(middle_d) * static_cast<long double>(num + err) < static_cast<long double>(middle_n))
358393
{
@@ -369,11 +404,11 @@ std::string dec2frac(double num, const std::string& var, double err)
369404
// Middle is our best fraction
370405
if (n != 0 && middle_d > std::numeric_limits<long long>::max() / std::llabs(n))
371406
{
372-
return trimZeros(std::to_string(sign * (n + num))) + var;
407+
return decimal_fallback(static_cast<long double>(sign) * (static_cast<long double>(n) + static_cast<long double>(num)));
373408
}
374409
if (sign != 0 && std::llabs(n * middle_d + middle_n) > std::numeric_limits<long long>::max() / std::llabs(sign))
375410
{
376-
return trimZeros(std::to_string(sign * (n + num))) + var;
411+
return decimal_fallback(static_cast<long double>(sign) * (static_cast<long double>(n) + static_cast<long double>(num)));
377412
}
378413
return multiple((n * middle_d + middle_n) * sign, var) + "/" + std::to_string(middle_d);
379414
}
@@ -382,7 +417,25 @@ std::string dec2frac(double num, const std::string& var, double err)
382417

383418
std::string trimZeros(const std::string& str)
384419
{
385-
return std::to_string(std::stoi(str));
420+
try
421+
{
422+
const long double value = std::stold(str);
423+
if (!std::isfinite(value))
424+
{
425+
return str;
426+
}
427+
428+
std::string out = format_decimal_value(value);
429+
if (out == "-0")
430+
{
431+
return "0";
432+
}
433+
return out;
434+
}
435+
catch (const std::exception&)
436+
{
437+
return str;
438+
}
386439
}
387440

388441
std::string entry_name_to_string(const TIVarType& type, const uint8_t* nameBytes, size_t size)

tests.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,8 @@ int main(int argc, char** argv)
189189
/* Tests */
190190

191191
assert(TIVarType{"ExactRealPi"}.getId() == 32);
192+
assert(dec2frac(0.25) == "1/4");
193+
assert(dec2frac(1.0 / 100001.0, "", 1e-12) != "0");
192194

193195
{
194196
TIFlashFile flashOs = TIFlashFile::loadFromFile("testData/TI-84_Plus_CE-Python-OS-5.8.0.0022.8eu");

0 commit comments

Comments
 (0)