Bug 1279545 - Part 1: Add parseRegExp testing function. r=till
authorTooru Fujisawa <arai_a@mac.com>
Tue, 25 Oct 2016 00:54:22 +0900
changeset 319225 b212d4a114fcab72161b2616957b4dc15308c13f
parent 319224 b443899d03b49b3e0af52e9274b287c990798315
child 319226 46e205ee383b94bc0b4e9c6a5ac0971c007c15da
push id20740
push usercbook@mozilla.com
push dateTue, 25 Oct 2016 08:51:26 +0000
treeherderfx-team@78b863e9fcd9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstill
bugs1279545
milestone52.0a1
Bug 1279545 - Part 1: Add parseRegExp testing function. r=till
js/src/builtin/TestingFunctions.cpp
js/src/irregexp/RegExpEngine.h
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -25,16 +25,22 @@
 #include "asmjs/WasmBinaryToExperimentalText.h"
 #include "asmjs/WasmBinaryToText.h"
 #include "asmjs/WasmJS.h"
 #include "asmjs/WasmModule.h"
 #include "asmjs/WasmSignalHandlers.h"
 #include "asmjs/WasmTextToBinary.h"
 #include "builtin/Promise.h"
 #include "builtin/SelfHostingDefines.h"
+#ifdef DEBUG
+#include "frontend/TokenStream.h"
+#include "irregexp/RegExpAST.h"
+#include "irregexp/RegExpEngine.h"
+#include "irregexp/RegExpParser.h"
+#endif
 #include "jit/InlinableNatives.h"
 #include "jit/JitFrameIterator.h"
 #include "js/Debug.h"
 #include "js/HashTable.h"
 #include "js/StructuredClone.h"
 #include "js/UbiNode.h"
 #include "js/UbiNodeBreadthFirst.h"
 #include "js/UbiNodeShortestPaths.h"
@@ -3582,16 +3588,336 @@ GetModuleEnvironmentValue(JSContext* cx,
     RootedString name(cx, args[1].toString());
     RootedId id(cx);
     if (!JS_StringToId(cx, name, &id))
         return false;
 
     return GetProperty(cx, env, env, id, args.rval());
 }
 
+#ifdef DEBUG
+static const char*
+AssertionTypeToString(irregexp::RegExpAssertion::AssertionType type)
+{
+    switch (type) {
+      case irregexp::RegExpAssertion::START_OF_LINE:
+        return "START_OF_LINE";
+      case irregexp::RegExpAssertion::START_OF_INPUT:
+        return "START_OF_INPUT";
+      case irregexp::RegExpAssertion::END_OF_LINE:
+        return "END_OF_LINE";
+      case irregexp::RegExpAssertion::END_OF_INPUT:
+        return "END_OF_INPUT";
+      case irregexp::RegExpAssertion::BOUNDARY:
+        return "BOUNDARY";
+      case irregexp::RegExpAssertion::NON_BOUNDARY:
+        return "NON_BOUNDARY";
+      case irregexp::RegExpAssertion::NOT_AFTER_LEAD_SURROGATE:
+        return "NOT_AFTER_LEAD_SURROGATE";
+      case irregexp::RegExpAssertion::NOT_IN_SURROGATE_PAIR:
+        return "NOT_IN_SURROGATE_PAIR";
+    }
+    MOZ_CRASH("unexpected AssertionType");
+}
+
+static JSObject*
+ConvertRegExpTreeToObject(JSContext* cx, irregexp::RegExpTree* tree)
+{
+    RootedObject obj(cx, JS_NewPlainObject(cx));
+    if (!obj)
+        return nullptr;
+
+    auto IntProp = [](JSContext* cx, HandleObject obj,
+                      const char* name, int32_t value) {
+        RootedValue val(cx, Int32Value(value));
+        return JS_SetProperty(cx, obj, name, val);
+    };
+
+    auto BooleanProp = [](JSContext* cx, HandleObject obj,
+                          const char* name, bool value) {
+        RootedValue val(cx, BooleanValue(value));
+        return JS_SetProperty(cx, obj, name, val);
+    };
+
+    auto StringProp = [](JSContext* cx, HandleObject obj,
+                         const char* name, const char* value) {
+        RootedString valueStr(cx, JS_NewStringCopyZ(cx, value));
+        if (!valueStr)
+            return false;
+
+        RootedValue val(cx, StringValue(valueStr));
+        return JS_SetProperty(cx, obj, name, val);
+    };
+
+    auto ObjectProp = [](JSContext* cx, HandleObject obj,
+                         const char* name, HandleObject value) {
+        RootedValue val(cx, ObjectValue(*value));
+        return JS_SetProperty(cx, obj, name, val);
+    };
+
+    auto CharVectorProp = [](JSContext* cx, HandleObject obj,
+                             const char* name, const irregexp::CharacterVector& data) {
+        RootedString valueStr(cx, JS_NewUCStringCopyN(cx, data.begin(), data.length()));
+        if (!valueStr)
+            return false;
+
+        RootedValue val(cx, StringValue(valueStr));
+        return JS_SetProperty(cx, obj, name, val);
+    };
+
+    auto TreeProp = [&ObjectProp](JSContext* cx, HandleObject obj,
+                                  const char* name, irregexp::RegExpTree* tree) {
+        RootedObject treeObj(cx, ConvertRegExpTreeToObject(cx, tree));
+        if (!treeObj)
+            return false;
+        return ObjectProp(cx, obj, name, treeObj);
+    };
+
+    auto TreeVectorProp = [&ObjectProp](JSContext* cx, HandleObject obj,
+                                        const char* name,
+                                        const irregexp::RegExpTreeVector& nodes) {
+        size_t len = nodes.length();
+        RootedObject array(cx, JS_NewArrayObject(cx, len));
+        if (!array)
+            return false;
+
+        for (size_t i = 0; i < len; i++) {
+            RootedObject child(cx, ConvertRegExpTreeToObject(cx, nodes[i]));
+            if (!child)
+                return false;
+
+            RootedValue childVal(cx, ObjectValue(*child));
+            if (!JS_SetElement(cx, array, i, childVal))
+                return false;
+        }
+        return ObjectProp(cx, obj, name, array);
+    };
+
+    auto CharRangesProp = [&ObjectProp](JSContext* cx, HandleObject obj,
+                                        const char* name,
+                                        const irregexp::CharacterRangeVector& ranges) {
+        size_t len = ranges.length();
+        RootedObject array(cx, JS_NewArrayObject(cx, len));
+        if (!array)
+            return false;
+
+        for (size_t i = 0; i < len; i++) {
+            const irregexp::CharacterRange& range = ranges[i];
+            RootedObject rangeObj(cx, JS_NewPlainObject(cx));
+            if (!rangeObj)
+                return false;
+
+            auto CharProp = [](JSContext* cx, HandleObject obj,
+                               const char* name, char16_t c) {
+                RootedString valueStr(cx, JS_NewUCStringCopyN(cx, &c, 1));
+                if (!valueStr)
+                    return false;
+                RootedValue val(cx, StringValue(valueStr));
+                return JS_SetProperty(cx, obj, name, val);
+            };
+
+            if (!CharProp(cx, rangeObj, "from", range.from()))
+                return false;
+            if (!CharProp(cx, rangeObj, "to", range.to()))
+                return false;
+
+            RootedValue rangeVal(cx, ObjectValue(*rangeObj));
+            if (!JS_SetElement(cx, array, i, rangeVal))
+                return false;
+        }
+        return ObjectProp(cx, obj, name, array);
+    };
+
+    auto ElemProp = [&ObjectProp](JSContext* cx, HandleObject obj,
+                                  const char* name, const irregexp::TextElementVector& elements) {
+        size_t len = elements.length();
+        RootedObject array(cx, JS_NewArrayObject(cx, len));
+        if (!array)
+            return false;
+
+        for (size_t i = 0; i < len; i++) {
+            const irregexp::TextElement& element = elements[i];
+            RootedObject elemTree(cx, ConvertRegExpTreeToObject(cx, element.tree()));
+            if (!elemTree)
+                return false;
+
+            RootedValue elemTreeVal(cx, ObjectValue(*elemTree));
+            if (!JS_SetElement(cx, array, i, elemTreeVal))
+                return false;
+        }
+        return ObjectProp(cx, obj, name, array);
+    };
+
+    if (tree->IsDisjunction()) {
+        if (!StringProp(cx, obj, "type", "Disjunction"))
+            return nullptr;
+        irregexp::RegExpDisjunction* t = tree->AsDisjunction();
+        if (!TreeVectorProp(cx, obj, "alternatives", t->alternatives()))
+            return nullptr;
+        return obj;
+    }
+    if (tree->IsAlternative()) {
+        if (!StringProp(cx, obj, "type", "Alternative"))
+            return nullptr;
+        irregexp::RegExpAlternative* t = tree->AsAlternative();
+        if (!TreeVectorProp(cx, obj, "nodes", t->nodes()))
+            return nullptr;
+        return obj;
+    }
+    if (tree->IsAssertion()) {
+        if (!StringProp(cx, obj, "type", "Assertion"))
+            return nullptr;
+        irregexp::RegExpAssertion* t = tree->AsAssertion();
+        if (!StringProp(cx, obj, "assertion_type", AssertionTypeToString(t->assertion_type())))
+            return nullptr;
+        return obj;
+    }
+    if (tree->IsCharacterClass()) {
+        if (!StringProp(cx, obj, "type", "CharacterClass"))
+            return nullptr;
+        irregexp::RegExpCharacterClass* t = tree->AsCharacterClass();
+        if (!BooleanProp(cx, obj, "is_negated", t->is_negated()))
+            return nullptr;
+        LifoAlloc* alloc = &cx->tempLifoAlloc();
+        if (!CharRangesProp(cx, obj, "ranges", t->ranges(alloc)))
+            return nullptr;
+        return obj;
+    }
+    if (tree->IsAtom()) {
+        if (!StringProp(cx, obj, "type", "Atom"))
+            return nullptr;
+        irregexp::RegExpAtom* t = tree->AsAtom();
+        if (!CharVectorProp(cx, obj, "data", t->data()))
+            return nullptr;
+        return obj;
+    }
+    if (tree->IsText()) {
+        if (!StringProp(cx, obj, "type", "Text"))
+            return nullptr;
+        irregexp::RegExpText* t = tree->AsText();
+        if (!ElemProp(cx, obj, "elements", t->elements()))
+            return nullptr;
+        return obj;
+    }
+    if (tree->IsQuantifier()) {
+        if (!StringProp(cx, obj, "type", "Quantifier"))
+            return nullptr;
+        irregexp::RegExpQuantifier* t = tree->AsQuantifier();
+        if (!IntProp(cx, obj, "min", t->min()))
+            return nullptr;
+        if (!IntProp(cx, obj, "max", t->max()))
+            return nullptr;
+        if (!StringProp(cx, obj, "quantifier_type",
+                        t->is_possessive() ? "POSSESSIVE"
+                        : t->is_non_greedy() ? "NON_GREEDY"
+                        : "GREEDY"))
+            return nullptr;
+        if (!TreeProp(cx, obj, "body", t->body()))
+            return nullptr;
+        return obj;
+    }
+    if (tree->IsCapture()) {
+        if (!StringProp(cx, obj, "type", "Capture"))
+            return nullptr;
+        irregexp::RegExpCapture* t = tree->AsCapture();
+        if (!IntProp(cx, obj, "index", t->index()))
+            return nullptr;
+        if (!TreeProp(cx, obj, "body", t->body()))
+            return nullptr;
+        return obj;
+    }
+    if (tree->IsLookahead()) {
+        if (!StringProp(cx, obj, "type", "Lookahead"))
+            return nullptr;
+        irregexp::RegExpLookahead* t = tree->AsLookahead();
+        if (!BooleanProp(cx, obj, "is_positive", t->is_positive()))
+            return nullptr;
+        if (!TreeProp(cx, obj, "body", t->body()))
+            return nullptr;
+        return obj;
+    }
+    if (tree->IsBackReference()) {
+        if (!StringProp(cx, obj, "type", "BackReference"))
+            return nullptr;
+        irregexp::RegExpBackReference* t = tree->AsBackReference();
+        if (!IntProp(cx, obj, "index", t->index()))
+            return nullptr;
+        return obj;
+    }
+    if (tree->IsEmpty()) {
+        if (!StringProp(cx, obj, "type", "Empty"))
+            return nullptr;
+        return obj;
+    }
+
+    MOZ_CRASH("unexpected RegExpTree type");
+}
+
+static bool
+ParseRegExp(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    RootedObject callee(cx, &args.callee());
+
+    if (args.length() == 0) {
+        ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+        return false;
+    }
+
+    if (!args[0].isString()) {
+        ReportUsageErrorASCII(cx, callee, "First argument must be a String");
+        return false;
+    }
+
+    RegExpFlag flags = RegExpFlag(0);
+    if (!args.get(1).isUndefined()) {
+        if (!args.get(1).isString()) {
+            ReportUsageErrorASCII(cx, callee, "Second argument, if present, must be a String");
+            return false;
+        }
+        RootedString flagStr(cx, args[1].toString());
+        if (!ParseRegExpFlags(cx, flagStr, &flags))
+            return false;
+    }
+
+    bool match_only = false;
+    if (!args.get(2).isUndefined()) {
+        if (!args.get(2).isBoolean()) {
+            ReportUsageErrorASCII(cx, callee, "Third argument, if present, must be a Boolean");
+            return false;
+        }
+        match_only = args[2].toBoolean();
+    }
+
+    RootedAtom pattern(cx, AtomizeString(cx, args[0].toString()));
+    if (!pattern)
+        return false;
+
+    CompileOptions options(cx);
+    frontend::TokenStream dummyTokenStream(cx, options, nullptr, 0, nullptr);
+
+    irregexp::RegExpCompileData data;
+    if (!irregexp::ParsePattern(dummyTokenStream, cx->tempLifoAlloc(), pattern,
+                                flags & MultilineFlag, match_only,
+                                flags & UnicodeFlag, flags & IgnoreCaseFlag,
+                                flags & GlobalFlag, flags & StickyFlag,
+                                &data))
+    {
+        return false;
+    }
+
+    RootedObject obj(cx, ConvertRegExpTreeToObject(cx, data.tree));
+    if (!obj)
+        return false;
+
+    args.rval().setObject(*obj);
+    return true;
+}
+#endif
+
 static const JSFunctionSpecWithHelp TestingFunctions[] = {
     JS_FN_HELP("gc", ::GC, 0, 0,
 "gc([obj] | 'zone' [, 'shrinking'])",
 "  Run the garbage collector. When obj is given, GC only its zone.\n"
 "  If 'zone' is given, GC any zones that were scheduled for\n"
 "  GC via schedulegc.\n"
 "  If 'shrinking' is passed as the optional second argument, perform a\n"
 "  shrinking GC rather than a normal GC."),
@@ -4101,16 +4427,26 @@ gc::ZealModeHelpText),
 
     JS_FN_HELP("getModuleEnvironmentValue", GetModuleEnvironmentValue, 2, 0,
 "getModuleEnvironmentValue(module, name)",
 "  Get the value of a bound name in a module environment.\n"),
 
     JS_FS_HELP_END
 };
 
+static const JSFunctionSpecWithHelp FuzzingUnsafeTestingFunctions[] = {
+#ifdef DEBUG
+    JS_FN_HELP("parseRegExp", ParseRegExp, 3, 0,
+"parseRegExp(pattern[, flags[, match_only])",
+"  Parses a RegExp pattern and returns a tree, potentially throwing."),
+#endif
+
+    JS_FS_HELP_END
+};
+
 static const JSPropertySpec TestingProperties[] = {
     JS_PSG("timesAccessed", TimesAccessed, 0),
     JS_PS_END
 };
 
 bool
 js::DefineTestingFunctions(JSContext* cx, HandleObject obj, bool fuzzingSafe_,
                            bool disableOOMFunctions_)
@@ -4119,10 +4455,15 @@ js::DefineTestingFunctions(JSContext* cx
     if (EnvVarIsDefined("MOZ_FUZZING_SAFE"))
         fuzzingSafe = true;
 
     disableOOMFunctions = disableOOMFunctions_;
 
     if (!JS_DefineProperties(cx, obj, TestingProperties))
         return false;
 
+    if (!fuzzingSafe) {
+        if (!JS_DefineFunctionsWithHelp(cx, obj, FuzzingUnsafeTestingFunctions))
+            return false;
+    }
+
     return JS_DefineFunctionsWithHelp(cx, obj, TestingFunctions);
 }
--- a/js/src/irregexp/RegExpEngine.h
+++ b/js/src/irregexp/RegExpEngine.h
@@ -148,16 +148,17 @@ class InfallibleVector
     void clear() { vector_.clear(); }
     void popBack() { vector_.popBack(); }
     void reserve(size_t n) { MOZ_ALWAYS_TRUE(vector_.reserve(n)); }
 
     size_t length() const { return vector_.length(); }
     T popCopy() { return vector_.popCopy(); }
 
     T* begin() { return vector_.begin(); }
+    const T* begin() const { return vector_.begin(); }
 
     T& operator[](size_t index) { return vector_[index]; }
     const T& operator[](size_t index) const { return vector_[index]; }
 
     InfallibleVector& operator=(InfallibleVector&& rhs) { vector_ = Move(rhs.vector_); return *this; }
 };
 
 class CharacterRange;