[njs] Fixed Number.prototype.toString(radix).
Dmitry Volyntsev
xeioex at nginx.com
Tue Dec 3 12:11:25 UTC 2019
details: https://hg.nginx.org/njs/rev/5b1bf60c8ede
branches:
changeset: 1272:5b1bf60c8ede
user: Dmitry Volyntsev <xeioex at nginx.com>
date: Tue Dec 03 14:59:26 2019 +0300
description:
Fixed Number.prototype.toString(radix).
Fixed buffer-overflow in Number.prototype.toString(radix) when
fraction == delta == 0. The last comparison might by true for very
small numbers (denormals) around zero when fast-math mode is enabled.
The issue was introduced in 5f0812d53158.
diffstat:
auto/clang | 13 ++++
src/njs_clang.h | 18 +++++
src/njs_number.c | 4 +-
src/njs_shell.c | 16 +++++
src/test/njs_unit_test.c | 150 +++++++++++++++++++++++++++++++++++++++-------
5 files changed, 175 insertions(+), 26 deletions(-)
diffs (326 lines):
diff -r 01c7375c9b5c -r 5b1bf60c8ede auto/clang
--- a/auto/clang Fri Nov 29 12:53:33 2019 +0300
+++ b/auto/clang Tue Dec 03 14:59:26 2019 +0300
@@ -177,3 +177,16 @@ njs_feature_test="#include <math.h>
return 0;
}"
. auto/feature
+
+
+njs_feature="_mm_setcsr()"
+njs_feature_name=NJS_HAVE_DENORMALS_CONTROL
+njs_feature_run=no
+njs_feature_incs=
+njs_feature_libs=
+njs_feature_test="#include <xmmintrin.h>
+ int main(void) {
+ _mm_setcsr(_mm_getcsr());
+ return 0;
+ }"
+. auto/feature
diff -r 01c7375c9b5c -r 5b1bf60c8ede src/njs_clang.h
--- a/src/njs_clang.h Fri Nov 29 12:53:33 2019 +0300
+++ b/src/njs_clang.h Tue Dec 03 14:59:26 2019 +0300
@@ -165,6 +165,24 @@ njs_leading_zeros64(uint64_t x)
#endif
+#if (NJS_HAVE_DENORMALS_CONTROL)
+#include <xmmintrin.h>
+
+/*
+ * 0x8000 Flush to zero
+ * 0x0040 Denormals are zeros
+ */
+
+#define NJS_MM_DENORMALS_MASK 0x8040
+
+#define njs_mm_denormals(on) \
+ _mm_setcsr((_mm_getcsr() & ~NJS_MM_DENORMALS_MASK) | (!(on) ? 0x8040: 0x0))
+#else
+
+#define njs_mm_denormals(on)
+#endif
+
+
#ifndef NJS_MAX_ALIGNMENT
#if (NJS_SOLARIS)
diff -r 01c7375c9b5c -r 5b1bf60c8ede src/njs_number.c
--- a/src/njs_number.c Fri Nov 29 12:53:33 2019 +0300
+++ b/src/njs_number.c Tue Dec 03 14:59:26 2019 +0300
@@ -565,7 +565,7 @@ njs_number_prototype_to_string(njs_vm_t
number = njs_number(value);
- if (radix != 10 && !isnan(number) && !isinf(number)) {
+ if (radix != 10 && !isnan(number) && !isinf(number) && number != 0) {
return njs_number_to_string_radix(vm, &vm->retval, number, radix);
}
}
@@ -838,7 +838,7 @@ njs_number_to_string_radix(njs_vm_t *vm,
delta = 0.5 * (njs_number_next_double(n) - n);
delta = njs_max(njs_number_next_double(0.0), delta);
- if (fraction >= delta) {
+ if (fraction >= delta && delta != 0) {
*p++ = '.';
do {
diff -r 01c7375c9b5c -r 5b1bf60c8ede src/njs_shell.c
--- a/src/njs_shell.c Fri Nov 29 12:53:33 2019 +0300
+++ b/src/njs_shell.c Tue Dec 03 14:59:26 2019 +0300
@@ -26,6 +26,7 @@
typedef struct {
uint8_t disassemble;
+ uint8_t denormals;
uint8_t interactive;
uint8_t module;
uint8_t quiet;
@@ -232,6 +233,8 @@ main(int argc, char **argv)
goto done;
}
+ njs_mm_denormals(opts.denormals);
+
njs_memzero(&vm_options, sizeof(njs_vm_opt_t));
if (opts.file == NULL) {
@@ -306,6 +309,7 @@ njs_get_options(njs_opts_t *opts, int ar
"Options:\n"
" -c specify the command to execute.\n"
" -d print disassembled code.\n"
+ " -f disabled denormals mode.\n"
" -p set path prefix for modules.\n"
" -q disable interactive introduction prompt.\n"
" -s sandbox mode.\n"
@@ -316,6 +320,8 @@ njs_get_options(njs_opts_t *opts, int ar
ret = NJS_DONE;
+ opts->denormals = 1;
+
for (i = 1; i < argc; i++) {
p = argv[i];
@@ -349,6 +355,16 @@ njs_get_options(njs_opts_t *opts, int ar
opts->disassemble = 1;
break;
+ case 'f':
+
+#if !(NJS_HAVE_DENORMALS_CONTROL)
+ njs_stderror("option \"-f\" is not supported\n");
+ return NJS_ERROR;
+#endif
+
+ opts->denormals = 0;
+ break;
+
case 'p':
if (++i < argc) {
opts->n_paths++;
diff -r 01c7375c9b5c -r 5b1bf60c8ede src/test/njs_unit_test.c
--- a/src/test/njs_unit_test.c Fri Nov 29 12:53:33 2019 +0300
+++ b/src/test/njs_unit_test.c Tue Dec 03 14:59:26 2019 +0300
@@ -361,13 +361,23 @@ static njs_unit_test_t njs_test[] =
/* Number.toString(radix) method. */
-#ifndef NJS_SUNC
{ njs_str("0..toString(2)"),
njs_str("0") },
-#endif
-
- { njs_str("240..toString(2)"),
- njs_str("11110000") },
+
+ { njs_str("(1234.567).toString(3)"),
+ njs_str("1200201.120022100021001021021002202") },
+
+ { njs_str("(1234.567).toString(5)"),
+ njs_str("14414.240414141414141414") },
+
+ { njs_str("(1234.567).toString(17)"),
+ njs_str("44a.9aeb6faa0da") },
+
+ { njs_str("(1234.567).toString(36)"),
+ njs_str("ya.kety9sifl") },
+
+ { njs_str("Number(-1.1).toString(36)"),
+ njs_str("-1.3llllllllm") },
{ njs_str("Math.pow(-2, 1023).toString(2).length"),
njs_str("1025") },
@@ -439,10 +449,6 @@ static njs_unit_test_t njs_test[] =
{ njs_str("1e20.toString(14)"),
njs_str("33cb3bb449c2a92000") },
- /* Smallest positive double (next_double(0)). */
- { njs_str("4.94065645841246544176568792868E-324.toString(36) == ('0.' + '0'.repeat(207) +'3')"),
- njs_str("true") },
-
{ njs_str("1.7976931348623157E+308.toString(36) == ('1a1e4vngaiqo' + '0'.repeat(187))"),
njs_str("true") },
@@ -450,13 +456,6 @@ static njs_unit_test_t njs_test[] =
{ njs_str("1.7976931348623157E+308.toString(2) == ('1'.repeat(53) + '0'.repeat(971))"),
njs_str("true") },
- /* Maximum fraction length. */
- { njs_str("2.2250738585072014E-323.toString(2) == ('0.' + '0'.repeat(1071) + '101')"),
- njs_str("true") },
-
- { njs_str("2.2250738585072014E-308.toString(2) == ('0.' + '0'.repeat(1021) + '1')"),
- njs_str("true") },
-
{ njs_str("Array(5).fill().map((n, i) => i + 10).map((v)=>(1.2312313132).toString(v))"),
njs_str("1.2312313132,1.25a850416057383,1.293699002749414,1.3010274cab0288,1.3346da6d5d455c") },
@@ -639,14 +638,6 @@ static njs_unit_test_t njs_test[] =
{ njs_str("Array(5).fill().map((n, i) => i + 1).map((v)=>((Math.pow(-1,v))*(2*v)/3).toExponential())"),
njs_str("-6.666666666666666e-1,1.3333333333333333e+0,-2e+0,2.6666666666666667e+0,-3.3333333333333337e+0") },
-#ifndef NJS_SUNC
- { njs_str("4.94065645841246544176568792868e-324.toExponential()"),
- njs_str("5e-324") },
-
- { njs_str("4.94065645841246544176568792868e-324.toExponential(10)"),
- njs_str("4.9406564584e-324") },
-#endif
-
{ njs_str("1.7976931348623157e+308.toExponential()"),
njs_str("1.7976931348623157e+308") },
@@ -15343,6 +15334,84 @@ static njs_unit_test_t njs_test[] =
};
+static njs_unit_test_t njs_denormals_test[] =
+{
+ { njs_str("2.2250738585072014e-308"),
+ njs_str("2.2250738585072014e-308") },
+
+#ifndef NJS_SUNC
+ { njs_str("2.2250738585072014E-308.toString(2) == ('0.' + '0'.repeat(1021) + '1')"),
+ njs_str("true") },
+
+ { njs_str("Number('2.2250738585072014E-323')"),
+ njs_str("2.5e-323") },
+
+ { njs_str("Number('2.2250738585072014E-323') + 0"),
+ njs_str("2.5e-323") },
+
+ /* Smallest positive double (next_double(0)). */
+ { njs_str("5E-324.toString(36) === '0.' + '0'.repeat(207) + '3'"),
+ njs_str("true") },
+
+ /* Maximum fraction length. */
+ { njs_str("2.2250738585072014E-323.toString(2) == ('0.' + '0'.repeat(1071) + '101')"),
+ njs_str("true") },
+
+ /* Denormals. */
+ { njs_str("var zeros = count => '0'.repeat(count);"
+ "["
+ " [1.8858070859709815e-308, `0.${zeros(1022)}1101100011110111011100000100011001111101110001010111`],"
+ // FIXME: " [Number.MIN_VALUE, `0.${zeros(1073)}1`]"
+ " [-5.06631661953108e-309, `-0.${zeros(1024)}11101001001010000001101111010101011111111011010111`],"
+ " [6.22574126804e-313, `0.${zeros(1037)}11101010101101100111000110100111001`],"
+ " [-4e-323, `-0.${zeros(1070)}1`],"
+ "].every(t=>t[0].toString(2) === t[1])"),
+ njs_str("true") },
+
+ { njs_str("4.94065645841246544176568792868e-324.toExponential()"),
+ njs_str("5e-324") },
+
+ { njs_str("4.94065645841246544176568792868e-324.toExponential(10)"),
+ njs_str("4.9406564584e-324") },
+#endif
+
+};
+
+
+static njs_unit_test_t njs_disabled_denormals_test[] =
+{
+ { njs_str("Number('2.2250738585072014E-323')"),
+ njs_str("0") },
+
+ { njs_str("Number('2.2250738585072014E-323') + 0"),
+ njs_str("0") },
+
+ /* Smallest positive double (next_double(0)). */
+ { njs_str("5E-324.toString(36)"),
+ njs_str("0") },
+
+ { njs_str("2.2250738585072014E-323.toString(2)"),
+ njs_str("0") },
+
+ /* Smallest normal double. */
+
+ { njs_str("2.2250738585072014e-308"),
+ njs_str("2.2250738585072014e-308") },
+
+ { njs_str("2.2250738585072014e-308/2"),
+ njs_str("0") },
+
+ /* Denormals. */
+ { njs_str("["
+ "1.8858070859709815e-308,"
+ "-5.06631661953108e-309,"
+ "6.22574126804e-313,"
+ "-4e-323,"
+ "].map(v=>v.toString(2))"),
+ njs_str("0,0,0,0") },
+};
+
+
static njs_unit_test_t njs_module_test[] =
{
{ njs_str("function f(){return 2}; var f; f()"),
@@ -16816,12 +16885,45 @@ main(int argc, char **argv)
opts.repeat = 1;
opts.unsafe = 1;
+ njs_mm_denormals(1);
+
ret = njs_unit_test(njs_test, njs_nitems(njs_test), "script tests",
&opts, &stat);
if (ret != NJS_OK) {
return ret;
}
+ ret = njs_unit_test(njs_denormals_test, njs_nitems(njs_denormals_test),
+ "denormals tests", &opts, &stat);
+ if (ret != NJS_OK) {
+ return ret;
+ }
+
+#if (NJS_HAVE_DENORMALS_CONTROL)
+
+ njs_mm_denormals(0);
+
+ ret = njs_unit_test(njs_test, njs_nitems(njs_test),
+ "script tests (disabled denormals)", &opts, &stat);
+ if (ret != NJS_OK) {
+ return ret;
+ }
+
+ ret = njs_unit_test(njs_disabled_denormals_test,
+ njs_nitems(njs_disabled_denormals_test),
+ "disabled denormals tests", &opts, &stat);
+ if (ret != NJS_OK) {
+ return ret;
+ }
+
+ njs_mm_denormals(1);
+
+#else
+
+ (void) njs_disabled_denormals_test;
+
+#endif
+
ret = njs_timezone_optional_test(&opts, &stat);
if (ret != NJS_OK) {
return ret;
More information about the nginx-devel
mailing list