]> git.kaiwu.me - njs.git/commitdiff
Fixed Number.prototype.toString(radix).
authorDmitry Volyntsev <xeioex@nginx.com>
Tue, 3 Dec 2019 11:59:26 +0000 (14:59 +0300)
committerDmitry Volyntsev <xeioex@nginx.com>
Tue, 3 Dec 2019 11:59:26 +0000 (14:59 +0300)
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.

auto/clang
src/njs_clang.h
src/njs_number.c
src/njs_shell.c
src/test/njs_unit_test.c

index 77f41b65be2258b3adefc3e84c4ff1a1838f6abe..82130b2ad75d310879920f14bebeafbe9c59f536 100644 (file)
@@ -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
index 3a7c03da198d6a6215bbe4c1ca83e79b563deed5..967c9ad63bf7601cdd35911ac573ded015c0e749 100644 (file)
@@ -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)
index 175fa2127691c259186877900d24b6f66260387e..8e3476d9f8cea72ba2cd24a9d7295e35d5082dee 100644 (file)
@@ -565,7 +565,7 @@ njs_number_prototype_to_string(njs_vm_t *vm, njs_value_t *args,
 
         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, njs_value_t *string,
     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 {
index 1f4c9452ece7988ba73012eacd50d8183ff81593..958acb5fa9d92ec229e8ac6179c04cdec0e668a5 100644 (file)
@@ -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 argc, char **argv)
         "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 argc, char **argv)
 
     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 argc, char **argv)
             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++;
index eb4542a2bf3bea3ee1fe855dcf4836e828a9c211..185c8715094482e8a294ccd4a5b9e9ad0a816f33 100644 (file)
@@ -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;