Bladeren bron

fixing issue where if a throw occurred in a promise without any attached rejection handlers, we wouldn't notify the debugger that an unhandled exception occurred

Mike Kaufman 7 jaren geleden
bovenliggende
commit
543e0e1f3d

+ 2 - 0
bin/ch/DbgController.js

@@ -383,6 +383,8 @@ var controllerObj = (function () {
                                 if (bpName == "none") {
                                     exceptionAttributes = 0; // JsDiagBreakOnExceptionAttributeNone
                                 } else if (bpName == "uncaught") {
+                                    exceptionAttributes = 0x1; // JsDiagBreakOnExceptionAttributeUncaught
+                                } else if (bpName == "firstchance") {
                                     exceptionAttributes = 0x2; // JsDiagBreakOnExceptionAttributeFirstChance
                                 } else if (bpName == "all") {
                                     exceptionAttributes = 0x1 | 0x2; // JsDiagBreakOnExceptionAttributeUncaught | JsDiagBreakOnExceptionAttributeFirstChance

+ 10 - 1
lib/Runtime/Language/JavascriptExceptionOperators.cpp

@@ -40,13 +40,22 @@ namespace Js
         }
     }
 
-    JavascriptExceptionOperators::AutoCatchHandlerExists::AutoCatchHandlerExists(ScriptContext* scriptContext)
+    JavascriptExceptionOperators::AutoCatchHandlerExists::AutoCatchHandlerExists(ScriptContext* scriptContext, bool isPromiseHandled)
     {
         Assert(scriptContext);
         m_threadContext = scriptContext->GetThreadContext();
         Assert(m_threadContext);
         m_previousCatchHandlerExists = m_threadContext->HasCatchHandler();
         m_threadContext->SetHasCatchHandler(TRUE);
+
+        if (!isPromiseHandled)
+        {
+            // If this is created from a promise-specific code path, and we don't have a rejection
+            // handler on the promise, then we want SetCatchHandler to be false so we report any
+            // unhandled exceptions to any detached debuggers.
+            m_threadContext->SetHasCatchHandler(false);
+        }
+
         m_previousCatchHandlerToUserCodeStatus = m_threadContext->IsUserCode();
         if (scriptContext->IsScriptContextInDebugMode())
         {

+ 1 - 1
lib/Runtime/Language/JavascriptExceptionOperators.h

@@ -39,7 +39,7 @@ namespace Js
             void FetchNonUserCodeStatus(ScriptContext *scriptContext);
 
           public:
-            AutoCatchHandlerExists(ScriptContext* scriptContext);
+            AutoCatchHandlerExists(ScriptContext* scriptContext, bool isPromiseHandled = true);
             ~AutoCatchHandlerExists();
         };
 

+ 99 - 2
lib/Runtime/Library/JavascriptPromise.cpp

@@ -932,7 +932,22 @@ namespace Js
         JavascriptExceptionObject* exception = nullptr;
 
         {
-            Js::JavascriptExceptionOperators::AutoCatchHandlerExists autoCatchHandlerExists(scriptContext);
+
+            bool isPromiseRejectionHandled = true;
+            if (scriptContext->IsScriptContextInDebugMode())
+            {
+                // only necessary to determine if false if debugger is attached.  This way we'll 
+                // correctly break on exceptions raised in promises that result in uhandled rejection
+                // notifications
+                Var promiseVar = promiseCapability->GetPromise();
+                if (JavascriptPromise::Is(promiseVar))
+                {
+                    JavascriptPromise* promise = JavascriptPromise::FromVar(promiseVar);
+                    isPromiseRejectionHandled = !promise->WillRejectionBeUnhandled();
+                }
+            }
+
+            Js::JavascriptExceptionOperators::AutoCatchHandlerExists autoCatchHandlerExists(scriptContext, isPromiseRejectionHandled);
             try
             {
                 BEGIN_SAFE_REENTRANT_CALL(scriptContext->GetThreadContext())
@@ -960,6 +975,80 @@ namespace Js
         return TryCallResolveOrRejectHandler(promiseCapability->GetResolve(), handlerResult, scriptContext);
     }
 
+
+    /**
+     * Determine if at the current point in time, the given promise has a path of reactions that result
+     * in an unhandled rejection.  This doesn't account for potential of a rejection handler added later
+     * in time.
+     */
+    bool JavascriptPromise::WillRejectionBeUnhandled()
+    {
+        bool willBeUnhandled = !this->GetIsHandled();
+        if (!willBeUnhandled)
+        {
+            // if this promise is handled, then we need to do a depth-first search over this promise's reject 
+            // reactions. If we find a reaction that 
+            //    - associated promise is "unhandled" (ie, it's never been "then'd")
+            //    - AND its rejection handler is our default "thrower function" 
+            // then this promise results in an unhandled rejection path.
+
+            JsUtil::Stack<JavascriptPromise*, HeapAllocator> stack(&HeapAllocator::Instance);
+            SimpleHashTable<JavascriptPromise*, int, HeapAllocator> visited(&HeapAllocator::Instance);
+            stack.Push(this);
+            visited.Add(this, 1);
+
+            while (!willBeUnhandled && !stack.Empty())
+            {
+                JavascriptPromise * curr = stack.Pop();
+                {
+                    JavascriptPromiseReactionList* reactions = curr->GetRejectReactions();
+                    for (int i = 0; i < reactions->Count(); i++)
+                    {
+                        JavascriptPromiseReaction* reaction = reactions->Item(i);
+                        Var promiseVar = reaction->GetCapabilities()->GetPromise();
+
+                        if (JavascriptPromise::Is(promiseVar))
+                        {
+                            JavascriptPromise* p = JavascriptPromise::FromVar(promiseVar);
+                            if (!p->GetIsHandled())
+                            {
+                                RecyclableObject* handler = reaction->GetHandler();
+                                if (JavascriptFunction::Is(handler))
+                                {
+                                    JavascriptFunction* func = JavascriptFunction::FromVar(handler);
+                                    FunctionInfo* functionInfo = func->GetFunctionInfo();
+
+#ifdef DEBUG
+                                    if (!func->IsCrossSiteObject())
+                                    {
+                                        // assert that Thrower function's FunctionInfo hasn't changed
+                                        AssertMsg(func->GetScriptContext()->GetLibrary()->GetThrowerFunction()->GetFunctionInfo() == &JavascriptPromise::EntryInfo::Thrower, "unexpected FunctionInfo for thrower function!");
+                                    }
+#endif
+
+                                    // If the function info is the default thrower function's function info, then assume that this is unhandled
+                                    // this will work across script contexts
+                                    if (functionInfo == &JavascriptPromise::EntryInfo::Thrower)
+                                    {
+                                        willBeUnhandled = true;
+                                        break;
+                                    }
+                                }
+                            }
+                            AssertMsg(visited.HasEntry(p) == false, "Unexpected cycle in promise reaction tree!");
+                            if (!visited.HasEntry(p))
+                            {
+                                stack.Push(p);
+                                visited.Add(p, 1);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return willBeUnhandled;
+    }
+
     Var JavascriptPromise::TryCallResolveOrRejectHandler(Var handler, Var value, ScriptContext* scriptContext)
     {
         Var undefinedVar = scriptContext->GetLibrary()->GetUndefined();
@@ -1092,7 +1181,15 @@ namespace Js
         JavascriptExceptionObject* exception = nullptr;
 
         {
-            Js::JavascriptExceptionOperators::AutoCatchHandlerExists autoCatchHandlerExists(scriptContext);
+            bool isPromiseRejectionHandled = true;
+            if (scriptContext->IsScriptContextInDebugMode())
+            {
+                // only necessary to determine if false if debugger is attached.  This way we'll 
+                // correctly break on exceptions raised in promises that result in uhandled rejections
+                isPromiseRejectionHandled = !promise->WillRejectionBeUnhandled();
+            }
+
+            Js::JavascriptExceptionOperators::AutoCatchHandlerExists autoCatchHandlerExists(scriptContext, isPromiseRejectionHandled);
             try
             {
                 BEGIN_SAFE_REENTRANT_CALL(scriptContext->GetThreadContext())

+ 1 - 0
lib/Runtime/Library/JavascriptPromise.h

@@ -563,6 +563,7 @@ namespace Js
 
     private :
         static void AsyncSpawnStep(JavascriptPromiseAsyncSpawnStepArgumentExecutorFunction* nextFunction, JavascriptGenerator* gen, Var resolve, Var reject);
+        bool WillRejectionBeUnhandled();
 
 #if ENABLE_TTD
     public:

+ 3 - 0
test/Debugger/JsDiagBreakOnUncaughtException.baseline

@@ -0,0 +1,3 @@
+Error: throw exception from throwFunction
+	at throwFunction() (JsDiagBreakOnUncaughtException.js:18:4)
+	at Global code (JsDiagBreakOnUncaughtException.js:20:1)

+ 20 - 0
test/Debugger/JsDiagBreakOnUncaughtException.js

@@ -0,0 +1,20 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+
+/**exception(uncaught):stack();**/
+
+function noThrowFunction() {
+  try {
+    throw new Error("throw exception from noThrowFunction");
+  } catch (err) {
+  }
+}
+noThrowFunction();
+
+// calling throwFunction() will terminate program, so this has to come last
+function throwFunction() { 
+   throw new Error("throw exception from throwFunction");
+}
+throwFunction();

+ 18 - 0
test/Debugger/JsDiagBreakOnUncaughtException.js.dbg.baseline

@@ -0,0 +1,18 @@
+[
+  {
+    "callStack": [
+      {
+        "line": 17,
+        "column": 3,
+        "sourceText": "throw new Error(\"throw exception from throwFunction\")",
+        "function": "throwFunction"
+      },
+      {
+        "line": 19,
+        "column": 0,
+        "sourceText": "throwFunction()",
+        "function": "Global code"
+      }
+    ]
+  }
+]

+ 0 - 0
test/Debugger/JsDiagExceptionsInAsyncFunctions_BreakOnUncaughtExceptions.baseline


+ 35 - 0
test/Debugger/JsDiagExceptionsInAsyncFunctions_BreakOnUncaughtExceptions.js

@@ -0,0 +1,35 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+
+/**exception(uncaught):stack();**/
+
+async function f1() {
+    await null;
+    throw new Error('error in f1');
+}
+f1();
+
+async function f2() {
+
+    async function f2a() {
+        throw "err";
+    }
+
+    async function f2b() {
+        try {
+            var p = f2a();
+        } catch (e) {
+            console.log("caught " + e);
+        }
+    }
+    
+    async function f2c() {
+        var p = f2a();
+    }
+
+    f2b();
+    f2c();
+}
+f2();

+ 40 - 0
test/Debugger/JsDiagExceptionsInAsyncFunctions_BreakOnUncaughtExceptions.js.dbg.baseline

@@ -0,0 +1,40 @@
+[
+  {
+    "callStack": [
+      {
+        "line": 16,
+        "column": 8,
+        "sourceText": "throw \"err\"",
+        "function": "f2a"
+      },
+      {
+        "line": 28,
+        "column": 8,
+        "sourceText": "var p = f2a()",
+        "function": "f2c"
+      },
+      {
+        "line": 32,
+        "column": 4,
+        "sourceText": "f2c()",
+        "function": "f2"
+      },
+      {
+        "line": 34,
+        "column": 0,
+        "sourceText": "f2()",
+        "function": "Global code"
+      }
+    ]
+  },
+  {
+    "callStack": [
+      {
+        "line": 9,
+        "column": 4,
+        "sourceText": "throw new Error('error in f1')",
+        "function": "f1"
+      }
+    ]
+  }
+]

+ 0 - 0
test/Debugger/JsDiagExceptionsInPromises_BreakOnFirstChanceExceptions.baseline


+ 20 - 0
test/Debugger/JsDiagExceptionsInPromises_BreakOnFirstChanceExceptions.crosscontext.js

@@ -0,0 +1,20 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+
+var externalContextPromise = (function () {
+    let resolvePromise;
+    let promise = new Promise((resolve, reject) => {
+        resolvePromise = resolve;
+
+    });
+    promise = promise.then(()=> {
+        throw new Error("error from externalContextPromise1");
+    })
+
+    return {
+        promise,
+        resolvePromise
+    };
+})();

+ 97 - 0
test/Debugger/JsDiagExceptionsInPromises_BreakOnFirstChanceExceptions.js

@@ -0,0 +1,97 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+
+/**exception(firstchance):stack();**/
+
+
+function unhandledPromiseRejection1() {
+    Promise.resolve(true)
+        .then(() => {
+            throw new Error('error for unhandledPromiseRejection1')
+        });
+}
+unhandledPromiseRejection1();
+
+function unhandledPromiseRejection2() {
+    Promise.resolve(true)
+        .then(() => {
+            throw new Error('error for unhandledPromiseRejection2');
+        })
+        .then(() => {
+            // no catch
+        });
+}
+unhandledPromiseRejection2();
+
+function unhandledPromiseRejection3() {
+    let p = Promise.resolve(true)
+        .then(() => {
+            throw new Error('error for unhandledPromiseRejection3');
+        })
+        .then(() => 0);
+    p.then(() => 0).then(() => 1); // this path is not caught
+    p.then(() => 2, (err) => { }); // this path is caught
+
+}
+unhandledPromiseRejection3();
+
+function unhandledPromiseRejection4() {
+    let p = Promise.resolve(true)
+        .then(() => {
+            throw new Error('error for unhandledPromiseRejection3');
+        })
+        .catch((err) => {
+            throw err;
+        });
+}
+unhandledPromiseRejection4();
+
+function handledPromiseRejection5() {
+    Promise.resolve(true)
+        .then(() => {
+            throw new Error('error for handledPromiseRejection5')
+        }).catch(() => { });
+}
+handledPromiseRejection5();
+
+function handledPromiseRejection6() {
+    Promise.resolve(true)
+        .then(() => {
+            throw new Error('error for handledPromiseRejection6');
+        })
+        .then(() => { }, () => { });
+}
+handledPromiseRejection6()
+
+function handledPromiseRejection7() {
+    let p = Promise.resolve(true)
+        .then(() => {
+            throw new Error('error for handledPromiseRejection7');
+        })
+        .then(() => 0);
+    p.then(() => 0).then(() => 1).catch(() => { }); // this path is  caught
+    p.then(() => 2, (err) => { }); // this path is caught
+
+}
+handledPromiseRejection7();
+
+function handledPromiseRejection8() {
+    var p = Promise.resolve(0).then(() => {
+        p.catch(() => { }); // lazily added catch on the currently executing promise
+        throw new Error('error for handledPromiseRejection8');
+    });
+}
+handledPromiseRejection8();
+
+function noRejection9() {
+    let p = Promise.resolve(true)
+        .then(() => {
+            try {
+                throw new Error('error for noRejection9');
+            } catch (err) {
+            }
+        });
+}
+noRejection9();

+ 102 - 0
test/Debugger/JsDiagExceptionsInPromises_BreakOnFirstChanceExceptions.js.dbg.baseline

@@ -0,0 +1,102 @@
+[
+  {
+    "callStack": [
+      {
+        "line": 11,
+        "column": 12,
+        "sourceText": "throw new Error('error for unhandledPromiseRejection1')",
+        "function": "Anonymous function"
+      }
+    ]
+  },
+  {
+    "callStack": [
+      {
+        "line": 19,
+        "column": 12,
+        "sourceText": "throw new Error('error for unhandledPromiseRejection2')",
+        "function": "Anonymous function"
+      }
+    ]
+  },
+  {
+    "callStack": [
+      {
+        "line": 30,
+        "column": 12,
+        "sourceText": "throw new Error('error for unhandledPromiseRejection3')",
+        "function": "Anonymous function"
+      }
+    ]
+  },
+  {
+    "callStack": [
+      {
+        "line": 42,
+        "column": 12,
+        "sourceText": "throw new Error('error for unhandledPromiseRejection3')",
+        "function": "Anonymous function"
+      }
+    ]
+  },
+  {
+    "callStack": [
+      {
+        "line": 53,
+        "column": 12,
+        "sourceText": "throw new Error('error for handledPromiseRejection5')",
+        "function": "Anonymous function"
+      }
+    ]
+  },
+  {
+    "callStack": [
+      {
+        "line": 61,
+        "column": 12,
+        "sourceText": "throw new Error('error for handledPromiseRejection6')",
+        "function": "Anonymous function"
+      }
+    ]
+  },
+  {
+    "callStack": [
+      {
+        "line": 70,
+        "column": 12,
+        "sourceText": "throw new Error('error for handledPromiseRejection7')",
+        "function": "Anonymous function"
+      }
+    ]
+  },
+  {
+    "callStack": [
+      {
+        "line": 82,
+        "column": 8,
+        "sourceText": "throw new Error('error for handledPromiseRejection8')",
+        "function": "Anonymous function"
+      }
+    ]
+  },
+  {
+    "callStack": [
+      {
+        "line": 91,
+        "column": 16,
+        "sourceText": "throw new Error('error for noRejection9')",
+        "function": "Anonymous function"
+      }
+    ]
+  },
+  {
+    "callStack": [
+      {
+        "line": 45,
+        "column": 12,
+        "sourceText": "throw err",
+        "function": "Anonymous function"
+      }
+    ]
+  }
+]

+ 0 - 0
test/Debugger/JsDiagExceptionsInPromises_BreakOnUncaughtExceptions.baseline


+ 159 - 0
test/Debugger/JsDiagExceptionsInPromises_BreakOnUncaughtExceptions.js

@@ -0,0 +1,159 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+
+/**exception(uncaught):stack();**/
+
+function unhandledPromiseRejection1() {
+    Promise.resolve(true)
+        .then(() => {
+            throw new Error('error for unhandledPromiseRejection1')
+        });
+}
+unhandledPromiseRejection1();
+
+function unhandledPromiseRejection2() {
+    Promise.resolve(true)
+        .then(() => {
+            throw new Error('error for unhandledPromiseRejection2');
+        })
+        .then(() => {
+            // no catch
+        });
+}
+unhandledPromiseRejection2();
+
+function unhandledPromiseRejection3() {
+    let p = Promise.resolve(true)
+        .then(() => {
+            throw new Error('error for unhandledPromiseRejection3');
+        })
+        .then(() => 0);
+    p.then(() => 0).then(() => 1); // this path is not caught
+    p.then(() => 2, (err) => { }); // this path is caught
+
+}
+unhandledPromiseRejection3();
+
+function unhandledPromiseRejection4() {
+    let p = Promise.resolve(true)
+        .then(() => {
+            throw new Error('error for unhandledPromiseRejection3');
+        })
+        .catch((err) => {
+            throw err;
+        });
+}
+unhandledPromiseRejection4();
+
+function handledPromiseRejection5() {
+    Promise.resolve(true)
+        .then(() => {
+            throw new Error('error for handledPromiseRejection5')
+        }).catch(() => { });
+}
+handledPromiseRejection5();
+
+function handledPromiseRejection6() {
+    Promise.resolve(true)
+        .then(() => {
+            throw new Error('error for handledPromiseRejection6');
+        })
+        .then(() => { }, () => { });
+}
+handledPromiseRejection6()
+
+function handledPromiseRejection7() {
+    let p = Promise.resolve(true)
+        .then(() => {
+            throw new Error('error for handledPromiseRejection7');
+        })
+        .then(() => 0);
+    p.then(() => 0).then(() => 1).catch(() => { }); // this path is  caught
+    p.then(() => 2, (err) => { }); // this path is caught
+
+}
+handledPromiseRejection7();
+
+//
+//  validate that when we have a handler from one script context 
+// and a promise from another script context, we'll break appropriately
+//
+function unhandledPromiseRejectionCrossContext() { 
+    var external = WScript.LoadScriptFile("JsDiagExceptionsInPromises_BreakOnFirstChanceExceptions.crosscontext.js", "samethread");
+    let p = Promise.prototype.then.call(
+        external.externalContextPromise.promise, () => { 
+    });
+    external.externalContextPromise.resolvePromise();
+}
+unhandledPromiseRejectionCrossContext();
+
+//
+//  validate that when we have a handler from one script context 
+// and a promise from another script context, we'll not break if a rejection handler is available
+//
+function handledPromiseRejectionCrossContext() { 
+    var external = WScript.LoadScriptFile("JsDiagExceptionsInPromises_BreakOnFirstChanceExceptions.crosscontext.js", "samethread");
+    let p = Promise.prototype.then.call(
+        external.externalContextPromise.promise, () => {}, () => {}); 
+    external.externalContextPromise.resolvePromise();
+}
+handledPromiseRejectionCrossContext();
+
+// 
+// This one below is an edge case where we will break on uncaught exceptions
+// even though the rejection is handled.  What's happening here is before
+// we execute the function onResolve, there is no handler attached
+// so we, then as part of executing onResolve, the catch handler is  
+// attached.  We'll break in the function below.
+//
+// I don't think it is worth fixing this since it seems like a relatively
+// rare case. 
+//
+function handledPromiseRejection8_bugbug() {
+    var p = Promise.resolve(0).then(function onResolve() {
+        p.catch(() => { }); // lazily added catch on the currently executing promise
+        throw new Error('error for handledPromiseRejection8_bugbug');
+    });
+}
+handledPromiseRejection8_bugbug();
+
+// 
+// In the case below, we're resolving a promise with a promise. 
+// Ultimately, the rejections is handled, but according to 
+// ES standard, the resolve of promiseA with promiseB gets
+// pushed on the task queue, therefore, at the time the exception
+// is raised, promiseB hasn't been "then'd".
+//
+// There are two ways to address this:
+//   1.  Change the ResolveThenable task to run immediately vs runing in task queue (this would be in violation of the spec) 
+//   2.  Keep a list of the pending resolve-thenable tasks.
+//
+function handledPromiseRejection9_bugbug() {
+    function f1() {
+        let promiseA =  new Promise((resolveA, rejectA) => {
+            let promiseB = Promise.resolve(true).then(() => {
+                throw new Error('error for handledPromiseRejection9_bugbug');
+            });
+            resolveA(promiseB);
+        });
+        return promiseA;
+    }
+        
+    f1().catch((e) => {
+    });
+}
+handledPromiseRejection9_bugbug();
+
+
+function noRejection10() {
+    let p = Promise.resolve(true)
+        .then(() => {
+            try {
+                throw new Error('error for noRejection10');
+            } catch (err) {
+            }
+        });
+}
+noRejection10();

+ 72 - 0
test/Debugger/JsDiagExceptionsInPromises_BreakOnUncaughtExceptions.js.dbg.baseline

@@ -0,0 +1,72 @@
+[
+  {
+    "callStack": [
+      {
+        "line": 10,
+        "column": 12,
+        "sourceText": "throw new Error('error for unhandledPromiseRejection1')",
+        "function": "Anonymous function"
+      }
+    ]
+  },
+  {
+    "callStack": [
+      {
+        "line": 18,
+        "column": 12,
+        "sourceText": "throw new Error('error for unhandledPromiseRejection2')",
+        "function": "Anonymous function"
+      }
+    ]
+  },
+  {
+    "callStack": [
+      {
+        "line": 29,
+        "column": 12,
+        "sourceText": "throw new Error('error for unhandledPromiseRejection3')",
+        "function": "Anonymous function"
+      }
+    ]
+  },
+  {
+    "callStack": [
+      {
+        "line": 12,
+        "column": 8,
+        "sourceText": "throw new Error(\"error from externalContextPromise1\")",
+        "function": "Anonymous function"
+      }
+    ]
+  },
+  {
+    "callStack": [
+      {
+        "line": 116,
+        "column": 8,
+        "sourceText": "throw new Error('error for handledPromiseRejection8_bugbug')",
+        "function": "onResolve"
+      }
+    ]
+  },
+  {
+    "callStack": [
+      {
+        "line": 136,
+        "column": 16,
+        "sourceText": "throw new Error('error for handledPromiseRejection9_bugbug')",
+        "function": "Anonymous function"
+      }
+    ]
+  },
+  {
+    "callStack": [
+      {
+        "line": 44,
+        "column": 12,
+        "sourceText": "throw err",
+        "function": "Anonymous function"
+      }
+    ]
+  }
+]

+ 28 - 0
test/Debugger/rlexe.xml

@@ -18,6 +18,34 @@
       <files>JsDiagBreakpoints_ArrayBuffer.js</files>
     </default>
   </test>
+  <test>
+    <default>
+      <compile-flags>-debuglaunch -dbgbaseline:JsDiagBreakOnUncaughtException.js.dbg.baseline</compile-flags>
+      <baseline>JsDiagBreakOnUncaughtException.baseline</baseline>
+      <files>JsDiagBreakOnUncaughtException.js</files>        
+    </default>
+  </test>
+  <test>
+    <default>
+      <compile-flags>-debuglaunch -dbgbaseline:JsDiagExceptionsInPromises_BreakOnUncaughtExceptions.js.dbg.baseline</compile-flags>
+      <baseline>JsDiagExceptionsInPromises_BreakOnUncaughtExceptions.baseline</baseline>
+      <files>JsDiagExceptionsInPromises_BreakOnUncaughtExceptions.js</files>        
+    </default>
+  </test>
+  <test>
+    <default>
+      <compile-flags>-debuglaunch -dbgbaseline:JsDiagExceptionsInPromises_BreakOnFirstChanceExceptions.js.dbg.baseline</compile-flags>
+      <baseline>JsDiagExceptionsInPromises_BreakOnFirstChanceExceptions.baseline</baseline>
+      <files>JsDiagExceptionsInPromises_BreakOnFirstChanceExceptions.js</files>        
+    </default>
+  </test>
+  <test>
+    <default>
+      <compile-flags>-debuglaunch -dbgbaseline:JsDiagExceptionsInAsyncFunctions_BreakOnUncaughtExceptions.js.dbg.baseline</compile-flags>
+      <baseline>JsDiagExceptionsInAsyncFunctions_BreakOnUncaughtExceptions.baseline</baseline>
+      <files>JsDiagExceptionsInAsyncFunctions_BreakOnUncaughtExceptions.js</files>        
+    </default>
+  </test>
   <test>
     <default>
       <compile-flags>-debuglaunch -dbgbaseline:JsDiagEvaluate.js.dbg.baseline</compile-flags>