Make converting JSString to StringView idiomatically safe
authordarin@apple.com <darin@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 27 Jun 2015 22:53:12 +0000 (22:53 +0000)
committerdarin@apple.com <darin@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 27 Jun 2015 22:53:12 +0000 (22:53 +0000)
https://bugs.webkit.org/show_bug.cgi?id=146387

Reviewed by Anders Carlsson.

* jsc.cpp:
(functionPrint): Add explicit call to SafeView::get, needed since there
is no StringView temporary.
(functionDebug): Ditto.

* runtime/ArrayPrototype.cpp:
(JSC::holesMustForwardToPrototype): Refactored into helper function.
(JSC::join): Refactored so that StringView is a function argument, making
the lifetime simpler.
(JSC::arrayProtoFuncJoin): Ditto.
(JSC::arrayProtoFuncReverse): Use new holesMustForwardToPrototype helper.

* runtime/JSGlobalObjectFunctions.cpp:
(JSC::encode): Add explicit call to SafeView::get.

* runtime/JSString.h: Moved declarations of functions to the top of the
file instead of mixing them in with the function definitions. Changed
return type of the view function to return a JSString::SafeView so that
the JSString's lifetime will last as long as the StringView does in
typical coding idioms.
(JSC::JSString::getIndex): Use unsafeView so we can index into the
view; could also have used view.get but here in this class this seems fine.
(JSC::JSRopeString::unsafeView): Renamed existing view function to this.
(JSC::JSString::unsafeView): Ditto.
(JSC::JSString::SafeView::SafeView): Contains reference to an ExecState
and a JSString. The ExecState is needed to create the StringView, and the
JSString needs to be kept alive as long as the StringView is.
(JSC::JSString::SafeView::operator StringView): Call unsafeView.
(JSC::JSString::SafeView::get): Convenience for when we want to call
StringView member functions.
(JSC::JSString::view): Added. Returns a SafeView.

* runtime/StringPrototype.cpp:
(JSC::stringProtoFuncIndexOf): Add explicit call to SafeView::get.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@186037 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/jsc.cpp
Source/JavaScriptCore/runtime/ArrayPrototype.cpp
Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp
Source/JavaScriptCore/runtime/JSString.h
Source/JavaScriptCore/runtime/StringPrototype.cpp

index 990be35..88a6894 100644 (file)
@@ -1,3 +1,45 @@
+2015-06-27  Darin Adler  <darin@apple.com>
+
+        Make converting JSString to StringView idiomatically safe
+        https://bugs.webkit.org/show_bug.cgi?id=146387
+
+        Reviewed by Anders Carlsson.
+
+        * jsc.cpp:
+        (functionPrint): Add explicit call to SafeView::get, needed since there
+        is no StringView temporary.
+        (functionDebug): Ditto.
+
+        * runtime/ArrayPrototype.cpp:
+        (JSC::holesMustForwardToPrototype): Refactored into helper function.
+        (JSC::join): Refactored so that StringView is a function argument, making
+        the lifetime simpler.
+        (JSC::arrayProtoFuncJoin): Ditto.
+        (JSC::arrayProtoFuncReverse): Use new holesMustForwardToPrototype helper.
+
+        * runtime/JSGlobalObjectFunctions.cpp:
+        (JSC::encode): Add explicit call to SafeView::get.
+
+        * runtime/JSString.h: Moved declarations of functions to the top of the
+        file instead of mixing them in with the function definitions. Changed
+        return type of the view function to return a JSString::SafeView so that
+        the JSString's lifetime will last as long as the StringView does in
+        typical coding idioms.
+        (JSC::JSString::getIndex): Use unsafeView so we can index into the
+        view; could also have used view.get but here in this class this seems fine.
+        (JSC::JSRopeString::unsafeView): Renamed existing view function to this.
+        (JSC::JSString::unsafeView): Ditto.
+        (JSC::JSString::SafeView::SafeView): Contains reference to an ExecState
+        and a JSString. The ExecState is needed to create the StringView, and the
+        JSString needs to be kept alive as long as the StringView is.
+        (JSC::JSString::SafeView::operator StringView): Call unsafeView.
+        (JSC::JSString::SafeView::get): Convenience for when we want to call
+        StringView member functions.
+        (JSC::JSString::view): Added. Returns a SafeView.
+
+        * runtime/StringPrototype.cpp:
+        (JSC::stringProtoFuncIndexOf): Add explicit call to SafeView::get.
+
 2015-06-26  Csaba Osztrogon√°c  <ossy@webkit.org>
 
         Remove ARMv7Assembler.cpp
index 02c6731..f300d1d 100644 (file)
@@ -701,7 +701,7 @@ EncodedJSValue JSC_HOST_CALL functionPrint(ExecState* exec)
         if (i)
             putchar(' ');
 
-        printf("%s", exec->uncheckedArgument(i).toString(exec)->view(exec).utf8().data());
+        printf("%s", exec->uncheckedArgument(i).toString(exec)->view(exec).get().utf8().data());
     }
 
     putchar('\n');
@@ -722,7 +722,7 @@ EncodedJSValue JSC_HOST_CALL functionDumpCallFrame(ExecState* exec)
 
 EncodedJSValue JSC_HOST_CALL functionDebug(ExecState* exec)
 {
-    fprintf(stderr, "--> %s\n", exec->argument(0).toString(exec)->view(exec).utf8().data());
+    fprintf(stderr, "--> %s\n", exec->argument(0).toString(exec)->view(exec).get().utf8().data());
     return JSValue::encode(jsUndefined());
 }
 
index c80eaa6..e682429 100644 (file)
@@ -40,7 +40,6 @@
 #include "ObjectConstructor.h"
 #include "ObjectPrototype.h"
 #include "StringRecursionChecker.h"
-#include "StrongInlines.h"
 #include <algorithm>
 #include <wtf/Assertions.h>
 #include <wtf/HashSet.h>
@@ -380,31 +379,17 @@ template<typename T> static inline bool containsHole(T* data, unsigned length)
     return false;
 }
 
-EncodedJSValue JSC_HOST_CALL arrayProtoFuncJoin(ExecState* exec)
+static inline bool holesMustForwardToPrototype(ExecState& state, JSObject* object)
 {
-    JSObject* thisObject = exec->thisValue().toThis(exec, StrictMode).toObject(exec);
-
-    unsigned length = getLength(exec, thisObject);
-    if (exec->hadException())
-        return JSValue::encode(jsUndefined());
-
-    StringRecursionChecker checker(exec, thisObject);
-    if (JSValue earlyReturnValue = checker.earlyReturnValue())
-        return JSValue::encode(earlyReturnValue);
-
-    auto& vm = exec->vm();
+    auto& vm = state.vm();
+    return object->structure(vm)->holesMustForwardToPrototype(vm);
+}
 
-    Strong<JSString> separatorOnHeap;
-    StringView separator;
-    if (exec->argument(0).isUndefined()) {
-        static const LChar comma = ',';
-        separator = { &comma, 1 };
-    } else {
-        separatorOnHeap.set(vm, exec->argument(0).toString(exec));
-        if (exec->hadException())
-            return JSValue::encode(jsUndefined());
-        separator = separatorOnHeap->view(exec);
-    }
+static inline JSValue join(ExecState& state, JSObject* thisObject, StringView separator)
+{
+    unsigned length = getLength(&state, thisObject);
+    if (state.hadException())
+        return jsUndefined();
 
     switch (thisObject->indexingType()) {
     case ALL_CONTIGUOUS_INDEXING_TYPES:
@@ -412,86 +397,106 @@ EncodedJSValue JSC_HOST_CALL arrayProtoFuncJoin(ExecState* exec)
         auto& butterfly = *thisObject->butterfly();
         if (length > butterfly.publicLength())
             break;
-        JSStringJoiner joiner(*exec, separator, length);
-        if (exec->hadException())
-            return JSValue::encode(jsUndefined());
+        JSStringJoiner joiner(state, separator, length);
+        if (state.hadException())
+            return jsUndefined();
         auto data = butterfly.contiguous().data();
         bool holesKnownToBeOK = false;
         for (unsigned i = 0; i < length; ++i) {
             if (JSValue value = data[i].get()) {
-                joiner.append(*exec, value);
-                if (exec->hadException())
-                    return JSValue::encode(jsUndefined());
+                joiner.append(state, value);
+                if (state.hadException())
+                    return jsUndefined();
             } else {
                 if (!holesKnownToBeOK) {
-                    if (thisObject->structure(vm)->holesMustForwardToPrototype(vm))
+                    if (holesMustForwardToPrototype(state, thisObject))
                         goto generalCase;
                     holesKnownToBeOK = true;
                 }
                 joiner.appendEmptyString();
             }
         }
-        return JSValue::encode(joiner.join(*exec));
+        return joiner.join(state);
     }
     case ALL_DOUBLE_INDEXING_TYPES: {
         auto& butterfly = *thisObject->butterfly();
         if (length > butterfly.publicLength())
             break;
-        JSStringJoiner joiner(*exec, separator, length);
-        if (exec->hadException())
-            return JSValue::encode(jsUndefined());
+        JSStringJoiner joiner(state, separator, length);
+        if (state.hadException())
+            return jsUndefined();
         auto data = butterfly.contiguousDouble().data();
         bool holesKnownToBeOK = false;
         for (unsigned i = 0; i < length; ++i) {
             double value = data[i];
             if (!isHole(value))
-                joiner.append(*exec, jsDoubleNumber(value));
+                joiner.append(state, jsDoubleNumber(value));
             else {
                 if (!holesKnownToBeOK) {
-                    if (thisObject->structure(vm)->holesMustForwardToPrototype(vm))
+                    if (thisObject->structure(state.vm())->holesMustForwardToPrototype(state.vm()))
                         goto generalCase;
                     holesKnownToBeOK = true;
                 }
                 joiner.appendEmptyString();
             }
         }
-        return JSValue::encode(joiner.join(*exec));
+        return joiner.join(state);
     }
     case ALL_ARRAY_STORAGE_INDEXING_TYPES: {
         auto& storage = *thisObject->butterfly()->arrayStorage();
         if (length > storage.vectorLength())
             break;
-        if (storage.hasHoles() && thisObject->structure(vm)->holesMustForwardToPrototype(vm))
+        if (storage.hasHoles() && thisObject->structure(state.vm())->holesMustForwardToPrototype(state.vm()))
             break;
-        JSStringJoiner joiner(*exec, separator, length);
-        if (exec->hadException())
-            return JSValue::encode(jsUndefined());
+        JSStringJoiner joiner(state, separator, length);
+        if (state.hadException())
+            return jsUndefined();
         auto data = storage.vector().data();
         for (unsigned i = 0; i < length; ++i) {
             if (JSValue value = data[i].get()) {
-                joiner.append(*exec, value);
-                if (exec->hadException())
-                    return JSValue::encode(jsUndefined());
+                joiner.append(state, value);
+                if (state.hadException())
+                    return jsUndefined();
             } else
                 joiner.appendEmptyString();
         }
-        return JSValue::encode(joiner.join(*exec));
+        return joiner.join(state);
     }
     }
 
 generalCase:
-    JSStringJoiner joiner(*exec, separator, length);
-    if (exec->hadException())
-        return JSValue::encode(jsUndefined());
+    JSStringJoiner joiner(state, separator, length);
+    if (state.hadException())
+        return jsUndefined();
     for (unsigned i = 0; i < length; ++i) {
-        JSValue element = thisObject->getIndex(exec, i);
-        if (exec->hadException())
-            return JSValue::encode(jsUndefined());
-        joiner.append(*exec, element);
-        if (exec->hadException())
-            return JSValue::encode(jsUndefined());
+        JSValue element = thisObject->getIndex(&state, i);
+        if (state.hadException())
+            return jsUndefined();
+        joiner.append(state, element);
+        if (state.hadException())
+            return jsUndefined();
+    }
+    return joiner.join(state);
+}
+
+EncodedJSValue JSC_HOST_CALL arrayProtoFuncJoin(ExecState* exec)
+{
+    JSObject* thisObject = exec->thisValue().toThis(exec, StrictMode).toObject(exec);
+
+    StringRecursionChecker checker(exec, thisObject);
+    if (JSValue earlyReturnValue = checker.earlyReturnValue())
+        return JSValue::encode(earlyReturnValue);
+
+    JSValue separatorValue = exec->argument(0);
+    if (separatorValue.isUndefined()) {
+        const LChar comma = ',';
+        return JSValue::encode(join(*exec, thisObject, { &comma, 1 }));
     }
-    return JSValue::encode(joiner.join(*exec));
+
+    JSString* separator = separatorValue.toString(exec);
+    if (exec->hadException())
+        return JSValue::encode(jsUndefined());
+    return JSValue::encode(join(*exec, thisObject, separator->view(exec)));
 }
 
 EncodedJSValue JSC_HOST_CALL arrayProtoFuncConcat(ExecState* exec)
@@ -619,8 +624,6 @@ EncodedJSValue JSC_HOST_CALL arrayProtoFuncReverse(ExecState* exec)
     if (exec->hadException())
         return JSValue::encode(jsUndefined());
 
-    auto& vm = exec->vm();
-
     switch (thisObject->indexingType()) {
     case ALL_CONTIGUOUS_INDEXING_TYPES:
     case ALL_INT32_INDEXING_TYPES: {
@@ -628,7 +631,7 @@ EncodedJSValue JSC_HOST_CALL arrayProtoFuncReverse(ExecState* exec)
         if (length > butterfly.publicLength())
             break;
         auto data = butterfly.contiguous().data();
-        if (containsHole(data, length) && thisObject->structure(vm)->holesMustForwardToPrototype(vm))
+        if (containsHole(data, length) && holesMustForwardToPrototype(*exec, thisObject))
             break;
         std::reverse(data, data + length);
         return JSValue::encode(thisObject);
@@ -638,7 +641,7 @@ EncodedJSValue JSC_HOST_CALL arrayProtoFuncReverse(ExecState* exec)
         if (length > butterfly.publicLength())
             break;
         auto data = butterfly.contiguousDouble().data();
-        if (containsHole(data, length) && thisObject->structure(vm)->holesMustForwardToPrototype(vm))
+        if (containsHole(data, length) && holesMustForwardToPrototype(*exec, thisObject))
             break;
         std::reverse(data, data + length);
         return JSValue::encode(thisObject);
@@ -647,7 +650,7 @@ EncodedJSValue JSC_HOST_CALL arrayProtoFuncReverse(ExecState* exec)
         auto& storage = *thisObject->butterfly()->arrayStorage();
         if (length > storage.vectorLength())
             break;
-        if (storage.hasHoles() && thisObject->structure(vm)->holesMustForwardToPrototype(vm))
+        if (storage.hasHoles() && holesMustForwardToPrototype(*exec, thisObject))
             break;
         auto data = storage.vector().data();
         std::reverse(data, data + length);
index c34bfcc..45a24ec 100644 (file)
@@ -64,7 +64,7 @@ static Bitmap<256> makeCharacterBitmap(const char (&characters)[charactersCount]
 
 static JSValue encode(ExecState* exec, const Bitmap<256>& doNotEscape)
 {
-    CString cstr = exec->argument(0).toString(exec)->view(exec).utf8(StrictConversion);
+    CString cstr = exec->argument(0).toString(exec)->view(exec).get().utf8(StrictConversion);
     if (!cstr.data())
         return exec->vm().throwException(exec, createURIError(exec, ASCIILiteral("String contained an illegal UTF-16 sequence.")));
 
index 034e7cb..068f52f 100644 (file)
@@ -47,6 +47,8 @@ JSString* jsSingleCharacterString(VM*, UChar);
 JSString* jsSingleCharacterString(ExecState*, UChar);
 JSString* jsSubstring(VM*, const String&, unsigned offset, unsigned length);
 JSString* jsSubstring(ExecState*, const String&, unsigned offset, unsigned length);
+JSString* jsSubstring8(VM*, const String&, unsigned offset, unsigned length);
+JSString* jsSubstring8(ExecState*, const String&, unsigned offset, unsigned length);
 
 // Non-trivial strings are two or more characters long.
 // These functions are faster than just calling jsString.
@@ -62,6 +64,9 @@ JSString* jsOwnedString(ExecState*, const String&);
 
 JSRopeString* jsStringBuilder(VM*);
 
+bool isJSString(JSValue);
+JSString* asString(JSValue);
+
 struct StringViewWithUnderlyingString {
     StringView view;
     String underlyingString;
@@ -148,8 +153,11 @@ public:
     Identifier toIdentifier(ExecState*) const;
     AtomicString toAtomicString(ExecState*) const;
     RefPtr<AtomicStringImpl> toExistingAtomicString(ExecState*) const;
-    StringView view(ExecState*) const;
+
+    class SafeView;
+    SafeView view(ExecState*) const;
     StringViewWithUnderlyingString viewWithUnderlyingString(ExecState&) const;
+
     const String& value(ExecState*) const;
     const String& tryGetValue() const;
     const StringImpl* tryGetValueImpl() const;
@@ -220,6 +228,7 @@ private:
     static JSValue toThis(JSCell*, ExecState*, ECMAMode);
 
     String& string() { ASSERT(!isRope()); return m_value; }
+    StringView unsafeView(ExecState&) const;
 
     friend JSValue jsString(ExecState*, JSString*, JSString*);
     friend JSString* jsSubstring(ExecState*, JSString*, unsigned offset, unsigned length);
@@ -389,7 +398,7 @@ private:
     void resolveRopeInternal16(UChar*) const;
     void resolveRopeInternal16NoSubstring(UChar*) const;
     void clearFibers() const;
-    StringView view(ExecState*) const;
+    StringView unsafeView(ExecState&) const;
     StringViewWithUnderlyingString viewWithUnderlyingString(ExecState&) const;
 
     WriteBarrierBase<JSString>& fiber(unsigned i) const
@@ -435,14 +444,30 @@ private:
     } u[s_maxInternalRopeLength];
 };
 
+class JSString::SafeView {
+public:
+    SafeView();
+    explicit SafeView(ExecState&, const JSString&);
+    operator StringView() const;
+    StringView get() const;
+
+private:
+    ExecState* m_state { nullptr };
+
+    // The following pointer is marked "volatile" to make the compiler leave it on the stack
+    // or in a register as long as this object is alive, even after the last use of the pointer.
+    // That's needed to prevent garbage collecting the string and possibly deleting the block
+    // with the characters in it, and then using the StringView after that.
+    const JSString* volatile m_string { nullptr };
+};
+
+JS_EXPORT_PRIVATE JSString* jsStringWithCacheSlowCase(VM&, StringImpl&);
 
 inline const StringImpl* JSString::tryGetValueImpl() const
 {
     return m_value.impl();
 }
 
-JSString* asString(JSValue);
-
 inline JSString* asString(JSValue value)
 {
     ASSERT(value.asCell()->isString());
@@ -511,7 +536,7 @@ inline const String& JSString::tryGetValue() const
 inline JSString* JSString::getIndex(ExecState* exec, unsigned i)
 {
     ASSERT(canGetIndex(i));
-    return jsSingleCharacterString(exec, view(exec)[i]);
+    return jsSingleCharacterString(exec, unsafeView(*exec)[i]);
 }
 
 inline JSString* jsString(VM* vm, const String& s)
@@ -597,8 +622,6 @@ inline JSString* jsNontrivialString(ExecState* exec, const String& s) { return j
 inline JSString* jsNontrivialString(ExecState* exec, String&& s) { return jsNontrivialString(&exec->vm(), WTF::move(s)); }
 inline JSString* jsOwnedString(ExecState* exec, const String& s) { return jsOwnedString(&exec->vm(), s); }
 
-JS_EXPORT_PRIVATE JSString* jsStringWithCacheSlowCase(VM&, StringImpl&);
-
 ALWAYS_INLINE JSString* jsStringWithCache(ExecState* exec, const String& s)
 {
     VM& vm = exec->vm();
@@ -651,44 +674,20 @@ ALWAYS_INLINE bool JSString::getStringPropertySlot(ExecState* exec, unsigned pro
     return false;
 }
 
-inline bool isJSString(JSValue v) { return v.isCell() && v.asCell()->type() == StringType; }
-
-// --- JSValue inlines ----------------------------
-
-inline bool JSValue::toBoolean(ExecState* exec) const
+inline bool isJSString(JSValue v)
 {
-    if (isInt32())
-        return asInt32();
-    if (isDouble())
-        return asDouble() > 0.0 || asDouble() < 0.0; // false for NaN
-    if (isCell())
-        return asCell()->toBoolean(exec);
-    return isTrue(); // false, null, and undefined all convert to false.
+    return v.isCell() && v.asCell()->type() == StringType;
 }
 
-inline JSString* JSValue::toString(ExecState* exec) const
-{
-    if (isString())
-        return jsCast<JSString*>(asCell());
-    return toStringSlowCase(exec);
-}
-
-inline String JSValue::toWTFString(ExecState* exec) const
-{
-    if (isString())
-        return static_cast<JSString*>(asCell())->value(exec);
-    return toWTFStringSlowCase(exec);
-}
-
-ALWAYS_INLINE StringView JSRopeString::view(ExecState* exec) const
+ALWAYS_INLINE StringView JSRopeString::unsafeView(ExecState& state) const
 {
     if (isSubstring()) {
         if (is8Bit())
             return StringView(substringBase()->m_value.characters8() + substringOffset(), m_length);
         return StringView(substringBase()->m_value.characters16() + substringOffset(), m_length);
     }
-    resolveRope(exec);
-    return StringView(m_value);
+    resolveRope(&state);
+    return m_value;
 }
 
 ALWAYS_INLINE StringViewWithUnderlyingString JSRopeString::viewWithUnderlyingString(ExecState& state) const
@@ -703,11 +702,11 @@ ALWAYS_INLINE StringViewWithUnderlyingString JSRopeString::viewWithUnderlyingStr
     return { m_value, m_value };
 }
 
-ALWAYS_INLINE StringView JSString::view(ExecState* exec) const
+ALWAYS_INLINE StringView JSString::unsafeView(ExecState& state) const
 {
     if (isRope())
-        return static_cast<const JSRopeString*>(this)->view(exec);
-    return StringView(m_value);
+        return static_cast<const JSRopeString*>(this)->unsafeView(state);
+    return m_value;
 }
 
 ALWAYS_INLINE StringViewWithUnderlyingString JSString::viewWithUnderlyingString(ExecState& state) const
@@ -722,6 +721,58 @@ inline bool JSString::isSubstring() const
     return isRope() && static_cast<const JSRopeString*>(this)->isSubstring();
 }
 
+inline JSString::SafeView::SafeView()
+{
+}
+
+inline JSString::SafeView::SafeView(ExecState& state, const JSString& string)
+    : m_state(&state)
+    , m_string(&string)
+{
+}
+
+inline JSString::SafeView::operator StringView() const
+{
+    return m_string->unsafeView(*m_state);
+}
+
+inline StringView JSString::SafeView::get() const
+{
+    return *this;
+}
+
+ALWAYS_INLINE JSString::SafeView JSString::view(ExecState* exec) const
+{
+    return SafeView(*exec, *this);
+}
+
+// --- JSValue inlines ----------------------------
+
+inline bool JSValue::toBoolean(ExecState* exec) const
+{
+    if (isInt32())
+        return asInt32();
+    if (isDouble())
+        return asDouble() > 0.0 || asDouble() < 0.0; // false for NaN
+    if (isCell())
+        return asCell()->toBoolean(exec);
+    return isTrue(); // false, null, and undefined all convert to false.
+}
+
+inline JSString* JSValue::toString(ExecState* exec) const
+{
+    if (isString())
+        return jsCast<JSString*>(asCell());
+    return toStringSlowCase(exec);
+}
+
+inline String JSValue::toWTFString(ExecState* exec) const
+{
+    if (isString())
+        return static_cast<JSString*>(asCell())->value(exec);
+    return toWTFStringSlowCase(exec);
+}
+
 } // namespace JSC
 
 #endif // JSString_h
index 9e2faa2..2edbf42 100644 (file)
@@ -896,7 +896,7 @@ EncodedJSValue JSC_HOST_CALL stringProtoFuncIndexOf(ExecState* exec)
     if (thisJSString->length() < otherJSString->length() + pos)
         return JSValue::encode(jsNumber(-1));
 
-    size_t result = thisJSString->view(exec).find(otherJSString->view(exec), pos);
+    size_t result = thisJSString->view(exec).get().find(otherJSString->view(exec), pos);
     if (result == notFound)
         return JSValue::encode(jsNumber(-1));
     return JSValue::encode(jsNumber(result));