Add more debugging features to $vm.
authormark.lam@apple.com <mark.lam@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 23 Jun 2018 15:48:25 +0000 (15:48 +0000)
committermark.lam@apple.com <mark.lam@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 23 Jun 2018 15:48:25 +0000 (15:48 +0000)
https://bugs.webkit.org/show_bug.cgi?id=186947

Reviewed by Keith Miller.

Adding the following features:

    // We now have println in addition to print.
    // println automatically adds a '\n' at the end.
    $vm.println("Hello");

    // We can now capture some info about a stack frame.
    var currentFrame = $vm.callFrame(); // Same as $vm.callFrame(0);
    var callerCallerFrame = $vm.callFrame(2);

    // We can inspect the following values associated with the frame:
    if (currentFrame.valid) {
        $vm.println("name is ", currentFrame.name));

        // Note: For a WASM frame, all of these will be undefined.
        $vm.println("callee is ", $vm.value(currentFrame.callee));
        $vm.println("codeBlock is ", currentFrame.codeBlock);
        $vm.println("unlinkedCodeBlock is ", currentFrame.unlinkedCodeBlock);
        $vm.println("executable is ", currentFrame.executable);
    }

    // Note that callee is a JSObject.  I printed its $vm.value() because I wanted
    // to dataLog its JSValue instead of its toString() result.

    // Note that $vm.println() (and $vm.print()) can now print internal JSCells
    // (and Symbols) as JSValue dumps. It won't just fail on trying to do a
    // toString on a non-object.

    // Does what it says about enabling/disabling debugger mode.
    $vm.enableDebuggerModeWhenIdle();
    $vm.disableDebuggerModeWhenIdle();

* tools/JSDollarVM.cpp:
(WTF::JSDollarVMCallFrame::JSDollarVMCallFrame):
(WTF::JSDollarVMCallFrame::createStructure):
(WTF::JSDollarVMCallFrame::create):
(WTF::JSDollarVMCallFrame::finishCreation):
(WTF::JSDollarVMCallFrame::addProperty):
(JSC::functionCallFrame):
(JSC::functionCodeBlockForFrame):
(JSC::codeBlockFromArg):
(JSC::doPrintln):
(JSC::functionPrint):
(JSC::functionPrintln):
(JSC::changeDebuggerModeWhenIdle):
(JSC::functionEnableDebuggerModeWhenIdle):
(JSC::functionDisableDebuggerModeWhenIdle):
(JSC::JSDollarVM::finishCreation):

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

Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/tools/JSDollarVM.cpp

index 49cef2e..ee293a7 100644 (file)
@@ -1,3 +1,59 @@
+2018-06-23  Mark Lam  <mark.lam@apple.com>
+
+        Add more debugging features to $vm.
+        https://bugs.webkit.org/show_bug.cgi?id=186947
+
+        Reviewed by Keith Miller.
+
+        Adding the following features:
+
+            // We now have println in addition to print.
+            // println automatically adds a '\n' at the end.
+            $vm.println("Hello");
+
+            // We can now capture some info about a stack frame.
+            var currentFrame = $vm.callFrame(); // Same as $vm.callFrame(0);
+            var callerCallerFrame = $vm.callFrame(2);
+
+            // We can inspect the following values associated with the frame:
+            if (currentFrame.valid) {
+                $vm.println("name is ", currentFrame.name));
+
+                // Note: For a WASM frame, all of these will be undefined.
+                $vm.println("callee is ", $vm.value(currentFrame.callee));
+                $vm.println("codeBlock is ", currentFrame.codeBlock);
+                $vm.println("unlinkedCodeBlock is ", currentFrame.unlinkedCodeBlock);
+                $vm.println("executable is ", currentFrame.executable);
+            }
+
+            // Note that callee is a JSObject.  I printed its $vm.value() because I wanted
+            // to dataLog its JSValue instead of its toString() result.
+
+            // Note that $vm.println() (and $vm.print()) can now print internal JSCells
+            // (and Symbols) as JSValue dumps. It won't just fail on trying to do a
+            // toString on a non-object.
+
+            // Does what it says about enabling/disabling debugger mode.
+            $vm.enableDebuggerModeWhenIdle();
+            $vm.disableDebuggerModeWhenIdle();
+
+        * tools/JSDollarVM.cpp:
+        (WTF::JSDollarVMCallFrame::JSDollarVMCallFrame):
+        (WTF::JSDollarVMCallFrame::createStructure):
+        (WTF::JSDollarVMCallFrame::create):
+        (WTF::JSDollarVMCallFrame::finishCreation):
+        (WTF::JSDollarVMCallFrame::addProperty):
+        (JSC::functionCallFrame):
+        (JSC::functionCodeBlockForFrame):
+        (JSC::codeBlockFromArg):
+        (JSC::doPrintln):
+        (JSC::functionPrint):
+        (JSC::functionPrintln):
+        (JSC::changeDebuggerModeWhenIdle):
+        (JSC::functionEnableDebuggerModeWhenIdle):
+        (JSC::functionDisableDebuggerModeWhenIdle):
+        (JSC::JSDollarVM::finishCreation):
+
 2018-06-22  Keith Miller  <keith_miller@apple.com>
 
         We need to have a getDirectConcurrently for use in the compilers
 2018-06-22  Keith Miller  <keith_miller@apple.com>
 
         We need to have a getDirectConcurrently for use in the compilers
index 0b9612b..3134c2d 100644 (file)
@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (C) 2015-2017 Apple Inc. All rights reserved.
+ * Copyright (C) 2015-2018 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -56,6 +56,74 @@ using namespace WTF;
 
 namespace {
 
 
 namespace {
 
+class JSDollarVMCallFrame : public JSDestructibleObject {
+    using Base = JSDestructibleObject;
+public:
+    JSDollarVMCallFrame(VM& vm, Structure* structure)
+        : Base(vm, structure)
+    { }
+
+    static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
+    {
+        return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
+    }
+
+    static JSDollarVMCallFrame* create(ExecState* exec, unsigned requestedFrameIndex)
+    {
+        VM& vm = exec->vm();
+        JSGlobalObject* globalObject = exec->lexicalGlobalObject();
+        Structure* structure = createStructure(vm, globalObject, jsNull());
+        JSDollarVMCallFrame* frame = new (NotNull, allocateCell<JSDollarVMCallFrame>(vm.heap, sizeof(JSDollarVMCallFrame))) JSDollarVMCallFrame(vm, structure);
+        frame->finishCreation(vm, exec, requestedFrameIndex);
+        return frame;
+    }
+
+    void finishCreation(VM& vm, CallFrame* frame, unsigned requestedFrameIndex)
+    {
+        Base::finishCreation(vm);
+
+        auto addProperty = [&] (VM& vm, const char* name, JSValue value) {
+            JSDollarVMCallFrame::addProperty(vm, name, value);
+        };
+
+        unsigned frameIndex = 0;
+        bool isValid = false;
+        frame->iterate([&] (StackVisitor& visitor) {
+
+            if (frameIndex++ != requestedFrameIndex)
+                return StackVisitor::Continue;
+
+            addProperty(vm, "name", jsString(&vm, visitor->functionName()));
+
+            if (visitor->callee().isCell())
+                addProperty(vm, "callee", visitor->callee().asCell());
+
+            CodeBlock* codeBlock = visitor->codeBlock();
+            if (!codeBlock) {
+                addProperty(vm, "codeBlock", codeBlock);
+                addProperty(vm, "unlinkedCodeBlock", codeBlock->unlinkedCodeBlock());
+                addProperty(vm, "executable", codeBlock->ownerExecutable());
+            }
+            isValid = true;
+
+            return StackVisitor::Done;
+        });
+
+        addProperty(vm, "valid", jsBoolean(isValid));
+    }
+
+    DECLARE_INFO;
+
+private:
+    void addProperty(VM& vm, const char* name, JSValue value)
+    {
+        Identifier identifier = Identifier::fromString(&vm, name);
+        putDirect(vm, identifier, value);
+    }
+};
+
+const ClassInfo JSDollarVMCallFrame::s_info = { "CallFrame", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSDollarVMCallFrame) };
+
 class ElementHandleOwner;
 class Root;
 
 class ElementHandleOwner;
 class Root;
 
@@ -1245,26 +1313,47 @@ static EncodedJSValue JSC_HOST_CALL functionEdenGC(ExecState* exec)
     return JSValue::encode(jsUndefined());
 }
 
     return JSValue::encode(jsUndefined());
 }
 
+// Gets a JSDollarVMCallFrame for a specified frame index.
+// Usage: var callFrame = $vm.callFrame(0) // frame 0 is the top frame.
+// Usage: var callFrame = $vm.callFrame() // implies frame 0 i.e. current frame.
+static EncodedJSValue JSC_HOST_CALL functionCallFrame(ExecState* exec)
+{
+    unsigned frameNumber = 1;
+    if (exec->argumentCount() >= 1) {
+        JSValue value = exec->uncheckedArgument(0);
+        if (!value.isUInt32())
+            return JSValue::encode(jsUndefined());
+
+        // We need to inc the frame number because the caller would consider
+        // its own frame as frame 0. Hence, we need discount the frame for this
+        // function.
+        frameNumber = value.asUInt32() + 1;
+    }
+
+    return JSValue::encode(JSDollarVMCallFrame::create(exec, frameNumber));
+}
+
 // Gets a token for the CodeBlock for a specified frame index.
 // Usage: codeBlockToken = $vm.codeBlockForFrame(0) // frame 0 is the top frame.
 // Gets a token for the CodeBlock for a specified frame index.
 // Usage: codeBlockToken = $vm.codeBlockForFrame(0) // frame 0 is the top frame.
+// Usage: codeBlockToken = $vm.codeBlockForFrame() // implies frame 0 i.e. current frame.
 static EncodedJSValue JSC_HOST_CALL functionCodeBlockForFrame(ExecState* exec)
 {
 static EncodedJSValue JSC_HOST_CALL functionCodeBlockForFrame(ExecState* exec)
 {
-    if (exec->argumentCount() < 1)
-        return JSValue::encode(jsUndefined());
+    unsigned frameNumber = 1;
+    if (exec->argumentCount() >= 1) {
+        JSValue value = exec->uncheckedArgument(0);
+        if (!value.isUInt32())
+            return JSValue::encode(jsUndefined());
 
 
-    JSValue value = exec->uncheckedArgument(0);
-    if (!value.isUInt32())
-        return JSValue::encode(jsUndefined());
+        // We need to inc the frame number because the caller would consider
+        // its own frame as frame 0. Hence, we need discount the frame for this
+        // function.
+        frameNumber = value.asUInt32() + 1;
+    }
 
 
-    // We need to inc the frame number because the caller would consider
-    // its own frame as frame 0. Hence, we need discount the frame for this
-    // function.
-    unsigned frameNumber = value.asUInt32() + 1;
     CodeBlock* codeBlock = VMInspector::codeBlockForFrame(exec, frameNumber);
     CodeBlock* codeBlock = VMInspector::codeBlockForFrame(exec, frameNumber);
-    // Though CodeBlock is a JSCell, it is not safe to return it directly back to JS code
-    // as it is an internal type that the JS code cannot handle. Hence, we first encode the
-    // CodeBlock* as a double token (which is safe for JS code to handle) before returning it.
-    return JSValue::encode(JSValue(bitwise_cast<double>(static_cast<uint64_t>(reinterpret_cast<uintptr_t>(codeBlock)))));
+    if (codeBlock)
+        return JSValue::encode(codeBlock);
+    return JSValue::encode(jsUndefined());
 }
 
 static CodeBlock* codeBlockFromArg(ExecState* exec)
 }
 
 static CodeBlock* codeBlockFromArg(ExecState* exec)
@@ -1282,12 +1371,8 @@ static CodeBlock* codeBlockFromArg(ExecState* exec)
                 candidateCodeBlock = nullptr;
             else
                 candidateCodeBlock = func->jsExecutable()->eitherCodeBlock();
                 candidateCodeBlock = nullptr;
             else
                 candidateCodeBlock = func->jsExecutable()->eitherCodeBlock();
-        }
-    } else if (value.isDouble()) {
-        // If the value is a double, it may be an encoded CodeBlock* that came from
-        // $vm.codeBlockForFrame(). We'll treat it as a candidate codeBlock and check if it's
-        // valid below before using.
-        candidateCodeBlock = reinterpret_cast<CodeBlock*>(bitwise_cast<uint64_t>(value.asDouble()));
+        } else
+            candidateCodeBlock = reinterpret_cast<CodeBlock*>(value.asCell());
     }
 
     if (candidateCodeBlock && VMInspector::isValidCodeBlock(exec, candidateCodeBlock))
     }
 
     if (candidateCodeBlock && VMInspector::isValidCodeBlock(exec, candidateCodeBlock))
@@ -1333,19 +1418,43 @@ static EncodedJSValue JSC_HOST_CALL functionPrintBytecodeFor(ExecState* exec)
     return JSValue::encode(jsUndefined());
 }
 
     return JSValue::encode(jsUndefined());
 }
 
-// Prints a series of comma separate strings without inserting a newline.
-// Usage: $vm.print(str1, str2, str3)
-static EncodedJSValue JSC_HOST_CALL functionPrint(ExecState* exec)
+static EncodedJSValue doPrintln(ExecState* exec, bool addLineFeed)
 {
     auto scope = DECLARE_THROW_SCOPE(exec->vm());
     for (unsigned i = 0; i < exec->argumentCount(); ++i) {
 {
     auto scope = DECLARE_THROW_SCOPE(exec->vm());
     for (unsigned i = 0; i < exec->argumentCount(); ++i) {
+        JSValue arg = exec->uncheckedArgument(i);
+        if (arg.isCell()
+            && !arg.isObject()
+            && !arg.isString()
+            && !arg.isBigInt()) {
+            dataLog(arg);
+            continue;
+        }
         String argStr = exec->uncheckedArgument(i).toWTFString(exec);
         RETURN_IF_EXCEPTION(scope, encodedJSValue());
         dataLog(argStr);
     }
         String argStr = exec->uncheckedArgument(i).toWTFString(exec);
         RETURN_IF_EXCEPTION(scope, encodedJSValue());
         dataLog(argStr);
     }
+    if (addLineFeed)
+        dataLog("\n");
     return JSValue::encode(jsUndefined());
 }
 
     return JSValue::encode(jsUndefined());
 }
 
+// Prints a series of comma separate strings without appending a newline.
+// Usage: $vm.print(str1, str2, str3)
+static EncodedJSValue JSC_HOST_CALL functionPrint(ExecState* exec)
+{
+    const bool addLineFeed = false;
+    return doPrintln(exec, addLineFeed);
+}
+
+// Prints a series of comma separate strings and appends a newline.
+// Usage: $vm.println(str1, str2, str3)
+static EncodedJSValue JSC_HOST_CALL functionPrintln(ExecState* exec)
+{
+    const bool addLineFeed = true;
+    return doPrintln(exec, addLineFeed);
+}
+
 // Prints the current CallFrame.
 // Usage: $vm.printCallFrame()
 static EncodedJSValue JSC_HOST_CALL functionPrintCallFrame(ExecState* exec)
 // Prints the current CallFrame.
 // Usage: $vm.printCallFrame()
 static EncodedJSValue JSC_HOST_CALL functionPrintCallFrame(ExecState* exec)
@@ -1717,6 +1826,30 @@ static EncodedJSValue JSC_HOST_CALL functionEnableExceptionFuzz(ExecState*)
     return JSValue::encode(jsUndefined());
 }
 
     return JSValue::encode(jsUndefined());
 }
 
+static EncodedJSValue changeDebuggerModeWhenIdle(ExecState* exec, DebuggerMode mode)
+{
+    bool newDebuggerMode = (mode == DebuggerOn);
+    if (Options::forceDebuggerBytecodeGeneration() == newDebuggerMode)
+        return JSValue::encode(jsUndefined());
+
+    VM* vm = &exec->vm();
+    vm->whenIdle([=] () {
+        Options::forceDebuggerBytecodeGeneration() = newDebuggerMode;
+        vm->deleteAllCode(PreventCollectionAndDeleteAllCode);
+    });
+    return JSValue::encode(jsUndefined());
+}
+
+static EncodedJSValue JSC_HOST_CALL functionEnableDebuggerModeWhenIdle(ExecState* exec)
+{
+    return changeDebuggerModeWhenIdle(exec, DebuggerOn);
+}
+
+static EncodedJSValue JSC_HOST_CALL functionDisableDebuggerModeWhenIdle(ExecState* exec)
+{
+    return changeDebuggerModeWhenIdle(exec, DebuggerOff);
+}
+
 static EncodedJSValue JSC_HOST_CALL functionGlobalObjectCount(ExecState* exec)
 {
     return JSValue::encode(jsNumber(exec->vm().heap.globalObjectCount()));
 static EncodedJSValue JSC_HOST_CALL functionGlobalObjectCount(ExecState* exec)
 {
     return JSValue::encode(jsNumber(exec->vm().heap.globalObjectCount()));
@@ -1832,12 +1965,14 @@ void JSDollarVM::finishCreation(VM& vm)
     addFunction(vm, "gc", functionGC, 0);
     addFunction(vm, "edenGC", functionEdenGC, 0);
 
     addFunction(vm, "gc", functionGC, 0);
     addFunction(vm, "edenGC", functionEdenGC, 0);
 
+    addFunction(vm, "callFrame", functionCallFrame, 1);
     addFunction(vm, "codeBlockFor", functionCodeBlockFor, 1);
     addFunction(vm, "codeBlockForFrame", functionCodeBlockForFrame, 1);
     addFunction(vm, "printSourceFor", functionPrintSourceFor, 1);
     addFunction(vm, "printBytecodeFor", functionPrintBytecodeFor, 1);
 
     addFunction(vm, "print", functionPrint, 1);
     addFunction(vm, "codeBlockFor", functionCodeBlockFor, 1);
     addFunction(vm, "codeBlockForFrame", functionCodeBlockForFrame, 1);
     addFunction(vm, "printSourceFor", functionPrintSourceFor, 1);
     addFunction(vm, "printBytecodeFor", functionPrintBytecodeFor, 1);
 
     addFunction(vm, "print", functionPrint, 1);
+    addFunction(vm, "println", functionPrintln, 1);
     addFunction(vm, "printCallFrame", functionPrintCallFrame, 0);
     addFunction(vm, "printStack", functionPrintStack, 0);
 
     addFunction(vm, "printCallFrame", functionPrintCallFrame, 0);
     addFunction(vm, "printStack", functionPrintStack, 0);
 
@@ -1882,6 +2017,9 @@ void JSDollarVM::finishCreation(VM& vm)
 
     addFunction(vm, "enableExceptionFuzz", functionEnableExceptionFuzz, 0);
 
 
     addFunction(vm, "enableExceptionFuzz", functionEnableExceptionFuzz, 0);
 
+    addFunction(vm, "enableDebuggerModeWhenIdle", functionEnableDebuggerModeWhenIdle, 0);
+    addFunction(vm, "disableDebuggerModeWhenIdle", functionDisableDebuggerModeWhenIdle, 0);
+
     addFunction(vm, "globalObjectCount", functionGlobalObjectCount, 0);
     addFunction(vm, "globalObjectForObject", functionGlobalObjectForObject, 1);
 
     addFunction(vm, "globalObjectCount", functionGlobalObjectCount, 0);
     addFunction(vm, "globalObjectForObject", functionGlobalObjectForObject, 1);