[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