summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/debug
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit-test/tests/debug')
-rw-r--r--js/src/jit-test/tests/debug/DebuggeeWouldRun-01.js7
-rw-r--r--js/src/jit-test/tests/debug/DebuggeeWouldRun-02.js7
-rw-r--r--js/src/jit-test/tests/debug/DebuggeeWouldRun-03.js9
-rw-r--r--js/src/jit-test/tests/debug/DebuggeeWouldRun-04.js9
-rw-r--r--js/src/jit-test/tests/debug/Debugger-add-Debugger-prototype.js6
-rw-r--r--js/src/jit-test/tests/debug/Debugger-adoptDebuggeeValue.js39
-rw-r--r--js/src/jit-test/tests/debug/Debugger-adoptFrame.js81
-rw-r--r--js/src/jit-test/tests/debug/Debugger-allowUnobservedAsmJS-01.js26
-rw-r--r--js/src/jit-test/tests/debug/Debugger-allowUnobservedAsmJS-02.js25
-rw-r--r--js/src/jit-test/tests/debug/Debugger-clearAllBreakpoints-01.js29
-rw-r--r--js/src/jit-test/tests/debug/Debugger-clearAllBreakpoints-02.js31
-rw-r--r--js/src/jit-test/tests/debug/Debugger-ctor-01.js21
-rw-r--r--js/src/jit-test/tests/debug/Debugger-ctor-02.js13
-rw-r--r--js/src/jit-test/tests/debug/Debugger-ctor-03.js19
-rw-r--r--js/src/jit-test/tests/debug/Debugger-ctor-04.js5
-rw-r--r--js/src/jit-test/tests/debug/Debugger-ctor-05.js8
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-01.js5
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-02.js10
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-03.js34
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-04.js26
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-05.js8
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-06.js27
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-08.js25
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-09.js21
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-10.js18
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-11.js22
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-12.js10
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-13.js9
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-14.js8
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-15.js7
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-16.js30
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-17.js26
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-18.js114
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-19.js49
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-20.js30
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-21.js12
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-22.js24
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-23.js107
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-24.js55
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-25.js48
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-26.js34
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-27.js19
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-28.js109
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-29.js6
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-30.js32
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-31.js30
-rw-r--r--js/src/jit-test/tests/debug/Debugger-debuggees-32.js13
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findAllGlobals-01.js24
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findAllGlobals-02.js26
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findObjects-01.js4
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findObjects-02.js18
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findObjects-03.js12
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findObjects-04.js16
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findObjects-05.js10
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findObjects-06.js14
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findObjects-07.js22
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findObjects-08.js12
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findObjects-09.js9
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findObjects-10.js5
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findObjects-11.js7
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-01.js4
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-02.js16
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-03.js16
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-04.js27
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-05.js18
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-06.js13
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-07.js33
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-08-script23
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-08.js81
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-09.js45
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-10.js13
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-11-script218
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-11.js36
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-12-script119
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-12-script219
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-12.js129
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-14.js30
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-14.script112
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-15.js9
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-16.js12
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-17.js15
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-18.js46
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-19.js5
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-20.js20
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-22.js8
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-23.js21
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-24.js35
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-25.js14
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-26.js18
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-27.js22
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-28.js26
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-29.js21
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-30.js21
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-31.js21
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-delazify.js485
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-optimized-out.js31
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-uncompleted-01.js54
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-uncompleted-02.js49
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findSourceURLs.js18
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findSources-01.js4
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findSources-02.js15
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findSources-03.js19
-rw-r--r--js/src/jit-test/tests/debug/Debugger-getNewestFrame-01.js20
-rw-r--r--js/src/jit-test/tests/debug/Debugger-getNewestFrame-02.js20
-rw-r--r--js/src/jit-test/tests/debug/Debugger-getNewestFrame-03.js9
-rw-r--r--js/src/jit-test/tests/debug/Debugger-getNewestFrame-generators-01.js49
-rw-r--r--js/src/jit-test/tests/debug/Debugger-isCompilableUnit.js58
-rw-r--r--js/src/jit-test/tests/debug/Debugger-multi-01.js31
-rw-r--r--js/src/jit-test/tests/debug/Debugger-multi-02.js32
-rw-r--r--js/src/jit-test/tests/debug/Debugger-multi-03.js21
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-01.js45
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-02.js28
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-03.js26
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-04.js16
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-05.js98
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-06.js31
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNativeCall-01.js64
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNativeCall-02.js61
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNativeCall-03.js24
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNativeCall-04.js26
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-01.js64
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-02.js23
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-03.js40
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-04.js14
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-05.js13
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-06.js20
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-07.js18
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-08.js26
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-09.js34
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-10.js27
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-11.js31
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-12.js25
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-13.js18
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-14.js17
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-15.js25
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewPromise-01.js13
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewPromise-02.js24
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewPromise-03.js41
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewPromise-04.js14
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewPromise-05.js24
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewPromise-06.js35
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewPromise-07.js13
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onPromiseSettled-01.js18
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onPromiseSettled-02.js24
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onPromiseSettled-03.js40
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onPromiseSettled-04.js14
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onPromiseSettled-05.js25
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onPromiseSettled-06.js35
-rw-r--r--js/src/jit-test/tests/debug/Debugger-setInstrumentation-01.js196
-rw-r--r--js/src/jit-test/tests/debug/Debugger-setInstrumentation-02.js130
-rw-r--r--js/src/jit-test/tests/debug/Debugger-setInstrumentation-03.js72
-rw-r--r--js/src/jit-test/tests/debug/Debugger-setInstrumentation-04.js34
-rw-r--r--js/src/jit-test/tests/debug/Debugger-setInstrumentation-05.js43
-rw-r--r--js/src/jit-test/tests/debug/Debugger-setInstrumentation-06.js105
-rw-r--r--js/src/jit-test/tests/debug/Debugger-setInstrumentation-bug1571169.js18
-rw-r--r--js/src/jit-test/tests/debug/Environment-01.js23
-rw-r--r--js/src/jit-test/tests/debug/Environment-02.js20
-rw-r--r--js/src/jit-test/tests/debug/Environment-03.js10
-rw-r--r--js/src/jit-test/tests/debug/Environment-Function-prototype.js7
-rw-r--r--js/src/jit-test/tests/debug/Environment-bug-1431461.js26
-rw-r--r--js/src/jit-test/tests/debug/Environment-calleeScript-01.js48
-rw-r--r--js/src/jit-test/tests/debug/Environment-calleeScript-02.js25
-rw-r--r--js/src/jit-test/tests/debug/Environment-calleeScript-03.js25
-rw-r--r--js/src/jit-test/tests/debug/Environment-find-01.js19
-rw-r--r--js/src/jit-test/tests/debug/Environment-find-02.js18
-rw-r--r--js/src/jit-test/tests/debug/Environment-find-03.js20
-rw-r--r--js/src/jit-test/tests/debug/Environment-find-04.js21
-rw-r--r--js/src/jit-test/tests/debug/Environment-find-05.js0
-rw-r--r--js/src/jit-test/tests/debug/Environment-find-06.js47
-rw-r--r--js/src/jit-test/tests/debug/Environment-find-07.js22
-rw-r--r--js/src/jit-test/tests/debug/Environment-gc-01.js19
-rw-r--r--js/src/jit-test/tests/debug/Environment-gc-02.js28
-rw-r--r--js/src/jit-test/tests/debug/Environment-gc-03.js21
-rw-r--r--js/src/jit-test/tests/debug/Environment-getVariable-01.js14
-rw-r--r--js/src/jit-test/tests/debug/Environment-getVariable-02.js18
-rw-r--r--js/src/jit-test/tests/debug/Environment-getVariable-03.js21
-rw-r--r--js/src/jit-test/tests/debug/Environment-getVariable-04.js12
-rw-r--r--js/src/jit-test/tests/debug/Environment-getVariable-05.js10
-rw-r--r--js/src/jit-test/tests/debug/Environment-getVariable-06.js12
-rw-r--r--js/src/jit-test/tests/debug/Environment-getVariable-07.js10
-rw-r--r--js/src/jit-test/tests/debug/Environment-getVariable-08.js10
-rw-r--r--js/src/jit-test/tests/debug/Environment-getVariable-09.js13
-rw-r--r--js/src/jit-test/tests/debug/Environment-getVariable-10.js27
-rw-r--r--js/src/jit-test/tests/debug/Environment-getVariable-11.js15
-rw-r--r--js/src/jit-test/tests/debug/Environment-getVariable-12.js61
-rw-r--r--js/src/jit-test/tests/debug/Environment-getVariable-13.js50
-rw-r--r--js/src/jit-test/tests/debug/Environment-getVariable-14.js18
-rw-r--r--js/src/jit-test/tests/debug/Environment-getVariable-15.js31
-rw-r--r--js/src/jit-test/tests/debug/Environment-getVariable-WouldRun.js17
-rw-r--r--js/src/jit-test/tests/debug/Environment-identity-01.js40
-rw-r--r--js/src/jit-test/tests/debug/Environment-identity-02.js29
-rw-r--r--js/src/jit-test/tests/debug/Environment-identity-03.js106
-rw-r--r--js/src/jit-test/tests/debug/Environment-identity-04.js19
-rw-r--r--js/src/jit-test/tests/debug/Environment-identity-05.js19
-rw-r--r--js/src/jit-test/tests/debug/Environment-inspectable-01.js80
-rw-r--r--js/src/jit-test/tests/debug/Environment-module-01.js26
-rw-r--r--js/src/jit-test/tests/debug/Environment-module-02.js30
-rw-r--r--js/src/jit-test/tests/debug/Environment-names-01.js19
-rw-r--r--js/src/jit-test/tests/debug/Environment-names-02.js34
-rw-r--r--js/src/jit-test/tests/debug/Environment-names-03.js22
-rw-r--r--js/src/jit-test/tests/debug/Environment-nondebuggee.js40
-rw-r--r--js/src/jit-test/tests/debug/Environment-object-01.js9
-rw-r--r--js/src/jit-test/tests/debug/Environment-optimizedOut-01.js44
-rw-r--r--js/src/jit-test/tests/debug/Environment-parent-01.js18
-rw-r--r--js/src/jit-test/tests/debug/Environment-scopeKind-01.js26
-rw-r--r--js/src/jit-test/tests/debug/Environment-selfhosted-builtins.js15
-rw-r--r--js/src/jit-test/tests/debug/Environment-setVariable-01.js9
-rw-r--r--js/src/jit-test/tests/debug/Environment-setVariable-02.js10
-rw-r--r--js/src/jit-test/tests/debug/Environment-setVariable-03.js16
-rw-r--r--js/src/jit-test/tests/debug/Environment-setVariable-04.js10
-rw-r--r--js/src/jit-test/tests/debug/Environment-setVariable-05.js14
-rw-r--r--js/src/jit-test/tests/debug/Environment-setVariable-06.js9
-rw-r--r--js/src/jit-test/tests/debug/Environment-setVariable-07.js14
-rw-r--r--js/src/jit-test/tests/debug/Environment-setVariable-08.js29
-rw-r--r--js/src/jit-test/tests/debug/Environment-setVariable-10.js32
-rw-r--r--js/src/jit-test/tests/debug/Environment-setVariable-11.js16
-rw-r--r--js/src/jit-test/tests/debug/Environment-setVariable-12.js21
-rw-r--r--js/src/jit-test/tests/debug/Environment-setVariable-13.js20
-rw-r--r--js/src/jit-test/tests/debug/Environment-setVariable-WouldRun.js23
-rw-r--r--js/src/jit-test/tests/debug/Environment-type-01.js29
-rw-r--r--js/src/jit-test/tests/debug/Environment-unscopables.js37
-rw-r--r--js/src/jit-test/tests/debug/Environment-variables.js85
-rw-r--r--js/src/jit-test/tests/debug/Frame-01.js34
-rw-r--r--js/src/jit-test/tests/debug/Frame-02.js24
-rw-r--r--js/src/jit-test/tests/debug/Frame-03.js19
-rw-r--r--js/src/jit-test/tests/debug/Frame-arguments-01.js41
-rw-r--r--js/src/jit-test/tests/debug/Frame-arguments-02.js19
-rw-r--r--js/src/jit-test/tests/debug/Frame-arguments-03.js34
-rw-r--r--js/src/jit-test/tests/debug/Frame-arguments-04.js18
-rw-r--r--js/src/jit-test/tests/debug/Frame-arguments-05.js19
-rw-r--r--js/src/jit-test/tests/debug/Frame-arguments-06.js38
-rw-r--r--js/src/jit-test/tests/debug/Frame-arguments-07.js24
-rw-r--r--js/src/jit-test/tests/debug/Frame-asyncPromise-01.js72
-rw-r--r--js/src/jit-test/tests/debug/Frame-asyncPromise-02.js26
-rw-r--r--js/src/jit-test/tests/debug/Frame-asyncPromise-03.js39
-rw-r--r--js/src/jit-test/tests/debug/Frame-asyncPromise-04.js104
-rw-r--r--js/src/jit-test/tests/debug/Frame-callee-01.js26
-rw-r--r--js/src/jit-test/tests/debug/Frame-callee-02.js37
-rw-r--r--js/src/jit-test/tests/debug/Frame-callee-03.js37
-rw-r--r--js/src/jit-test/tests/debug/Frame-callee-04.js47
-rw-r--r--js/src/jit-test/tests/debug/Frame-constructing-01.js27
-rw-r--r--js/src/jit-test/tests/debug/Frame-constructing-02.js31
-rw-r--r--js/src/jit-test/tests/debug/Frame-constructing-03.js35
-rw-r--r--js/src/jit-test/tests/debug/Frame-environment-01.js13
-rw-r--r--js/src/jit-test/tests/debug/Frame-environment-02.js12
-rw-r--r--js/src/jit-test/tests/debug/Frame-environment-03.js11
-rw-r--r--js/src/jit-test/tests/debug/Frame-environment-04.js12
-rw-r--r--js/src/jit-test/tests/debug/Frame-environment-05.js9
-rw-r--r--js/src/jit-test/tests/debug/Frame-environment-06.js105
-rw-r--r--js/src/jit-test/tests/debug/Frame-environment-07.js83
-rw-r--r--js/src/jit-test/tests/debug/Frame-environment-08.js108
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-01.js8
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-02.js10
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-03.js19
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-04.js11
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-05.js14
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-06.js19
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-07.js31
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-08.js23
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-09.js21
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-10.js13
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-11.js15
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-12.js13
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-13.js13
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-14.js26
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-15.js13
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-16.js26
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-17.js24
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-18.js12
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-19.js34
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-20.js48
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-21.js33
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-22.js32
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-23.js37
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-24.js25
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-25.js25
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-26.js16
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-27.js13
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-28.js12
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-29.js59
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-30.js19
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-31.js9
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-32.js8
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-33.js39
-rw-r--r--js/src/jit-test/tests/debug/Frame-eval-stack.js19
-rw-r--r--js/src/jit-test/tests/debug/Frame-evalWithBindings-01.js35
-rw-r--r--js/src/jit-test/tests/debug/Frame-evalWithBindings-02.js21
-rw-r--r--js/src/jit-test/tests/debug/Frame-evalWithBindings-03.js16
-rw-r--r--js/src/jit-test/tests/debug/Frame-evalWithBindings-04.js17
-rw-r--r--js/src/jit-test/tests/debug/Frame-evalWithBindings-05.js12
-rw-r--r--js/src/jit-test/tests/debug/Frame-evalWithBindings-06.js9
-rw-r--r--js/src/jit-test/tests/debug/Frame-evalWithBindings-07.js16
-rw-r--r--js/src/jit-test/tests/debug/Frame-evalWithBindings-08.js13
-rw-r--r--js/src/jit-test/tests/debug/Frame-evalWithBindings-09.js27
-rw-r--r--js/src/jit-test/tests/debug/Frame-evalWithBindings-10.js16
-rw-r--r--js/src/jit-test/tests/debug/Frame-evalWithBindings-11.js18
-rw-r--r--js/src/jit-test/tests/debug/Frame-evalWithBindings-12.js26
-rw-r--r--js/src/jit-test/tests/debug/Frame-evalWithBindings-13.js24
-rw-r--r--js/src/jit-test/tests/debug/Frame-evalWithBindings-14.js20
-rw-r--r--js/src/jit-test/tests/debug/Frame-evalWithBindings-15.js15
-rw-r--r--js/src/jit-test/tests/debug/Frame-identity-01.js19
-rw-r--r--js/src/jit-test/tests/debug/Frame-identity-02.js21
-rw-r--r--js/src/jit-test/tests/debug/Frame-identity-03.js49
-rw-r--r--js/src/jit-test/tests/debug/Frame-identity-04.js20
-rw-r--r--js/src/jit-test/tests/debug/Frame-identity-05.js20
-rw-r--r--js/src/jit-test/tests/debug/Frame-identity-06.js45
-rw-r--r--js/src/jit-test/tests/debug/Frame-identity-07.js52
-rw-r--r--js/src/jit-test/tests/debug/Frame-implementation-01.js45
-rw-r--r--js/src/jit-test/tests/debug/Frame-implementation-02.js51
-rw-r--r--js/src/jit-test/tests/debug/Frame-newTargetEval-01.js40
-rw-r--r--js/src/jit-test/tests/debug/Frame-newTargetEval-02.js43
-rw-r--r--js/src/jit-test/tests/debug/Frame-newTargetOverflow-01.js41
-rw-r--r--js/src/jit-test/tests/debug/Frame-offset-01.js11
-rw-r--r--js/src/jit-test/tests/debug/Frame-offset-02.js16
-rw-r--r--js/src/jit-test/tests/debug/Frame-offset-03.js71
-rw-r--r--js/src/jit-test/tests/debug/Frame-offset-04.js50
-rw-r--r--js/src/jit-test/tests/debug/Frame-offset-05.js94
-rw-r--r--js/src/jit-test/tests/debug/Frame-older-01.js19
-rw-r--r--js/src/jit-test/tests/debug/Frame-older-02.js34
-rw-r--r--js/src/jit-test/tests/debug/Frame-older-generators-01.js53
-rw-r--r--js/src/jit-test/tests/debug/Frame-older-generators-02.js50
-rw-r--r--js/src/jit-test/tests/debug/Frame-older-generators-03.js24
-rw-r--r--js/src/jit-test/tests/debug/Frame-older-generators-04.js28
-rw-r--r--js/src/jit-test/tests/debug/Frame-older-generators-05.js32
-rw-r--r--js/src/jit-test/tests/debug/Frame-olderSavedFrame-01.js40
-rw-r--r--js/src/jit-test/tests/debug/Frame-olderSavedFrame-02.js32
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-01.js29
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-02.js20
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-03.js32
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-04.js30
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-05.js25
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-06.js20
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-08.js16
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-09.js23
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-10.js22
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-11.js23
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-12.js21
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-13.js37
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-14.js25
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-15.js31
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-16.js18
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-17.js41
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-18.js22
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-19.js16
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-20.js15
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-21.js30
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-23.js34
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-after-debugger-return.js11
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-assign-function.js48
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-assign-generator.js68
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-async-01.js39
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-async-02.js36
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-async-generators-01.js53
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-dead-frame.js28
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-error-error.js60
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-error-return.js47
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-error-scope-unwind-01.js33
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-error-scope-unwind-02.js36
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-error-throw.js42
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-error.js59
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-generator-resumption-01.js14
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-generators-01.js20
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-generators-02.js19
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-generators-03.js42
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-generators-04.js26
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-generators-05.js29
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-generators-06.js28
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-generators-07.js40
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-generators-08.js16
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-multiple-01.js106
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-multiple-02.js36
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-multiple-04.js27
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-return-error.js59
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-return-return.js46
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-return-throw.js41
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-return.js45
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-throw-error.js59
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-throw-return.js46
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-throw-throw.js41
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-throw.js40
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStack-01.js41
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStack-02.js32
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStack-03.js25
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStack-04.js31
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStack-05.js33
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStack-06.js26
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStack-07.js56
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-01.js24
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-02.js27
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-03.js28
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-04.js34
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-05.js14
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-06.js58
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-07.js23
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-08.js29
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-09.js24
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-10.js28
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-11.js36
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-12.js118
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-13.js29
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-14.js46
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-15.js43
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-16.js34
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-17.js35
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-18.js22
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-19.js41
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-20.js41
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-assign-function.js49
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-assign-generator.js68
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-async-01.js35
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-async-02.js87
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-async-03.js18
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-async-gc-01.js28
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-generator-resumption-01.js42
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-generator-resumption-02.js78
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-generator-resumption-03.js49
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-generators-01.js31
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-generators-02.js44
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-generators-03.js45
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-generators-04.js36
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-generators-05.js14
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-generators-defaults.js35
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-generators-gc-01.js84
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-iterators.js20
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-lines-01.js78
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-resumption-01.js14
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-resumption-02.js17
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-resumption-03.js19
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-resumption-04.js31
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-resumption-05.js55
-rw-r--r--js/src/jit-test/tests/debug/Frame-onStep-resumption-06.js31
-rw-r--r--js/src/jit-test/tests/debug/Frame-script-01.js25
-rw-r--r--js/src/jit-test/tests/debug/Frame-script-02.js27
-rw-r--r--js/src/jit-test/tests/debug/Frame-script-03.js8
-rw-r--r--js/src/jit-test/tests/debug/Frame-script-04.js35
-rw-r--r--js/src/jit-test/tests/debug/Frame-script-05.js37
-rw-r--r--js/src/jit-test/tests/debug/Frame-script-06.js48
-rw-r--r--js/src/jit-test/tests/debug/Frame-script-environment-nondebuggee.js32
-rw-r--r--js/src/jit-test/tests/debug/Frame-terminated-01.js19
-rw-r--r--js/src/jit-test/tests/debug/Frame-terminated-02.js27
-rw-r--r--js/src/jit-test/tests/debug/Frame-terminated-03.js23
-rw-r--r--js/src/jit-test/tests/debug/Frame-terminated-04.js31
-rw-r--r--js/src/jit-test/tests/debug/Frame-this-01.js24
-rw-r--r--js/src/jit-test/tests/debug/Frame-this-02.js17
-rw-r--r--js/src/jit-test/tests/debug/Frame-this-03.js29
-rw-r--r--js/src/jit-test/tests/debug/Frame-this-04.js25
-rw-r--r--js/src/jit-test/tests/debug/Frame-this-05.js23
-rw-r--r--js/src/jit-test/tests/debug/Frame-this-06.js22
-rw-r--r--js/src/jit-test/tests/debug/Frame-this-07.js19
-rw-r--r--js/src/jit-test/tests/debug/Frame-this-08.js16
-rw-r--r--js/src/jit-test/tests/debug/Frame-this-09.js45
-rw-r--r--js/src/jit-test/tests/debug/Frame-this-10.js42
-rw-r--r--js/src/jit-test/tests/debug/Frame-this-11.js46
-rw-r--r--js/src/jit-test/tests/debug/Frame-this-12.js42
-rw-r--r--js/src/jit-test/tests/debug/Frame-this-13.js26
-rw-r--r--js/src/jit-test/tests/debug/Frame-this-14.js31
-rw-r--r--js/src/jit-test/tests/debug/Frame-this-15.js34
-rw-r--r--js/src/jit-test/tests/debug/Frame-this-16.js38
-rw-r--r--js/src/jit-test/tests/debug/Frame-type-01.js22
-rw-r--r--js/src/jit-test/tests/debug/Frame-type-02.js27
-rw-r--r--js/src/jit-test/tests/debug/Frame-type-03.js31
-rw-r--r--js/src/jit-test/tests/debug/Frame-type-04.js35
-rw-r--r--js/src/jit-test/tests/debug/Memory-01.js6
-rw-r--r--js/src/jit-test/tests/debug/Memory-allocationSamplingProbability-01.js45
-rw-r--r--js/src/jit-test/tests/debug/Memory-allocationSamplingProbability-02.js36
-rw-r--r--js/src/jit-test/tests/debug/Memory-allocationsLogOverflowed-01.js24
-rw-r--r--js/src/jit-test/tests/debug/Memory-drainAllocationsLog-01.js31
-rw-r--r--js/src/jit-test/tests/debug/Memory-drainAllocationsLog-02.js14
-rw-r--r--js/src/jit-test/tests/debug/Memory-drainAllocationsLog-03.js24
-rw-r--r--js/src/jit-test/tests/debug/Memory-drainAllocationsLog-04.js21
-rw-r--r--js/src/jit-test/tests/debug/Memory-drainAllocationsLog-05.js9
-rw-r--r--js/src/jit-test/tests/debug/Memory-drainAllocationsLog-06.js23
-rw-r--r--js/src/jit-test/tests/debug/Memory-drainAllocationsLog-07.js10
-rw-r--r--js/src/jit-test/tests/debug/Memory-drainAllocationsLog-08.js30
-rw-r--r--js/src/jit-test/tests/debug/Memory-drainAllocationsLog-09.js20
-rw-r--r--js/src/jit-test/tests/debug/Memory-drainAllocationsLog-10.js21
-rw-r--r--js/src/jit-test/tests/debug/Memory-drainAllocationsLog-11.js25
-rw-r--r--js/src/jit-test/tests/debug/Memory-drainAllocationsLog-13.js16
-rw-r--r--js/src/jit-test/tests/debug/Memory-drainAllocationsLog-14.js47
-rw-r--r--js/src/jit-test/tests/debug/Memory-drainAllocationsLog-15.js30
-rw-r--r--js/src/jit-test/tests/debug/Memory-drainAllocationsLog-17.js55
-rw-r--r--js/src/jit-test/tests/debug/Memory-drainAllocationsLog-18.js31
-rw-r--r--js/src/jit-test/tests/debug/Memory-takeCensus-01.js23
-rw-r--r--js/src/jit-test/tests/debug/Memory-takeCensus-02.js49
-rw-r--r--js/src/jit-test/tests/debug/Memory-takeCensus-03.js26
-rw-r--r--js/src/jit-test/tests/debug/Memory-takeCensus-04.js27
-rw-r--r--js/src/jit-test/tests/debug/Memory-takeCensus-05.js14
-rw-r--r--js/src/jit-test/tests/debug/Memory-takeCensus-06.js115
-rw-r--r--js/src/jit-test/tests/debug/Memory-takeCensus-07.js75
-rw-r--r--js/src/jit-test/tests/debug/Memory-takeCensus-08.js73
-rw-r--r--js/src/jit-test/tests/debug/Memory-takeCensus-09.js74
-rw-r--r--js/src/jit-test/tests/debug/Memory-takeCensus-10.js57
-rw-r--r--js/src/jit-test/tests/debug/Memory-takeCensus-11.js49
-rw-r--r--js/src/jit-test/tests/debug/Memory-takeCensus-12.js61
-rw-r--r--js/src/jit-test/tests/debug/Memory-trackingAllocationSites-01.js37
-rw-r--r--js/src/jit-test/tests/debug/Memory-trackingAllocationSites-02.js19
-rw-r--r--js/src/jit-test/tests/debug/Memory-trackingAllocationSites-03.js76
-rw-r--r--js/src/jit-test/tests/debug/Object-01.js17
-rw-r--r--js/src/jit-test/tests/debug/Object-02.js13
-rw-r--r--js/src/jit-test/tests/debug/Object-apply-01.js59
-rw-r--r--js/src/jit-test/tests/debug/Object-apply-02.js58
-rw-r--r--js/src/jit-test/tests/debug/Object-apply-03.js21
-rw-r--r--js/src/jit-test/tests/debug/Object-apply-04.js15
-rw-r--r--js/src/jit-test/tests/debug/Object-asEnvironment-01.js15
-rw-r--r--js/src/jit-test/tests/debug/Object-boundTargetFunction-01.js26
-rw-r--r--js/src/jit-test/tests/debug/Object-boundTargetFunction-02.js25
-rw-r--r--js/src/jit-test/tests/debug/Object-boundTargetFunction-03.js20
-rw-r--r--js/src/jit-test/tests/debug/Object-callable.js18
-rw-r--r--js/src/jit-test/tests/debug/Object-class.js26
-rw-r--r--js/src/jit-test/tests/debug/Object-createSource.js18
-rw-r--r--js/src/jit-test/tests/debug/Object-defineProperties-01.js46
-rw-r--r--js/src/jit-test/tests/debug/Object-defineProperties-02.js33
-rw-r--r--js/src/jit-test/tests/debug/Object-defineProperties-03.js20
-rw-r--r--js/src/jit-test/tests/debug/Object-defineProperty-01.js12
-rw-r--r--js/src/jit-test/tests/debug/Object-defineProperty-02.js10
-rw-r--r--js/src/jit-test/tests/debug/Object-defineProperty-03.js21
-rw-r--r--js/src/jit-test/tests/debug/Object-defineProperty-04.js9
-rw-r--r--js/src/jit-test/tests/debug/Object-defineProperty-05.js20
-rw-r--r--js/src/jit-test/tests/debug/Object-defineProperty-06.js21
-rw-r--r--js/src/jit-test/tests/debug/Object-defineProperty-07.js10
-rw-r--r--js/src/jit-test/tests/debug/Object-defineProperty-08.js10
-rw-r--r--js/src/jit-test/tests/debug/Object-defineProperty-09.js24
-rw-r--r--js/src/jit-test/tests/debug/Object-defineProperty-10.js10
-rw-r--r--js/src/jit-test/tests/debug/Object-defineProperty-11.js16
-rw-r--r--js/src/jit-test/tests/debug/Object-defineProperty-12.js18
-rw-r--r--js/src/jit-test/tests/debug/Object-defineProperty-13.js16
-rw-r--r--js/src/jit-test/tests/debug/Object-defineProperty-14.js15
-rw-r--r--js/src/jit-test/tests/debug/Object-defineProperty-non-primitive-key.js41
-rw-r--r--js/src/jit-test/tests/debug/Object-defineProperty-surfaces-01.js8
-rw-r--r--js/src/jit-test/tests/debug/Object-deleteProperty-01.js17
-rw-r--r--js/src/jit-test/tests/debug/Object-deleteProperty-error-01.js16
-rw-r--r--js/src/jit-test/tests/debug/Object-deleteProperty-error-02.js19
-rw-r--r--js/src/jit-test/tests/debug/Object-deleteProperty-non-primitive-key.js49
-rw-r--r--js/src/jit-test/tests/debug/Object-displayName-01.js21
-rw-r--r--js/src/jit-test/tests/debug/Object-environment-01.js17
-rw-r--r--js/src/jit-test/tests/debug/Object-environment-02.js22
-rw-r--r--js/src/jit-test/tests/debug/Object-errorLineNumber-errorColumnNumber.js55
-rw-r--r--js/src/jit-test/tests/debug/Object-executeInGlobal-01.js13
-rw-r--r--js/src/jit-test/tests/debug/Object-executeInGlobal-02.js20
-rw-r--r--js/src/jit-test/tests/debug/Object-executeInGlobal-03.js19
-rw-r--r--js/src/jit-test/tests/debug/Object-executeInGlobal-04.js55
-rw-r--r--js/src/jit-test/tests/debug/Object-executeInGlobal-05.js21
-rw-r--r--js/src/jit-test/tests/debug/Object-executeInGlobal-06.js8
-rw-r--r--js/src/jit-test/tests/debug/Object-executeInGlobal-07.js24
-rw-r--r--js/src/jit-test/tests/debug/Object-executeInGlobal-08.js22
-rw-r--r--js/src/jit-test/tests/debug/Object-executeInGlobal-09.js9
-rw-r--r--js/src/jit-test/tests/debug/Object-executeInGlobal-10.js13
-rw-r--r--js/src/jit-test/tests/debug/Object-forceLexicalInitializationByName.js61
-rw-r--r--js/src/jit-test/tests/debug/Object-gc-01.js14
-rw-r--r--js/src/jit-test/tests/debug/Object-getErrorMessageName.js29
-rw-r--r--js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-01.js59
-rw-r--r--js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-02.js8
-rw-r--r--js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-03.js22
-rw-r--r--js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-04.js18
-rw-r--r--js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-05.js17
-rw-r--r--js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-06.js29
-rw-r--r--js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-non-primitive-key.js37
-rw-r--r--js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-surfaces-01.js14
-rw-r--r--js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-surfaces-02.js14
-rw-r--r--js/src/jit-test/tests/debug/Object-getOwnPropertyNames-01.js33
-rw-r--r--js/src/jit-test/tests/debug/Object-getOwnPropertyNames-02.js11
-rw-r--r--js/src/jit-test/tests/debug/Object-getOwnPropertySymbols-01.js33
-rw-r--r--js/src/jit-test/tests/debug/Object-getOwnPropertySymbols-02.js12
-rw-r--r--js/src/jit-test/tests/debug/Object-getPromiseReactions-01.js25
-rw-r--r--js/src/jit-test/tests/debug/Object-getPromiseReactions-02.js49
-rw-r--r--js/src/jit-test/tests/debug/Object-getPromiseReactions-03.js41
-rw-r--r--js/src/jit-test/tests/debug/Object-getPromiseReactions-04.js48
-rw-r--r--js/src/jit-test/tests/debug/Object-getPromiseReactions-05.js48
-rw-r--r--js/src/jit-test/tests/debug/Object-getPromiseReactions-06.js43
-rw-r--r--js/src/jit-test/tests/debug/Object-getProperty-01.js122
-rw-r--r--js/src/jit-test/tests/debug/Object-getProperty-02.js35
-rw-r--r--js/src/jit-test/tests/debug/Object-getProperty-03.js46
-rw-r--r--js/src/jit-test/tests/debug/Object-getProperty-non-primitive-key.js37
-rw-r--r--js/src/jit-test/tests/debug/Object-identity-01.js10
-rw-r--r--js/src/jit-test/tests/debug/Object-identity-02.js10
-rw-r--r--js/src/jit-test/tests/debug/Object-identity-03.js25
-rw-r--r--js/src/jit-test/tests/debug/Object-isArrowFunction.js24
-rw-r--r--js/src/jit-test/tests/debug/Object-isClassConstructor.js21
-rw-r--r--js/src/jit-test/tests/debug/Object-isSameNative-01.js9
-rw-r--r--js/src/jit-test/tests/debug/Object-isSameNative.js46
-rw-r--r--js/src/jit-test/tests/debug/Object-makeDebuggeeNativeFunction-01.js28
-rw-r--r--js/src/jit-test/tests/debug/Object-makeDebuggeeValue-01.js42
-rw-r--r--js/src/jit-test/tests/debug/Object-makeDebuggeeValue-02.js12
-rw-r--r--js/src/jit-test/tests/debug/Object-name-01.js17
-rw-r--r--js/src/jit-test/tests/debug/Object-name-02.js16
-rw-r--r--js/src/jit-test/tests/debug/Object-parameterNames.js32
-rw-r--r--js/src/jit-test/tests/debug/Object-preventExtensions-01.js17
-rw-r--r--js/src/jit-test/tests/debug/Object-promiseDependentPromises-realms.js17
-rw-r--r--js/src/jit-test/tests/debug/Object-proto.js23
-rw-r--r--js/src/jit-test/tests/debug/Object-proxy.js44
-rw-r--r--js/src/jit-test/tests/debug/Object-script-AsmJSNative.js15
-rw-r--r--js/src/jit-test/tests/debug/Object-script-environment-nondebuggee.js24
-rw-r--r--js/src/jit-test/tests/debug/Object-script-lazy.js57
-rw-r--r--js/src/jit-test/tests/debug/Object-script.js25
-rw-r--r--js/src/jit-test/tests/debug/Object-seal-01.js63
-rw-r--r--js/src/jit-test/tests/debug/Object-setProperty-01.js185
-rw-r--r--js/src/jit-test/tests/debug/Object-setProperty-02.js36
-rw-r--r--js/src/jit-test/tests/debug/Object-setProperty-03.js67
-rw-r--r--js/src/jit-test/tests/debug/Object-setProperty-non-primitive-key.js49
-rw-r--r--js/src/jit-test/tests/debug/Object-unsafeDereference-01.js10
-rw-r--r--js/src/jit-test/tests/debug/Object-unwrap-01.js23
-rw-r--r--js/src/jit-test/tests/debug/Object-unwrap-02.js21
-rw-r--r--js/src/jit-test/tests/debug/Object-unwrap-03.js15
-rw-r--r--js/src/jit-test/tests/debug/Promise-race-dependent-promises.js46
-rw-r--r--js/src/jit-test/tests/debug/RematerializedFrame-retval.js39
-rw-r--r--js/src/jit-test/tests/debug/Script-01.js70
-rw-r--r--js/src/jit-test/tests/debug/Script-02.js6
-rw-r--r--js/src/jit-test/tests/debug/Script-clearBreakpoint-01.js19
-rw-r--r--js/src/jit-test/tests/debug/Script-clearBreakpoint-02.js26
-rw-r--r--js/src/jit-test/tests/debug/Script-clearBreakpoint-03.js25
-rw-r--r--js/src/jit-test/tests/debug/Script-clearBreakpoint-04.js28
-rw-r--r--js/src/jit-test/tests/debug/Script-displayName-01.js17
-rw-r--r--js/src/jit-test/tests/debug/Script-format-01.js18
-rw-r--r--js/src/jit-test/tests/debug/Script-gc-01.js26
-rw-r--r--js/src/jit-test/tests/debug/Script-gc-02.js14
-rw-r--r--js/src/jit-test/tests/debug/Script-gc-03.js15
-rw-r--r--js/src/jit-test/tests/debug/Script-getAllColumnOffsets.js147
-rw-r--r--js/src/jit-test/tests/debug/Script-getBreakpoints-01.js40
-rw-r--r--js/src/jit-test/tests/debug/Script-getBreakpoints-02.js42
-rw-r--r--js/src/jit-test/tests/debug/Script-getChildScripts-01.js40
-rw-r--r--js/src/jit-test/tests/debug/Script-getChildScripts-02.js20
-rw-r--r--js/src/jit-test/tests/debug/Script-getChildScripts-03.js16
-rw-r--r--js/src/jit-test/tests/debug/Script-getChildScripts-04.js15
-rw-r--r--js/src/jit-test/tests/debug/Script-getChildScripts-05.js16
-rw-r--r--js/src/jit-test/tests/debug/Script-getEffectfulOffsets.js33
-rw-r--r--js/src/jit-test/tests/debug/Script-getLineOffsets-01.js13
-rw-r--r--js/src/jit-test/tests/debug/Script-getLineOffsets-02.js33
-rw-r--r--js/src/jit-test/tests/debug/Script-getLineOffsets-03.js36
-rw-r--r--js/src/jit-test/tests/debug/Script-getLineOffsets-04.js53
-rw-r--r--js/src/jit-test/tests/debug/Script-getLineOffsets-05.js65
-rw-r--r--js/src/jit-test/tests/debug/Script-getLineOffsets-06.js99
-rw-r--r--js/src/jit-test/tests/debug/Script-getLineOffsets-07.js19
-rw-r--r--js/src/jit-test/tests/debug/Script-getLineOffsets-08.js25
-rw-r--r--js/src/jit-test/tests/debug/Script-getOffsetLine-01.js25
-rw-r--r--js/src/jit-test/tests/debug/Script-getOffsetLine-02.js19
-rw-r--r--js/src/jit-test/tests/debug/Script-getOffsetLocation.js37
-rw-r--r--js/src/jit-test/tests/debug/Script-getOffsetMetadata.js36
-rw-r--r--js/src/jit-test/tests/debug/Script-getOffsetsCoverage-01.js501
-rw-r--r--js/src/jit-test/tests/debug/Script-getOffsetsCoverage-02.js41
-rw-r--r--js/src/jit-test/tests/debug/Script-getOffsetsCoverage-03.js21
-rw-r--r--js/src/jit-test/tests/debug/Script-getOffsetsCoverage-04.js22
-rw-r--r--js/src/jit-test/tests/debug/Script-getOffsetsCoverage-05.js24
-rw-r--r--js/src/jit-test/tests/debug/Script-getOffsetsCoverage-bug1233178.js13
-rw-r--r--js/src/jit-test/tests/debug/Script-getPossibleBreakpoints-02.js83
-rw-r--r--js/src/jit-test/tests/debug/Script-getPossibleBreakpoints.js384
-rw-r--r--js/src/jit-test/tests/debug/Script-global-01.js18
-rw-r--r--js/src/jit-test/tests/debug/Script-global-02.js36
-rw-r--r--js/src/jit-test/tests/debug/Script-isFunction.js25
-rw-r--r--js/src/jit-test/tests/debug/Script-isInCatchScope.js68
-rw-r--r--js/src/jit-test/tests/debug/Script-isModule-01.js13
-rw-r--r--js/src/jit-test/tests/debug/Script-isModule-02.js11
-rw-r--r--js/src/jit-test/tests/debug/Script-isModule-03.js18
-rw-r--r--js/src/jit-test/tests/debug/Script-isModule-04.js19
-rw-r--r--js/src/jit-test/tests/debug/Script-lineCount.js23
-rw-r--r--js/src/jit-test/tests/debug/Script-mainOffset-01.js20
-rw-r--r--js/src/jit-test/tests/debug/Script-parameterNames.js36
-rw-r--r--js/src/jit-test/tests/debug/Script-selfhosted-builtins.js15
-rw-r--r--js/src/jit-test/tests/debug/Script-source-01.js26
-rw-r--r--js/src/jit-test/tests/debug/Script-source-02.js16
-rw-r--r--js/src/jit-test/tests/debug/Script-source-03.js22
-rw-r--r--js/src/jit-test/tests/debug/Script-sourceStart-01.js22
-rw-r--r--js/src/jit-test/tests/debug/Script-sourceStart-02.js32
-rw-r--r--js/src/jit-test/tests/debug/Script-sourceStart-03.js35
-rw-r--r--js/src/jit-test/tests/debug/Script-sourceStart-04.js25
-rw-r--r--js/src/jit-test/tests/debug/Script-startColumn.js99
-rw-r--r--js/src/jit-test/tests/debug/Script-startLine.js63
-rw-r--r--js/src/jit-test/tests/debug/Script-url.js10
-rw-r--r--js/src/jit-test/tests/debug/Source-displayURL-deprecated.js26
-rw-r--r--js/src/jit-test/tests/debug/Source-displayURL-disable.js14
-rw-r--r--js/src/jit-test/tests/debug/Source-displayURL.js91
-rw-r--r--js/src/jit-test/tests/debug/Source-element-01.js13
-rw-r--r--js/src/jit-test/tests/debug/Source-element-02.js6
-rw-r--r--js/src/jit-test/tests/debug/Source-element-03.js26
-rw-r--r--js/src/jit-test/tests/debug/Source-element-04.js13
-rw-r--r--js/src/jit-test/tests/debug/Source-element-05.js11
-rw-r--r--js/src/jit-test/tests/debug/Source-elementAttributeName.js11
-rw-r--r--js/src/jit-test/tests/debug/Source-introductionScript-01.js118
-rw-r--r--js/src/jit-test/tests/debug/Source-introductionScript-02.js44
-rw-r--r--js/src/jit-test/tests/debug/Source-introductionScript-03.js32
-rw-r--r--js/src/jit-test/tests/debug/Source-introductionType-data1
-rw-r--r--js/src/jit-test/tests/debug/Source-introductionType.js119
-rw-r--r--js/src/jit-test/tests/debug/Source-invisible.js10
-rw-r--r--js/src/jit-test/tests/debug/Source-reparse.js42
-rw-r--r--js/src/jit-test/tests/debug/Source-sourceMapURL-deprecated.js82
-rw-r--r--js/src/jit-test/tests/debug/Source-sourceMapURL-disable.js14
-rw-r--r--js/src/jit-test/tests/debug/Source-sourceMapURL.js82
-rw-r--r--js/src/jit-test/tests/debug/Source-startLine.js11
-rw-r--r--js/src/jit-test/tests/debug/Source-surfaces.js33
-rw-r--r--js/src/jit-test/tests/debug/Source-text-01.js26
-rw-r--r--js/src/jit-test/tests/debug/Source-text-02.js20
-rw-r--r--js/src/jit-test/tests/debug/Source-text-lazy.js38
-rw-r--r--js/src/jit-test/tests/debug/Source-url-01.js10
-rw-r--r--js/src/jit-test/tests/debug/Source-url-02.js11
-rw-r--r--js/src/jit-test/tests/debug/Source-url.js10
-rw-r--r--js/src/jit-test/tests/debug/breakpoint-01.js22
-rw-r--r--js/src/jit-test/tests/debug/breakpoint-02.js15
-rw-r--r--js/src/jit-test/tests/debug/breakpoint-03.js16
-rw-r--r--js/src/jit-test/tests/debug/breakpoint-04.js30
-rw-r--r--js/src/jit-test/tests/debug/breakpoint-05.js19
-rw-r--r--js/src/jit-test/tests/debug/breakpoint-06.js20
-rw-r--r--js/src/jit-test/tests/debug/breakpoint-07.js30
-rw-r--r--js/src/jit-test/tests/debug/breakpoint-08.js31
-rw-r--r--js/src/jit-test/tests/debug/breakpoint-09.js13
-rw-r--r--js/src/jit-test/tests/debug/breakpoint-10.js19
-rw-r--r--js/src/jit-test/tests/debug/breakpoint-11.js38
-rw-r--r--js/src/jit-test/tests/debug/breakpoint-12.js78
-rw-r--r--js/src/jit-test/tests/debug/breakpoint-13.js13
-rw-r--r--js/src/jit-test/tests/debug/breakpoint-14.js14
-rw-r--r--js/src/jit-test/tests/debug/breakpoint-gc-01.js25
-rw-r--r--js/src/jit-test/tests/debug/breakpoint-gc-02.js28
-rw-r--r--js/src/jit-test/tests/debug/breakpoint-gc-04.js23
-rw-r--r--js/src/jit-test/tests/debug/breakpoint-gc-05.js25
-rw-r--r--js/src/jit-test/tests/debug/breakpoint-multi-01.js28
-rw-r--r--js/src/jit-test/tests/debug/breakpoint-multi-02.js42
-rw-r--r--js/src/jit-test/tests/debug/breakpoint-multi-03.js27
-rw-r--r--js/src/jit-test/tests/debug/breakpoint-multi-04.js48
-rw-r--r--js/src/jit-test/tests/debug/breakpoint-noncng.js20
-rw-r--r--js/src/jit-test/tests/debug/breakpoint-oom-01.js41
-rw-r--r--js/src/jit-test/tests/debug/breakpoint-resume-01.js25
-rw-r--r--js/src/jit-test/tests/debug/breakpoint-resume-02.js34
-rw-r--r--js/src/jit-test/tests/debug/breakpoint-resume-03.js30
-rw-r--r--js/src/jit-test/tests/debug/breakpoint-resume-04.js37
-rw-r--r--js/src/jit-test/tests/debug/bug-1102549.js9
-rw-r--r--js/src/jit-test/tests/debug/bug-1103386.js10
-rw-r--r--js/src/jit-test/tests/debug/bug-1103813.js7
-rw-r--r--js/src/jit-test/tests/debug/bug-1103817.js5
-rw-r--r--js/src/jit-test/tests/debug/bug-1110327.js5
-rw-r--r--js/src/jit-test/tests/debug/bug-1136806.js7
-rw-r--r--js/src/jit-test/tests/debug/bug-1192401.js6
-rw-r--r--js/src/jit-test/tests/debug/bug-1238610.js21
-rw-r--r--js/src/jit-test/tests/debug/bug-1240090.js23
-rw-r--r--js/src/jit-test/tests/debug/bug-1248162.js11
-rw-r--r--js/src/jit-test/tests/debug/bug-1260725.js9
-rw-r--r--js/src/jit-test/tests/debug/bug-1260728.js12
-rw-r--r--js/src/jit-test/tests/debug/bug-1385844-2.js39
-rw-r--r--js/src/jit-test/tests/debug/bug-1385844.js24
-rw-r--r--js/src/jit-test/tests/debug/bug-1444604-reduced.js27
-rw-r--r--js/src/jit-test/tests/debug/bug-1444604.js16
-rw-r--r--js/src/jit-test/tests/debug/bug-1477084.js30
-rw-r--r--js/src/jit-test/tests/debug/bug-1564012.js8
-rw-r--r--js/src/jit-test/tests/debug/bug-1565275.js19
-rw-r--r--js/src/jit-test/tests/debug/bug-1572391.js15
-rw-r--r--js/src/jit-test/tests/debug/bug-1576862-2.js23
-rw-r--r--js/src/jit-test/tests/debug/bug-1584195.js31
-rw-r--r--js/src/jit-test/tests/debug/bug-725733.js9
-rw-r--r--js/src/jit-test/tests/debug/bug-800586.js7
-rw-r--r--js/src/jit-test/tests/debug/bug-826669.js7
-rw-r--r--js/src/jit-test/tests/debug/bug-858170.js7
-rw-r--r--js/src/jit-test/tests/debug/bug-876654.js13
-rw-r--r--js/src/jit-test/tests/debug/bug1001372.js21
-rw-r--r--js/src/jit-test/tests/debug/bug1002797.js15
-rw-r--r--js/src/jit-test/tests/debug/bug1004447.js23
-rw-r--r--js/src/jit-test/tests/debug/bug1006205.js20
-rw-r--r--js/src/jit-test/tests/debug/bug1006473.js19
-rw-r--r--js/src/jit-test/tests/debug/bug1106164.js17
-rw-r--r--js/src/jit-test/tests/debug/bug1106719.js12
-rw-r--r--js/src/jit-test/tests/debug/bug1107525.js9
-rw-r--r--js/src/jit-test/tests/debug/bug1107913.js7
-rw-r--r--js/src/jit-test/tests/debug/bug1108159.js12
-rw-r--r--js/src/jit-test/tests/debug/bug1108556.js10
-rw-r--r--js/src/jit-test/tests/debug/bug1109328.js8
-rw-r--r--js/src/jit-test/tests/debug/bug1109915.js17
-rw-r--r--js/src/jit-test/tests/debug/bug1109964.js10
-rw-r--r--js/src/jit-test/tests/debug/bug1111199.js17
-rw-r--r--js/src/jit-test/tests/debug/bug1114587.js26
-rw-r--r--js/src/jit-test/tests/debug/bug1116103.js11
-rw-r--r--js/src/jit-test/tests/debug/bug1118878.js11
-rw-r--r--js/src/jit-test/tests/debug/bug1121083.js15
-rw-r--r--js/src/jit-test/tests/debug/bug1130768.js12
-rw-r--r--js/src/jit-test/tests/debug/bug1133196.js16
-rw-r--r--js/src/jit-test/tests/debug/bug1147939.js8
-rw-r--r--js/src/jit-test/tests/debug/bug1148917.js14
-rw-r--r--js/src/jit-test/tests/debug/bug1160182.js13
-rw-r--r--js/src/jit-test/tests/debug/bug1161332.js16
-rw-r--r--js/src/jit-test/tests/debug/bug1188334.js18
-rw-r--r--js/src/jit-test/tests/debug/bug1191499.js17
-rw-r--r--js/src/jit-test/tests/debug/bug1216261.js12
-rw-r--r--js/src/jit-test/tests/debug/bug1219905.js11
-rw-r--r--js/src/jit-test/tests/debug/bug1221378.js11
-rw-r--r--js/src/jit-test/tests/debug/bug1232655.js5
-rw-r--r--js/src/jit-test/tests/debug/bug1240546.js9
-rw-r--r--js/src/jit-test/tests/debug/bug1240803.js20
-rw-r--r--js/src/jit-test/tests/debug/bug1242111.js8
-rw-r--r--js/src/jit-test/tests/debug/bug1242798.js14
-rw-r--r--js/src/jit-test/tests/debug/bug1245862.js22
-rw-r--r--js/src/jit-test/tests/debug/bug1246605.js13
-rw-r--r--js/src/jit-test/tests/debug/bug1251919.js10
-rw-r--r--js/src/jit-test/tests/debug/bug1252453.js21
-rw-r--r--js/src/jit-test/tests/debug/bug1252464.js15
-rw-r--r--js/src/jit-test/tests/debug/bug1253246.js5
-rw-r--r--js/src/jit-test/tests/debug/bug1254123.js14
-rw-r--r--js/src/jit-test/tests/debug/bug1254190.js12
-rw-r--r--js/src/jit-test/tests/debug/bug1254578.js19
-rw-r--r--js/src/jit-test/tests/debug/bug1257045.js10
-rw-r--r--js/src/jit-test/tests/debug/bug1263899.js30
-rw-r--r--js/src/jit-test/tests/debug/bug1264961.js23
-rw-r--r--js/src/jit-test/tests/debug/bug1266434.js8
-rw-r--r--js/src/jit-test/tests/debug/bug1272908.js19
-rw-r--r--js/src/jit-test/tests/debug/bug1275001.js30
-rw-r--r--js/src/jit-test/tests/debug/bug1282741.js28
-rw-r--r--js/src/jit-test/tests/debug/bug1299121.js10
-rw-r--r--js/src/jit-test/tests/debug/bug1300517.js12
-rw-r--r--js/src/jit-test/tests/debug/bug1300528.js33
-rw-r--r--js/src/jit-test/tests/debug/bug1302432.js11
-rw-r--r--js/src/jit-test/tests/debug/bug1304553.js21
-rw-r--r--js/src/jit-test/tests/debug/bug1308578.js10
-rw-r--r--js/src/jit-test/tests/debug/bug1330339.js36
-rw-r--r--js/src/jit-test/tests/debug/bug1330489-sps.js44
-rw-r--r--js/src/jit-test/tests/debug/bug1330489.js36
-rw-r--r--js/src/jit-test/tests/debug/bug1330491.js21
-rw-r--r--js/src/jit-test/tests/debug/bug1331064.js19
-rw-r--r--js/src/jit-test/tests/debug/bug1331592.js28
-rw-r--r--js/src/jit-test/tests/debug/bug1332493.js14
-rw-r--r--js/src/jit-test/tests/debug/bug1343579.js26
-rw-r--r--js/src/jit-test/tests/debug/bug1351059.js22
-rw-r--r--js/src/jit-test/tests/debug/bug1353356.js65
-rw-r--r--js/src/jit-test/tests/debug/bug1363233.js14
-rw-r--r--js/src/jit-test/tests/debug/bug1368736.js19
-rw-r--r--js/src/jit-test/tests/debug/bug1370905.js15
-rw-r--r--js/src/jit-test/tests/debug/bug1375447.js18
-rw-r--r--js/src/jit-test/tests/debug/bug1385843.js21
-rw-r--r--js/src/jit-test/tests/debug/bug1397049.js40
-rw-r--r--js/src/jit-test/tests/debug/bug1397385.js16
-rw-r--r--js/src/jit-test/tests/debug/bug1404710.js10
-rw-r--r--js/src/jit-test/tests/debug/bug1406437.js5
-rw-r--r--js/src/jit-test/tests/debug/bug1417961.js31
-rw-r--r--js/src/jit-test/tests/debug/bug1432764.js16
-rw-r--r--js/src/jit-test/tests/debug/bug1434391.js8
-rw-r--r--js/src/jit-test/tests/debug/bug1437537.js18
-rw-r--r--js/src/jit-test/tests/debug/bug1480390.js35
-rw-r--r--js/src/jit-test/tests/debug/bug1488163.js17
-rw-r--r--js/src/jit-test/tests/debug/bug1516958.js3
-rw-r--r--js/src/jit-test/tests/debug/bug1557343-2.js30
-rw-r--r--js/src/jit-test/tests/debug/bug1557343.js25
-rw-r--r--js/src/jit-test/tests/debug/bug1563051.js11
-rw-r--r--js/src/jit-test/tests/debug/bug1586762.js9
-rw-r--r--js/src/jit-test/tests/debug/bug1591342.js13
-rw-r--r--js/src/jit-test/tests/debug/bug1602392.js19
-rw-r--r--js/src/jit-test/tests/debug/bug1644699-terminated-generator.js79
-rw-r--r--js/src/jit-test/tests/debug/bug1647309.js18
-rw-r--r--js/src/jit-test/tests/debug/bug1675755-forceLexicalInitializationByName.js38
-rw-r--r--js/src/jit-test/tests/debug/bug1684821.js26
-rw-r--r--js/src/jit-test/tests/debug/bug911065.js34
-rw-r--r--js/src/jit-test/tests/debug/bug967039.js6
-rw-r--r--js/src/jit-test/tests/debug/bug973566.js7
-rw-r--r--js/src/jit-test/tests/debug/bug980585.js10
-rw-r--r--js/src/jit-test/tests/debug/bug999655.js11
-rw-r--r--js/src/jit-test/tests/debug/class-01.js20
-rw-r--r--js/src/jit-test/tests/debug/class-02.js20
-rw-r--r--js/src/jit-test/tests/debug/class-03.js23
-rw-r--r--js/src/jit-test/tests/debug/class-04.js22
-rw-r--r--js/src/jit-test/tests/debug/class-05.js31
-rw-r--r--js/src/jit-test/tests/debug/class-06.js22
-rw-r--r--js/src/jit-test/tests/debug/class-07.js21
-rw-r--r--js/src/jit-test/tests/debug/class-08.js13
-rw-r--r--js/src/jit-test/tests/debug/class-default-constructor-01.js34
-rw-r--r--js/src/jit-test/tests/debug/clear-old-analyses-01.js38
-rw-r--r--js/src/jit-test/tests/debug/clear-old-analyses-02.js39
-rw-r--r--js/src/jit-test/tests/debug/dispatch-01.js22
-rw-r--r--js/src/jit-test/tests/debug/execution-observability-01.js22
-rw-r--r--js/src/jit-test/tests/debug/execution-observability-02.js15
-rw-r--r--js/src/jit-test/tests/debug/execution-observability-03.js17
-rw-r--r--js/src/jit-test/tests/debug/execution-observability-04.js21
-rw-r--r--js/src/jit-test/tests/debug/execution-observability-05.js23
-rw-r--r--js/src/jit-test/tests/debug/execution-observability-06.js24
-rw-r--r--js/src/jit-test/tests/debug/gc-01.js20
-rw-r--r--js/src/jit-test/tests/debug/gc-02.js28
-rw-r--r--js/src/jit-test/tests/debug/gc-03.js24
-rw-r--r--js/src/jit-test/tests/debug/gc-04.js25
-rw-r--r--js/src/jit-test/tests/debug/gc-05.js41
-rw-r--r--js/src/jit-test/tests/debug/gc-06.js6
-rw-r--r--js/src/jit-test/tests/debug/gc-07.js9
-rw-r--r--js/src/jit-test/tests/debug/gc-08.js22
-rw-r--r--js/src/jit-test/tests/debug/gc-09.2.js16
-rw-r--r--js/src/jit-test/tests/debug/gc-09.js15
-rw-r--r--js/src/jit-test/tests/debug/gc-10.js28
-rw-r--r--js/src/jit-test/tests/debug/gc-compartment-01.js6
-rw-r--r--js/src/jit-test/tests/debug/gc-compartment-02.js13
-rw-r--r--js/src/jit-test/tests/debug/initarrayelem-hole-value.js12
-rw-r--r--js/src/jit-test/tests/debug/inspect-wrapped-promise.js87
-rw-r--r--js/src/jit-test/tests/debug/isAsyncFunction-isGeneratorFunction.js57
-rw-r--r--js/src/jit-test/tests/debug/isError.js19
-rw-r--r--js/src/jit-test/tests/debug/job-queue-01.js122
-rw-r--r--js/src/jit-test/tests/debug/job-queue-02.js82
-rw-r--r--js/src/jit-test/tests/debug/job-queue-03.js173
-rw-r--r--js/src/jit-test/tests/debug/job-queue-04.js24
-rw-r--r--js/src/jit-test/tests/debug/makeGlobalObjectReference-01.js26
-rw-r--r--js/src/jit-test/tests/debug/makeGlobalObjectReference-02.js13
-rw-r--r--js/src/jit-test/tests/debug/makeGlobalObjectReference-03.js8
-rw-r--r--js/src/jit-test/tests/debug/noExecute-01.js29
-rw-r--r--js/src/jit-test/tests/debug/noExecute-02.js39
-rw-r--r--js/src/jit-test/tests/debug/noExecute-03.js28
-rw-r--r--js/src/jit-test/tests/debug/noExecute-04.js39
-rw-r--r--js/src/jit-test/tests/debug/noExecute-05.js43
-rw-r--r--js/src/jit-test/tests/debug/noExecute-06.js81
-rw-r--r--js/src/jit-test/tests/debug/noExecute-07.js36
-rw-r--r--js/src/jit-test/tests/debug/onDebuggerStatement-01.js7
-rw-r--r--js/src/jit-test/tests/debug/onDebuggerStatement-02.js22
-rw-r--r--js/src/jit-test/tests/debug/onDebuggerStatement-03.js13
-rw-r--r--js/src/jit-test/tests/debug/onDebuggerStatement-04.js10
-rw-r--r--js/src/jit-test/tests/debug/onDebuggerStatement-05.js22
-rw-r--r--js/src/jit-test/tests/debug/onDebuggerStatement-async-generator-resumption-01.js60
-rw-r--r--js/src/jit-test/tests/debug/onDebuggerStatement-async-resumption-01.js34
-rw-r--r--js/src/jit-test/tests/debug/onDebuggerStatement-async-resumption-02.js31
-rw-r--r--js/src/jit-test/tests/debug/onDebuggerStatement-generator-resumption-01.js32
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-01.js29
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-02.js22
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-03.js23
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-05.js15
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-06.js19
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-07.js15
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-async-01.js32
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-async-resumption-01.js35
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-async-resumption-02.js35
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-async-resumption-03.js29
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-async-resumption-04.js36
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-async-resumption-05.js41
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-async-resumption-06.js49
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-async-resumption-07.js62
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-async-resumption-08.js53
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-async-resumption-09.js33
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-async-resumption-10.js24
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-async-resumption-11.js28
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-async-resumption-12.js28
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-async-resumption-13.js21
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-async-tryskipawait-01.js41
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-async-tryskipawait-02.js41
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-async-tryskipawait-03.js42
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-generator-01.js82
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-generator-02.js27
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-generator-03.js25
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-generator-04.js44
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-generator-05.js34
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-generator-06.js24
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-generator-07.js23
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-generator-08.js17
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-generator-09.js17
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-generator-10.js17
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-generator-resumption-01.js36
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-generator-resumption-02.js39
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-generator-resumption-03.js35
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-generator-resumption-04.js34
-rw-r--r--js/src/jit-test/tests/debug/onEnterFrame-generator-resumption-05.js22
-rw-r--r--js/src/jit-test/tests/debug/onExceptionUnwind-01.js24
-rw-r--r--js/src/jit-test/tests/debug/onExceptionUnwind-02.js47
-rw-r--r--js/src/jit-test/tests/debug/onExceptionUnwind-03.js57
-rw-r--r--js/src/jit-test/tests/debug/onExceptionUnwind-04.js17
-rw-r--r--js/src/jit-test/tests/debug/onExceptionUnwind-05.js12
-rw-r--r--js/src/jit-test/tests/debug/onExceptionUnwind-06.js13
-rw-r--r--js/src/jit-test/tests/debug/onExceptionUnwind-07.js15
-rw-r--r--js/src/jit-test/tests/debug/onExceptionUnwind-08.js18
-rw-r--r--js/src/jit-test/tests/debug/onExceptionUnwind-09.js15
-rw-r--r--js/src/jit-test/tests/debug/onExceptionUnwind-10.js16
-rw-r--r--js/src/jit-test/tests/debug/onExceptionUnwind-12.js14
-rw-r--r--js/src/jit-test/tests/debug/onExceptionUnwind-13.js16
-rw-r--r--js/src/jit-test/tests/debug/onExceptionUnwind-14.js23
-rw-r--r--js/src/jit-test/tests/debug/onExceptionUnwind-15.js25
-rw-r--r--js/src/jit-test/tests/debug/onExceptionUnwind-generators-01.js39
-rw-r--r--js/src/jit-test/tests/debug/onExceptionUnwind-resumption-01.js9
-rw-r--r--js/src/jit-test/tests/debug/onExceptionUnwind-resumption-02.js10
-rw-r--r--js/src/jit-test/tests/debug/onExceptionUnwind-resumption-03.js11
-rw-r--r--js/src/jit-test/tests/debug/onExceptionUnwind-resumption-04.js17
-rw-r--r--js/src/jit-test/tests/debug/onExceptionUnwind-resumption-05.js32
-rw-r--r--js/src/jit-test/tests/debug/onExceptionUnwind-resumption-async-02.js31
-rw-r--r--js/src/jit-test/tests/debug/onExceptionUnwind-resumption-async.js35
-rw-r--r--js/src/jit-test/tests/debug/onExceptionUnwind-resumption-generator.js52
-rw-r--r--js/src/jit-test/tests/debug/onNewScript-01.js44
-rw-r--r--js/src/jit-test/tests/debug/onNewScript-02.js62
-rw-r--r--js/src/jit-test/tests/debug/onNewScript-03.js7
-rw-r--r--js/src/jit-test/tests/debug/onNewScript-CloneAndExecuteScript.js28
-rw-r--r--js/src/jit-test/tests/debug/onNewScript-ExecuteInGlobalAndReturnScope.js32
-rw-r--r--js/src/jit-test/tests/debug/onNewScript-off-main-thread-01.js17
-rw-r--r--js/src/jit-test/tests/debug/onNewScript-off-main-thread-02.js12
-rw-r--r--js/src/jit-test/tests/debug/onNewScript-wasm-01.js33
-rw-r--r--js/src/jit-test/tests/debug/onNewScript-wasm-02.js41
-rw-r--r--js/src/jit-test/tests/debug/optimized-out-01.js47
-rw-r--r--js/src/jit-test/tests/debug/optimized-out-02.js38
-rw-r--r--js/src/jit-test/tests/debug/optimized-out-03.js31
-rw-r--r--js/src/jit-test/tests/debug/optimized-out-arrow-this.js38
-rw-r--r--js/src/jit-test/tests/debug/private-methods-eval-in-frame.js25
-rw-r--r--js/src/jit-test/tests/debug/prologueFailure-01.js32
-rw-r--r--js/src/jit-test/tests/debug/prologueFailure-02.js49
-rw-r--r--js/src/jit-test/tests/debug/prologueFailure-03.js26
-rw-r--r--js/src/jit-test/tests/debug/relazify-debugee-script-01.js14
-rw-r--r--js/src/jit-test/tests/debug/resumption-01.js12
-rw-r--r--js/src/jit-test/tests/debug/resumption-02.js9
-rw-r--r--js/src/jit-test/tests/debug/resumption-03.js35
-rw-r--r--js/src/jit-test/tests/debug/resumption-05.js35
-rw-r--r--js/src/jit-test/tests/debug/resumption-07.js34
-rw-r--r--js/src/jit-test/tests/debug/resumption-08.js93
-rw-r--r--js/src/jit-test/tests/debug/resumption-09.js41
-rw-r--r--js/src/jit-test/tests/debug/resumption-error-01.js7
-rw-r--r--js/src/jit-test/tests/debug/resumption-error-02.js16
-rw-r--r--js/src/jit-test/tests/debug/save-queue-resets-draining.js18
-rw-r--r--js/src/jit-test/tests/debug/setter-argc.js52
-rw-r--r--js/src/jit-test/tests/debug/surfaces-01.js17
-rw-r--r--js/src/jit-test/tests/debug/surfaces-02.js31
-rw-r--r--js/src/jit-test/tests/debug/surfaces-03.js19
-rw-r--r--js/src/jit-test/tests/debug/surfaces-offsets.js37
-rw-r--r--js/src/jit-test/tests/debug/testEarlyReturnOnCall.js24
-rw-r--r--js/src/jit-test/tests/debug/uncaughtExceptionHook-01.js19
-rw-r--r--js/src/jit-test/tests/debug/uncaughtExceptionHook-02.js12
-rw-r--r--js/src/jit-test/tests/debug/uncaughtExceptionHook-03.js34
-rw-r--r--js/src/jit-test/tests/debug/uncaughtExceptionHook-resumption-01.js25
-rw-r--r--js/src/jit-test/tests/debug/uncaughtExceptionHook-resumption-02.js25
-rw-r--r--js/src/jit-test/tests/debug/uncaughtExceptionHook-resumption-03.js12
-rw-r--r--js/src/jit-test/tests/debug/wasm-01.js32
-rw-r--r--js/src/jit-test/tests/debug/wasm-02.js21
-rw-r--r--js/src/jit-test/tests/debug/wasm-03.js35
-rw-r--r--js/src/jit-test/tests/debug/wasm-04.js27
-rw-r--r--js/src/jit-test/tests/debug/wasm-06-onEnterFrame-null.js17
-rw-r--r--js/src/jit-test/tests/debug/wasm-06-onPop-null.js19
-rw-r--r--js/src/jit-test/tests/debug/wasm-06.js332
-rw-r--r--js/src/jit-test/tests/debug/wasm-07.js39
-rw-r--r--js/src/jit-test/tests/debug/wasm-08.js121
-rw-r--r--js/src/jit-test/tests/debug/wasm-09.js32
-rw-r--r--js/src/jit-test/tests/debug/wasm-10.js77
-rw-r--r--js/src/jit-test/tests/debug/wasm-11.js33
-rw-r--r--js/src/jit-test/tests/debug/wasm-12.js25
-rw-r--r--js/src/jit-test/tests/debug/wasm-13.js106
-rw-r--r--js/src/jit-test/tests/debug/wasm-binary-sources.js32
-rw-r--r--js/src/jit-test/tests/debug/wasm-breakpoint.js194
-rw-r--r--js/src/jit-test/tests/debug/wasm-get-return.js62
-rw-r--r--js/src/jit-test/tests/debug/wasm-getAllColumnOffsets.js41
-rw-r--r--js/src/jit-test/tests/debug/wasm-jseval.js45
-rw-r--r--js/src/jit-test/tests/debug/wasm-onExceptionUnwind-gc.js48
-rw-r--r--js/src/jit-test/tests/debug/wasm-responseurls.js40
-rw-r--r--js/src/jit-test/tests/debug/wasm-sourceMappingURL.js72
-rw-r--r--js/src/jit-test/tests/debug/wasm-step.js65
1029 files changed, 31966 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/debug/DebuggeeWouldRun-01.js b/js/src/jit-test/tests/debug/DebuggeeWouldRun-01.js
new file mode 100644
index 0000000000..315e3dcf4f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/DebuggeeWouldRun-01.js
@@ -0,0 +1,7 @@
+// Bug 1250190: Shouldn't crash. |jit-test| exitstatus: 3
+
+g = newGlobal();
+var dbg = Debugger(g)
+dbg.onNewPromise = () => g.makeFakePromise();
+g.makeFakePromise();
+
diff --git a/js/src/jit-test/tests/debug/DebuggeeWouldRun-02.js b/js/src/jit-test/tests/debug/DebuggeeWouldRun-02.js
new file mode 100644
index 0000000000..290effcb98
--- /dev/null
+++ b/js/src/jit-test/tests/debug/DebuggeeWouldRun-02.js
@@ -0,0 +1,7 @@
+// Bug 1250190: Shouldn't crash. |jit-test| exitstatus: 3
+
+var g = newGlobal();
+var dbg = Debugger(g)
+dbg.onNewGlobalObject = () => g.newGlobal();
+g.newGlobal();
+print("yo");
diff --git a/js/src/jit-test/tests/debug/DebuggeeWouldRun-03.js b/js/src/jit-test/tests/debug/DebuggeeWouldRun-03.js
new file mode 100644
index 0000000000..e6c522c485
--- /dev/null
+++ b/js/src/jit-test/tests/debug/DebuggeeWouldRun-03.js
@@ -0,0 +1,9 @@
+// Bug 1250190: Shouldn't crash. |jit-test| error: yadda
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+dbg.onNewGlobalObject = function () {
+ dbg.onNewGlobalObject = function () { throw "yadda"; };
+ newGlobal({newCompartment: true});
+}
+newGlobal({newCompartment: true});
diff --git a/js/src/jit-test/tests/debug/DebuggeeWouldRun-04.js b/js/src/jit-test/tests/debug/DebuggeeWouldRun-04.js
new file mode 100644
index 0000000000..684f3f8e7e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/DebuggeeWouldRun-04.js
@@ -0,0 +1,9 @@
+// Bug 1250190: Shouldn't crash. |jit-test| error: yadda
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+dbg.onNewScript = function () {
+ dbg.onNewScript = function () { throw "yadda"; };
+ g.Function("noodles;");
+}
+g.Function("poodles;");
diff --git a/js/src/jit-test/tests/debug/Debugger-add-Debugger-prototype.js b/js/src/jit-test/tests/debug/Debugger-add-Debugger-prototype.js
new file mode 100644
index 0000000000..4af5eee474
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-add-Debugger-prototype.js
@@ -0,0 +1,6 @@
+load(libdir + "asserts.js");
+
+assertThrowsInstanceOf(function () {
+ var dbg = new Debugger();
+ dbg.addDebuggee(Debugger.Object.prototype);
+}, TypeError);
diff --git a/js/src/jit-test/tests/debug/Debugger-adoptDebuggeeValue.js b/js/src/jit-test/tests/debug/Debugger-adoptDebuggeeValue.js
new file mode 100644
index 0000000000..4d10062acb
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-adoptDebuggeeValue.js
@@ -0,0 +1,39 @@
+// simplest possible test of Debugger.adoptDebuggeeValue
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+
+var dbg1 = new Debugger();
+var gDO1 = dbg1.addDebuggee(g);
+var obj1 = gDO1.executeInGlobal("({})").return;
+
+var dbg2 = Debugger(g);
+var gDO2 = dbg2.addDebuggee(g);
+var obj2 = gDO2.executeInGlobal("({})").return;
+
+assertThrowsInstanceOf(function () {
+ obj1.defineProperty("foo", {
+ configurable: true,
+ enumerable: true,
+ value: obj2,
+ writable: true
+ });
+}, Error);
+
+let obj3 = dbg1.adoptDebuggeeValue(obj2);
+
+obj1.defineProperty("foo", {
+ configurable: true,
+ enumerable: true,
+ value: obj3,
+ writable: true
+});
+
+assertThrowsInstanceOf(function () {
+ dbg1.adoptDebuggeeValue({});
+}, TypeError);
+
+assertThrowsInstanceOf(function () {
+ dbg1.adoptDebuggeeValue(Debugger.Object.prototype);
+}, TypeError);
diff --git a/js/src/jit-test/tests/debug/Debugger-adoptFrame.js b/js/src/jit-test/tests/debug/Debugger-adoptFrame.js
new file mode 100644
index 0000000000..f5a911fe6c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-adoptFrame.js
@@ -0,0 +1,81 @@
+// validate the common behavior of of Debugger.adoptFrame
+
+load(libdir + "asserts.js");
+
+const g = newGlobal({ newCompartment: true });
+
+const dbg1 = new Debugger();
+const gDO1 = dbg1.addDebuggee(g);
+
+let suspendedFrame;
+dbg1.onDebuggerStatement = function() {
+ // Test working with an onStack frame.
+ const frame1 = dbg1.getNewestFrame();
+
+ const dbg = new Debugger();
+ assertErrorMessage(
+ () => dbg.adoptFrame(frame1),
+ Error,
+ "Debugger.Frame's global is not a debuggee"
+ );
+
+ dbg.addDebuggee(g);
+
+ const frame2 = dbg.adoptFrame(frame1);
+
+ assertMatchingFrame(frame1, frame2);
+
+ suspendedFrame = frame1;
+};
+const generator = g.eval(`
+function* fn() {
+ debugger;
+ yield;
+}
+fn();
+`);
+generator.next();
+
+(function() {
+ // Test working with a suspended generator frame.
+ const dbg = new Debugger();
+ assertErrorMessage(
+ () => dbg.adoptFrame(suspendedFrame),
+ Error,
+ "Debugger.Frame's global is not a debuggee"
+ );
+
+ dbg.addDebuggee(g);
+
+ const frame2 = dbg.adoptFrame(suspendedFrame);
+
+ assertMatchingFrame(frame2, suspendedFrame);
+})();
+
+generator.next();
+const deadFrame = suspendedFrame;
+
+(function() {
+ // Test working with a dead frame.
+ const dbg = new Debugger();
+
+ // This doesn't throw because the dead frame doesn't have any
+ // debuggee-specific data associated with it anymore.
+ dbg.adoptFrame(deadFrame);
+
+ dbg.addDebuggee(g);
+
+ const frame2 = dbg.adoptFrame(deadFrame);
+
+ assertMatchingFrame(frame2, deadFrame);
+})();
+
+function assertMatchingFrame(frame1, frame2) {
+ assertEq(frame2.onStack, frame1.onStack);
+ assertEq(frame2.terminated, frame1.terminated);
+
+ if (!frame2.terminated) {
+ assertEq(frame2.type, frame1.type);
+ assertEq(frame2.offset, frame1.offset);
+ }
+}
diff --git a/js/src/jit-test/tests/debug/Debugger-allowUnobservedAsmJS-01.js b/js/src/jit-test/tests/debug/Debugger-allowUnobservedAsmJS-01.js
new file mode 100644
index 0000000000..51f9d033d5
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-allowUnobservedAsmJS-01.js
@@ -0,0 +1,26 @@
+load(libdir + "asm.js");
+
+var g = newGlobal({newCompartment: true});
+g.parent = this;
+g.eval("dbg = new Debugger(parent);");
+
+// Initial state is to inhibit asm.js.
+assertEq(g.dbg.allowUnobservedAsmJS, false);
+
+var asmFunStr = USE_ASM + 'function f() {} return f';
+
+// With asm.js inhibited, asm.js should fail with a type error about the
+// debugger being on.
+assertAsmTypeFail(asmFunStr);
+
+// With asm.js uninhibited, asm.js linking should work.
+g.dbg.allowUnobservedAsmJS = true;
+assertEq(asmLink(asmCompile(asmFunStr))(), undefined);
+
+// Toggling back should inhibit again.
+g.dbg.allowUnobservedAsmJS = false;
+assertAsmTypeFail(asmFunStr);
+
+// Removing the global should lift the inhibition.
+g.dbg.removeDebuggee(this);
+assertEq(asmLink(asmCompile(asmFunStr))(), undefined);
diff --git a/js/src/jit-test/tests/debug/Debugger-allowUnobservedAsmJS-02.js b/js/src/jit-test/tests/debug/Debugger-allowUnobservedAsmJS-02.js
new file mode 100644
index 0000000000..324de24004
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-allowUnobservedAsmJS-02.js
@@ -0,0 +1,25 @@
+// |jit-test| skip-if: helperThreadCount() === 0
+
+// Debugger.allowUnobservedAsmJS with off-thread parsing.
+
+load(libdir + "asm.js");
+
+
+var g = newGlobal({newCompartment: true});
+g.parent = this;
+g.eval("dbg = new Debugger(parent);");
+
+assertEq(g.dbg.allowUnobservedAsmJS, false);
+
+enableLastWarning();
+
+var asmFunStr = USE_ASM + 'function f() {} return f';
+offThreadCompileScript("(function() {" + asmFunStr + "})");
+runOffThreadScript();
+
+var msg = getLastWarning().message;
+assertEq(msg === "asm.js type error: Disabled by debugger" ||
+ msg === "asm.js type error: Disabled by lack of a JIT compiler" ||
+ msg === "asm.js type error: Disabled by 'asmjs' runtime option" ||
+ msg === "asm.js type error: Disabled by lack of compiler support",
+ true);
diff --git a/js/src/jit-test/tests/debug/Debugger-clearAllBreakpoints-01.js b/js/src/jit-test/tests/debug/Debugger-clearAllBreakpoints-01.js
new file mode 100644
index 0000000000..ea5489a699
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-clearAllBreakpoints-01.js
@@ -0,0 +1,29 @@
+// clearAllBreakpoints clears breakpoints for the current Debugger object only.
+
+var g = newGlobal({newCompartment: true});
+
+var hits = 0;
+function attach(i) {
+ var dbg = Debugger(g);
+ var handler = {
+ hit: function (frame) {
+ hits++;
+ dbg.clearAllBreakpoints();
+ }
+ };
+
+ dbg.onDebuggerStatement = function (frame) {
+ var s = frame.script;
+ var offs = s.getLineOffsets(g.line0 + 3);
+ for (var i = 0; i < offs.length; i++)
+ s.setBreakpoint(offs[i], handler);
+ };
+}
+for (var i = 0; i < 4; i++)
+ attach(i);
+
+g.eval("var line0 = Error().lineNumber;\n" +
+ "debugger;\n" + // line0 + 1
+ "for (var i = 0; i < 7; i++)\n" + // line0 + 2
+ " Math.sin(0);\n"); // line0 + 3
+assertEq(hits, 4);
diff --git a/js/src/jit-test/tests/debug/Debugger-clearAllBreakpoints-02.js b/js/src/jit-test/tests/debug/Debugger-clearAllBreakpoints-02.js
new file mode 100644
index 0000000000..a5394a633e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-clearAllBreakpoints-02.js
@@ -0,0 +1,31 @@
+// |jit-test| skip-if: !wasmDebuggingEnabled()
+// clearAllBreakpoints should clear breakpoints for WASM scripts.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+g.eval(`
+ var wasm = wasmTextToBinary(
+ '(module (func (nop) (nop)) (export "test" (func 0)))');
+ var m = new WebAssembly.Instance(new WebAssembly.Module(wasm));
+ var offsets = wasmCodeOffsets(wasm);
+`);
+var wasmScript = dbg.findScripts().filter(s => s.format == 'wasm')[0];
+
+let count = 0;
+wasmScript.setBreakpoint(g.offsets[0], {
+ hit: () => {
+ count++;
+ },
+});
+
+g.m.exports.test();
+assertEq(count, 1);
+
+g.m.exports.test();
+assertEq(count, 2);
+
+dbg.clearAllBreakpoints();
+
+g.m.exports.test();
+assertEq(count, 2);
diff --git a/js/src/jit-test/tests/debug/Debugger-ctor-01.js b/js/src/jit-test/tests/debug/Debugger-ctor-01.js
new file mode 100644
index 0000000000..72a5409bb0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-ctor-01.js
@@ -0,0 +1,21 @@
+load(libdir + 'asserts.js');
+
+// Debugger rejects arguments that aren't cross-compartment wrappers.
+assertThrowsInstanceOf(function () { Debugger(null); }, TypeError);
+assertThrowsInstanceOf(function () { Debugger(true); }, TypeError);
+assertThrowsInstanceOf(function () { Debugger(42); }, TypeError);
+assertThrowsInstanceOf(function () { Debugger("bad"); }, TypeError);
+assertThrowsInstanceOf(function () { Debugger(function () {}); }, TypeError);
+assertThrowsInstanceOf(function () { Debugger(this); }, TypeError);
+assertThrowsInstanceOf(function () { new Debugger(null); }, TypeError);
+assertThrowsInstanceOf(function () { new Debugger(true); }, TypeError);
+assertThrowsInstanceOf(function () { new Debugger(42); }, TypeError);
+assertThrowsInstanceOf(function () { new Debugger("bad"); }, TypeError);
+assertThrowsInstanceOf(function () { new Debugger(function () {}); }, TypeError);
+assertThrowsInstanceOf(function () { new Debugger(this); }, TypeError);
+
+// From the main compartment, creating a Debugger on a sandbox compartment.
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+assertEq(dbg instanceof Debugger, true);
+assertEq(Object.getPrototypeOf(dbg), Debugger.prototype);
diff --git a/js/src/jit-test/tests/debug/Debugger-ctor-02.js b/js/src/jit-test/tests/debug/Debugger-ctor-02.js
new file mode 100644
index 0000000000..f1e4afdba9
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-ctor-02.js
@@ -0,0 +1,13 @@
+// Test creating a Debugger in a sandbox, debugging the initial global.
+
+load(libdir + 'asserts.js');
+
+var g = newGlobal({newCompartment: true});
+g.debuggeeGlobal = this;
+g.eval("var dbg = new Debugger(debuggeeGlobal);");
+assertEq(g.eval("dbg instanceof Debugger"), true);
+
+// The Debugger constructor from this compartment will not accept as its argument
+// an Object from this compartment. Shenanigans won't fool the membrane.
+g.parent = this;
+assertThrowsInstanceOf(function () { g.eval("parent.Debugger(parent.Object())"); }, TypeError);
diff --git a/js/src/jit-test/tests/debug/Debugger-ctor-03.js b/js/src/jit-test/tests/debug/Debugger-ctor-03.js
new file mode 100644
index 0000000000..f2245e7c9a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-ctor-03.js
@@ -0,0 +1,19 @@
+// If the debuggee cannot be put into debug mode, throw.
+
+// Run this test only if this compartment can't be put into debug mode.
+var canEnable = true;
+if (typeof setDebugMode === 'function') {
+ try {
+ setDebugMode(true);
+ } catch (exc) {
+ canEnable = false;
+ }
+}
+
+if (!canEnable) {
+ var g = newGlobal();
+ g.libdir = libdir;
+ g.eval("load(libdir + 'asserts.js');");
+ g.parent = this;
+ g.eval("assertThrowsInstanceOf(function () { new Debugger(parent); }, Error);");
+}
diff --git a/js/src/jit-test/tests/debug/Debugger-ctor-04.js b/js/src/jit-test/tests/debug/Debugger-ctor-04.js
new file mode 100644
index 0000000000..4ecc619bd4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-ctor-04.js
@@ -0,0 +1,5 @@
+// Repeated Debugger() arguments are ignored.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g, g, g);
+assertEq(dbg.getDebuggees().length, 1);
diff --git a/js/src/jit-test/tests/debug/Debugger-ctor-05.js b/js/src/jit-test/tests/debug/Debugger-ctor-05.js
new file mode 100644
index 0000000000..3b5e3127c7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-ctor-05.js
@@ -0,0 +1,8 @@
+// Redundant non-global Debugger() arguments are ignored.
+
+var g = newGlobal({newCompartment: true});
+g.eval("var a = {}, b = {};");
+var dbg = Debugger(g.a, g.b);
+var arr = dbg.getDebuggees();
+assertEq(arr.length, 1);
+assertEq(arr[0], dbg.addDebuggee(g));
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-01.js b/js/src/jit-test/tests/debug/Debugger-debuggees-01.js
new file mode 100644
index 0000000000..b8a22c029c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-01.js
@@ -0,0 +1,5 @@
+// A Debugger object created with no argument initially has no debuggees.
+var dbg = new Debugger;
+var debuggees = dbg.getDebuggees();
+assertEq(Array.isArray(debuggees), true);
+assertEq(debuggees.length, 0);
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-02.js b/js/src/jit-test/tests/debug/Debugger-debuggees-02.js
new file mode 100644
index 0000000000..cc220edba3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-02.js
@@ -0,0 +1,10 @@
+// The array returned by getDebuggees is just a snapshot, not live.
+var dbg = new Debugger;
+var a1 = dbg.getDebuggees();
+var g = newGlobal({newCompartment: true});
+var gw = dbg.addDebuggee(g);
+assertEq(gw instanceof Debugger.Object, true);
+var a2 = dbg.getDebuggees();
+assertEq(a2.length, 1);
+assertEq(a2[0], gw);
+assertEq(a1.length, 0);
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-03.js b/js/src/jit-test/tests/debug/Debugger-debuggees-03.js
new file mode 100644
index 0000000000..a792eed138
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-03.js
@@ -0,0 +1,34 @@
+// Debugger hooks fire based on debuggees.
+
+var g1 = newGlobal({newCompartment: true});
+g1.eval("var g2 = newGlobal('same-compartment')");
+var g2 = g1.g2;
+g1.eval("function f() { debugger; g2.g(); }");
+g2.eval("function g() { debugger; }");
+
+var log;
+var dbg = new Debugger;
+dbg.onDebuggerStatement = function (frame) { log += frame.callee.name; };
+
+// No debuggees: onDebuggerStatement is not called.
+log = '';
+g1.f();
+assertEq(log, '');
+
+// Add a debuggee and check that the handler is called.
+var g1w = dbg.addDebuggee(g1);
+log = '';
+g1.f();
+assertEq(log, 'f');
+
+// Two debuggees, two onDebuggerStatement calls.
+dbg.addDebuggee(g2);
+log = '';
+g1.f();
+assertEq(log, 'fg');
+
+// After a debuggee is removed, it no longer calls hooks.
+assertEq(dbg.removeDebuggee(g1w), undefined);
+log = '';
+g1.f();
+assertEq(log, 'g');
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-04.js b/js/src/jit-test/tests/debug/Debugger-debuggees-04.js
new file mode 100644
index 0000000000..9f4714f86e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-04.js
@@ -0,0 +1,26 @@
+// hasDebuggee tests.
+
+var g1 = newGlobal({newCompartment: true}), g1w;
+g1.eval("var g2 = newGlobal('same-compartment')");
+var g2 = g1.g2;
+var g1w, g2w;
+
+var dbg = new Debugger;
+function checkHas(hasg1, hasg2) {
+ assertEq(dbg.hasDebuggee(g1), hasg1);
+ if (typeof g1w === 'object')
+ assertEq(dbg.hasDebuggee(g1w), hasg1);
+ assertEq(dbg.hasDebuggee(g2), hasg2);
+ if (typeof g2w === 'object')
+ assertEq(dbg.hasDebuggee(g2w), hasg2);
+}
+
+checkHas(false, false);
+g1w = dbg.addDebuggee(g1);
+checkHas(true, false);
+g2w = dbg.addDebuggee(g2);
+checkHas(true, true);
+dbg.removeDebuggee(g1w);
+checkHas(false, true);
+dbg.removeDebuggee(g2);
+checkHas(false, false);
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-05.js b/js/src/jit-test/tests/debug/Debugger-debuggees-05.js
new file mode 100644
index 0000000000..14ed9337f3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-05.js
@@ -0,0 +1,8 @@
+// addDebuggee returns different Debugger.Object wrappers for different Debugger objects.
+
+var g = newGlobal({newCompartment: true});
+var dbg1 = new Debugger;
+var gw1 = dbg1.addDebuggee(g);
+var dbg2 = new Debugger;
+var gw2 = dbg2.addDebuggee(g);
+assertEq(gw1 !== gw2, true);
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-06.js b/js/src/jit-test/tests/debug/Debugger-debuggees-06.js
new file mode 100644
index 0000000000..264f32c980
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-06.js
@@ -0,0 +1,27 @@
+// {has,add,remove}Debuggee throw a TypeError if the argument is invalid.
+
+load(libdir + "asserts.js");
+
+var dbg = new Debugger;
+
+function check(val) {
+ assertThrowsInstanceOf(function () { dbg.hasDebuggee(val); }, TypeError);
+ assertThrowsInstanceOf(function () { dbg.addDebuggee(val); }, TypeError);
+ assertThrowsInstanceOf(function () { dbg.removeDebuggee(val); }, TypeError);
+}
+
+// Primitive values are invalid.
+check(undefined);
+check(null);
+check(false);
+check(1);
+check(NaN);
+check("ok");
+check(Symbol("ok"));
+
+// A Debugger.Object that belongs to a different Debugger object is invalid.
+var g = newGlobal({newCompartment: true});
+var dbg2 = new Debugger;
+var w = dbg2.addDebuggee(g);
+assertEq(w instanceof Debugger.Object, true);
+check(w);
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-08.js b/js/src/jit-test/tests/debug/Debugger-debuggees-08.js
new file mode 100644
index 0000000000..4793e49eaf
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-08.js
@@ -0,0 +1,25 @@
+// Adding a debuggee more than once is redundant.
+
+var dbg = new Debugger;
+var g = newGlobal({newCompartment: true});
+var w = dbg.addDebuggee(g);
+assertEq(w instanceof Debugger.Object, true);
+
+function usual() {
+ assertEq(dbg.hasDebuggee(g), true);
+ assertEq(dbg.hasDebuggee(w), true);
+ var arr = dbg.getDebuggees();
+ assertEq(arr.length, 1);
+ assertEq(arr[0], w);
+}
+
+usual();
+assertEq(dbg.addDebuggee(g), w);
+usual();
+assertEq(dbg.addDebuggee(w), w);
+usual();
+
+// Removing the debuggee once is enough.
+assertEq(dbg.removeDebuggee(g), undefined);
+assertEq(dbg.hasDebuggee(g), false);
+assertEq(dbg.getDebuggees().length, 0);
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-09.js b/js/src/jit-test/tests/debug/Debugger-debuggees-09.js
new file mode 100644
index 0000000000..bb7fb046a7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-09.js
@@ -0,0 +1,21 @@
+// If hasDebuggee(x) is false, removeDebuggee(x) does nothing.
+
+var dbg = new Debugger;
+
+function check(obj) {
+ // If obj is something we could never debug, hasDebuggee(obj) is false.
+ assertEq(dbg.hasDebuggee(obj), false);
+
+ // If hasDebuggee(x) is false, removeDebuggee(x) does nothing.
+ assertEq(dbg.removeDebuggee(obj), undefined);
+}
+
+// global objects which happen not to be debuggees at the moment
+var g1 = newGlobal('same-compartment');
+check(g1);
+
+// objects in a compartment that is already debugging us
+var g2 = newGlobal({newCompartment: true});
+g2.parent = this;
+g2.eval("var dbg = new Debugger(parent);");
+check(g2);
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-10.js b/js/src/jit-test/tests/debug/Debugger-debuggees-10.js
new file mode 100644
index 0000000000..7e5c369bc0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-10.js
@@ -0,0 +1,18 @@
+// Allow diamonds in the graph of the compartment "debugs" relation.
+var program = newGlobal({newCompartment: true});
+var d1 = newGlobal({newCompartment: true});
+d1.top = this;
+var d2 = newGlobal({newCompartment: true});
+d2.top = this;
+var dbg = new Debugger(d1, d2);
+d1.eval("var dbg = new Debugger(top.program)");
+d2.eval("var dbg = new Debugger(top.program)");
+
+// mess with the edges a little bit -- all this should be fine, no cycles
+d1.dbg.removeDebuggee(program);
+d1.dbg.addDebuggee(program);
+dbg.addDebuggee(program);
+d1.dbg.addDebuggee(d2);
+dbg.removeDebuggee(d2);
+dbg.addDebuggee(d2);
+
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-11.js b/js/src/jit-test/tests/debug/Debugger-debuggees-11.js
new file mode 100644
index 0000000000..d1ca587e2b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-11.js
@@ -0,0 +1,22 @@
+// Don't allow cycles in the graph of the compartment "debugs" relation.
+
+load(libdir + "asserts.js");
+
+// trivial cycles
+var dbg = new Debugger;
+assertThrowsInstanceOf(function () { dbg.addDebuggee(this); }, TypeError);
+assertThrowsInstanceOf(function () { new Debugger(this); }, TypeError);
+
+// cycles of length 2
+var d1 = newGlobal({newCompartment: true});
+d1.top = this;
+d1.eval("var dbg = new Debugger(top)");
+assertThrowsInstanceOf(function () { dbg.addDebuggee(d1); }, TypeError);
+assertThrowsInstanceOf(function () { new Debugger(d1); }, TypeError);
+
+// cycles of length 3
+var d2 = newGlobal({newCompartment: true});
+d2.top = this;
+d2.eval("var dbg = new Debugger(top.d1)");
+assertThrowsInstanceOf(function () { dbg.addDebuggee(d2); }, TypeError);
+assertThrowsInstanceOf(function () { new Debugger(d2); }, TypeError);
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-12.js b/js/src/jit-test/tests/debug/Debugger-debuggees-12.js
new file mode 100644
index 0000000000..b77e9cff59
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-12.js
@@ -0,0 +1,10 @@
+// Events in a non-debuggee are ignored, even if a debuggee is in the same compartment.
+var g1 = newGlobal({newCompartment: true});
+var g2 = g1.eval("newGlobal('same-compartment')");
+var dbg = new Debugger(g1);
+var hits = 0;
+dbg.onDebuggerStatement = function () { hits++; };
+g1.eval("debugger;");
+assertEq(hits, 1);
+g2.eval("debugger;");
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-13.js b/js/src/jit-test/tests/debug/Debugger-debuggees-13.js
new file mode 100644
index 0000000000..6030bda3cc
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-13.js
@@ -0,0 +1,9 @@
+// Removing a debuggee does not detach the debugger from a compartment if another debuggee is in it.
+var g1 = newGlobal({newCompartment: true});
+var g2 = g1.eval("newGlobal('same-compartment')");
+var dbg = new Debugger(g1, g2);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) { hits++; };
+dbg.removeDebuggee(g1);
+g2.eval("debugger;");
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-14.js b/js/src/jit-test/tests/debug/Debugger-debuggees-14.js
new file mode 100644
index 0000000000..491a116c09
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-14.js
@@ -0,0 +1,8 @@
+// Adding a debuggee in a compartment that is already in debug mode works
+// even if a script from that compartment is on the stack.
+
+var g = newGlobal({newCompartment: true});
+var dbg1 = Debugger(g);
+var dbg2 = Debugger();
+g.parent = this;
+g.eval("parent.dbg2.addDebuggee(this);");
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-15.js b/js/src/jit-test/tests/debug/Debugger-debuggees-15.js
new file mode 100644
index 0000000000..d20972e1ce
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-15.js
@@ -0,0 +1,7 @@
+// Debugger mode can be disabled for a compartment even if it has scripts running.
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+g.parent = this;
+var n = 2;
+g.eval("parent.dbg.removeDebuggee(this); parent.n += 2");
+assertEq(n, 4);
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-16.js b/js/src/jit-test/tests/debug/Debugger-debuggees-16.js
new file mode 100644
index 0000000000..eebe99ba96
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-16.js
@@ -0,0 +1,30 @@
+// GC can turn off debug mode in a compartment.
+
+var dbgs = [];
+var nonDebugGlobals = [];
+var f = gc;
+for (var i = 0; i < 4; i++) {
+ // Create two globals, one debuggee.
+ var g1 = newGlobal({newCompartment: true});
+ var g2 = g1.eval("newGlobal('same-compartment')");
+ var dbg = Debugger(g1);
+ dbg.onDebuggerStatement = function () {};
+
+ // Thread a chain of functions through the non-debuggee globals.
+ g2.eval("function f() { return g() + 1; }");
+ g2.g = f;
+ f = g2.f;
+
+ // Root the Debugger objects and non-debuggee globals.
+ dbgs[i] = dbg;
+ nonDebugGlobals[i] = g2;
+}
+
+// Call the chain of functions. At the end of the chain is gc. This will
+// collect (some or all of) the debuggee globals, leaving non-debuggee
+// globals. It should disable debug mode in those debuggee compartments.
+nonDebugGlobals[nonDebugGlobals.length - 1].f();
+
+gc();
+nonDebugGlobals[0].g = function () { return 0; }
+assertEq(nonDebugGlobals[nonDebugGlobals.length - 1].f(), nonDebugGlobals.length);
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-17.js b/js/src/jit-test/tests/debug/Debugger-debuggees-17.js
new file mode 100644
index 0000000000..bb1ec664e5
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-17.js
@@ -0,0 +1,26 @@
+// addDebuggee, hasDebuggee, and removeDebuggee all throw if presented with
+// objects that are not valid global object designators.
+
+load(libdir + 'asserts.js');
+
+var dbg = new Debugger;
+
+function check(bad) {
+ print("check(" + JSON.stringify(bad) + ")");
+ assertThrowsInstanceOf(function () { dbg.addDebuggee(bad); }, TypeError);
+ assertEq(dbg.getDebuggees().length, 0);
+ assertThrowsInstanceOf(function () { dbg.hasDebuggee(bad); }, TypeError);
+ assertThrowsInstanceOf(function () { dbg.removeDebuggee(bad); }, TypeError);
+}
+
+var g = newGlobal({newCompartment: true});
+check(g.Object());
+check(g.Object);
+check(g.Function(""));
+
+// A Debugger.Object belonging to a different Debugger is not a valid way
+// to designate a global, even if its referent is a global.
+var g2 = newGlobal({newCompartment: true});
+var dbg2 = new Debugger;
+var d2g2 = dbg2.addDebuggee(g2);
+check(d2g2);
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-18.js b/js/src/jit-test/tests/debug/Debugger-debuggees-18.js
new file mode 100644
index 0000000000..a4aa94ed54
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-18.js
@@ -0,0 +1,114 @@
+// Debugger.prototype.{addDebuggee,hasDebuggee,removeDebuggee} recognize globals
+// regardless of how they are specified.
+
+var dbg = new Debugger;
+
+// Assert that dbg's debuggees are exactly the set passed as arguments.
+// The arguments are assumed to be Debugger.Object instances referring to
+// globals without wrappers --- which is the sort returned by addDebuggee.
+function assertDebuggees(...expected) {
+ print("assertDebuggees([" + expected.map((g) => g.toSource()) + "])");
+ var debuggees = dbg.getDebuggees();
+ assertEq(expected.length, debuggees.length);
+ for (let g of expected)
+ assertEq(debuggees.indexOf(g) != -1, true);
+}
+
+var g1 = newGlobal({newCompartment: true}); g1.toSource = function () { return "[global g1]"; };
+var g2 = newGlobal({newCompartment: true}); g2.toSource = function () { return "[global g2]"; };
+
+assertDebuggees();
+
+// Produce every possible way to designate g1, for us to play with.
+// Globals can be designated by any of the following:
+//
+// - "CCW": a Cross-Compartment Wrapper (CCW) of a global object
+// - "D.O": a Debugger.Object whose referent is a global object
+// - "D.O of CCW": a Debugger.Object whose referent is a CCW of a
+// global object, where the CCW can be securely unwrapped
+//
+// There's no direct "G", since globals are always in their own
+// compartments, never the debugger's; if we ever viewed them directly,
+// that would be a compartment violation.
+
+// "dg1" means "Debugger.Object referring (directly) to g1".
+var dg1 = dbg.addDebuggee(g1);
+dg1.toSource = function() { return "[Debugger.Object for global g1]"; };
+assertEq(dg1.unwrap(), dg1);
+assertDebuggees(dg1);
+
+// We need to add g2 as a debuggee; that's the only way to get a D.O referring
+// to it without a wrapper.
+var dg2 = dbg.addDebuggee(g2);
+dg2.toSource = function() { return "[Debugger.Object for global g2]"; };
+assertEq(dg2.unwrap(), dg2);
+assertDebuggees(dg1, dg2);
+
+// "dwg1" means "Debugger.Object referring to CCW of g1".
+var dwg1 = dg2.makeDebuggeeValue(g1);
+assertEq(dwg1.unwrap(), dg1.makeDebuggeeValue(g1));
+dwg1.toSource = function() { return "[Debugger.Object for CCW of WindowProxy of g1]"; };
+
+assertDebuggees(dg1, dg2);
+assertEq(dbg.removeDebuggee(g1), undefined);
+assertEq(dbg.removeDebuggee(g2), undefined);
+assertDebuggees();
+
+// Systematically cover all the single-global possibilities:
+//
+// | added as | designated as | addDebuggee | hasDebuggee | removeDebuggee |
+// |-------------+---------------+-------------+-------------+----------------|
+// | (not added) | CCW | X | X | X |
+// | | D.O | X | X | X |
+// | | D.O of CCW | X | X | X |
+// |-------------+---------------+-------------+-------------+----------------|
+// | CCW | CCW | X | X | X |
+// | | D.O | X | X | X |
+// | | D.O of CCW | X | X | X |
+// |-------------+---------------+-------------+-------------+----------------|
+// | D.O | CCW | X | X | X |
+// | | D.O | X | X | X |
+// | | D.O of CCW | X | X | X |
+// |-------------+---------------+-------------+-------------+----------------|
+// | D.O of CCW | CCW | X | X | X |
+// | | D.O | X | X | X |
+// | | D.O of CCW | X | X | X |
+
+// Cover the "(not added)" section of the table, other than "addDebuggee":
+assertEq(dbg.hasDebuggee(g1), false);
+assertEq(dbg.hasDebuggee(dg1), false);
+assertEq(dbg.hasDebuggee(dwg1), false);
+
+assertEq(dbg.removeDebuggee(g1), undefined); assertDebuggees();
+assertEq(dbg.removeDebuggee(dg1), undefined); assertDebuggees();
+assertEq(dbg.removeDebuggee(dwg1), undefined); assertDebuggees();
+
+// Try all operations adding the debuggee using |addAs|, and operating on it
+// using |designateAs|, thereby covering one row of the table (outside the '(not
+// added)' section), and one case in the '(not added)', 'designated as' section.
+//
+// |Direct| should be the Debugger.Object referring directly to the debuggee
+// global, for checking the results from addDebuggee and getDebuggees.
+function combo(addAs, designateAs, direct) {
+ print("combo(" + JSON.stringify(addAs) + ", " + JSON.stringify(designateAs) + ")");
+ assertDebuggees();
+ assertEq(dbg.addDebuggee(addAs), direct);
+ assertDebuggees(direct);
+ assertEq(dbg.addDebuggee(designateAs), direct);
+ assertDebuggees(direct);
+ assertEq(dbg.hasDebuggee(designateAs), true);
+ assertEq(dbg.removeDebuggee(designateAs), undefined);
+ assertDebuggees();
+}
+
+combo(g1, g1, dg1);
+combo(dg1, g1, dg1);
+combo(dwg1, g1, dg1);
+
+combo(g1, dg1, dg1);
+combo(dg1, dg1, dg1);
+combo(dwg1, dg1, dg1);
+
+combo(g1, dwg1, dg1);
+combo(dg1, dwg1, dg1);
+combo(dwg1, dwg1, dg1);
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-19.js b/js/src/jit-test/tests/debug/Debugger-debuggees-19.js
new file mode 100644
index 0000000000..7883a8a076
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-19.js
@@ -0,0 +1,49 @@
+// removeAllDebuggees removes all the debuggees.
+
+var dbg = new Debugger;
+
+// If we eval in a debuggee, log which debuggee it was.
+var log;
+dbg.onEnterFrame = function (frame) {
+ log += 'e';
+ // frame.environment in all evals below is the global lexical env.
+ log += frame.environment.parent.object.label;
+};
+
+var g1 = newGlobal({newCompartment: true});
+log = '';
+g1.eval('Math');
+assertEq(log, ''); // not yet a debuggee
+
+var g1w = dbg.addDebuggee(g1);
+assertEq(g1w instanceof Debugger.Object, true);
+g1w.label = 'g1';
+log = '';
+g1.eval('Math'); // now a debuggee
+assertEq(log, 'eg1');
+
+var g2 = newGlobal({newCompartment: true});
+log = '';
+g1.eval('Math'); // debuggee
+g2.eval('Math'); // not a debuggee
+assertEq(log, 'eg1');
+
+var g2w = dbg.addDebuggee(g2);
+assertEq(g2w instanceof Debugger.Object, true);
+g2w.label = 'g2';
+log = '';
+g1.eval('Math'); // debuggee
+g2.eval('this'); // debuggee
+assertEq(log, 'eg1eg2');
+
+var a1 = dbg.getDebuggees();
+assertEq(a1.length, 2);
+
+assertEq(dbg.removeAllDebuggees(), undefined);
+var a2 = dbg.getDebuggees();
+assertEq(a2.length, 0);
+
+log = '';
+g1.eval('Math'); // no longer a debuggee
+g2.eval('this'); // no longer a debuggee
+assertEq(log, '');
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-20.js b/js/src/jit-test/tests/debug/Debugger-debuggees-20.js
new file mode 100644
index 0000000000..8cc27d0fca
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-20.js
@@ -0,0 +1,30 @@
+// addAllGlobalsAsDebuggees adds all the globals as debuggees.
+
+var g1 = newGlobal({newCompartment: true}); // Created before the Debugger; debuggee.
+var g2 = newGlobal({newCompartment: true}); // Created before the Debugger; not debuggee.
+
+var dbg = new Debugger;
+
+var g3 = newGlobal({newCompartment: true}); // Created after the Debugger; debuggee.
+var g4 = newGlobal({newCompartment: true}); // Created after the Debugger; not debuggee.
+
+var g1w = dbg.addDebuggee(g1);
+var g3w = dbg.addDebuggee(g3);
+assertEq(dbg.addAllGlobalsAsDebuggees(), undefined);
+
+// Get Debugger.Objects viewing the WindowProxies from their own compartments.
+assertEq(g1w.makeDebuggeeValue(g1), g3w.makeDebuggeeValue(g1).unwrap());
+assertEq(g3w.makeDebuggeeValue(g3), g1w.makeDebuggeeValue(g3).unwrap());
+
+var g2w = g1w.makeDebuggeeValue(g2).unwrap();
+var g4w = g1w.makeDebuggeeValue(g4).unwrap();
+
+var thisw = g1w.makeDebuggeeValue(this).unwrap();
+
+// Check that they're all there.
+assertEq(dbg.hasDebuggee(g1w), true);
+assertEq(dbg.hasDebuggee(g2w), true);
+assertEq(dbg.hasDebuggee(g3w), true);
+assertEq(dbg.hasDebuggee(g4w), true);
+// The debugger's global is not a debuggee.
+assertEq(dbg.hasDebuggee(thisw), false);
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-21.js b/js/src/jit-test/tests/debug/Debugger-debuggees-21.js
new file mode 100644
index 0000000000..fb5f1b849d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-21.js
@@ -0,0 +1,12 @@
+// Errors adding globals in addAllGlobalsAsDebuggees should be reported.
+
+// The exception that might be thrown in this test reflects our inability
+// to change compartments to debug mode while they have frames on the
+// stack. If we run this test with --debugjit, it won't throw an error at
+// all, since all compartments are already in debug mode. So, pass if the
+// script completes normally, or throws an appropriate exception.
+try {
+ newGlobal().eval("(new Debugger).addAllGlobalsAsDebuggees();");
+} catch (ex) {
+ assertEq(!!(''+ex).match(/can't start debugging: a debuggee script is on the stack/), true);
+}
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-22.js b/js/src/jit-test/tests/debug/Debugger-debuggees-22.js
new file mode 100644
index 0000000000..4191fb0e61
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-22.js
@@ -0,0 +1,24 @@
+// Adding a debuggee allowed with scripts on stack.
+
+var g = newGlobal({newCompartment: true});
+g.dbg = new Debugger;
+
+g.eval("" + function f(d) {
+ g(d);
+ if (d)
+ assertEq(dbg.hasDebuggee(this), true);
+});
+
+g.eval("" + function g(d) {
+ if (!d)
+ return;
+
+ dbg.addDebuggee(this);
+});
+
+g.eval("(" + function test() {
+ f(false);
+ f(false);
+ f(true);
+ f(true);
+} + ")();");
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-23.js b/js/src/jit-test/tests/debug/Debugger-debuggees-23.js
new file mode 100644
index 0000000000..fabdc3761e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-23.js
@@ -0,0 +1,107 @@
+// Adding a debuggee allowed with scripts on stack from stranger places.
+
+// Test CCW.
+(function testCCW() {
+ var g = newGlobal({newCompartment: true});
+ var dbg = new Debugger;
+ g.dbg = dbg;
+ g.GLOBAL = g;
+
+ g.turnOnDebugger = function () {
+ dbg.addDebuggee(g);
+ };
+
+ g.eval("" + function f(d) {
+ turnOnDebugger();
+ assertEq(dbg.hasDebuggee(GLOBAL), true);
+ });
+
+ g.eval("(" + function test() {
+ f(false);
+ f(false);
+ f(true);
+ f(true);
+ } + ")();");
+})();
+
+// Test getter.
+(function testGetter() {
+ var g = newGlobal({newCompartment: true});
+ g.dbg = new Debugger;
+ g.GLOBAL = g;
+
+ g.eval("" + function f(obj) {
+ obj.foo;
+ assertEq(dbg.hasDebuggee(GLOBAL), true);
+ });
+
+ g.eval("(" + function test() {
+ f({ get foo() { dbg.addDebuggee(GLOBAL); } });
+ } + ")();");
+})();
+
+// Test setter.
+(function testSetter() {
+ var g = newGlobal({newCompartment: true});
+ g.dbg = new Debugger;
+ g.GLOBAL = g;
+
+ g.eval("" + function f(obj) {
+ obj.foo = 42;
+ assertEq(dbg.hasDebuggee(GLOBAL), true);
+ });
+
+ g.eval("(" + function test() {
+ f({ set foo(v) { dbg.addDebuggee(GLOBAL); } });
+ } + ")();");
+})();
+
+// Test toString.
+(function testToString() {
+ var g = newGlobal({newCompartment: true});
+ g.dbg = new Debugger;
+ g.GLOBAL = g;
+
+ g.eval("" + function f(obj) {
+ obj + "";
+ assertEq(dbg.hasDebuggee(GLOBAL), true);
+ });
+
+ g.eval("(" + function test() {
+ f({ toString: function () { dbg.addDebuggee(GLOBAL); }});
+ } + ")();");
+})();
+
+// Test valueOf.
+(function testValueOf() {
+ var g = newGlobal({newCompartment: true});
+ g.dbg = new Debugger;
+ g.GLOBAL = g;
+
+ g.eval("" + function f(obj) {
+ obj + "";
+ assertEq(dbg.hasDebuggee(GLOBAL), true);
+ });
+
+ g.eval("(" + function test() {
+ f({ valueOf: function () { dbg.addDebuggee(GLOBAL); }});
+ } + ")();");
+})();
+
+// Test proxy trap.
+(function testProxyTrap() {
+ var g = newGlobal({newCompartment: true});
+ g.dbg = new Debugger;
+ g.GLOBAL = g;
+
+ g.eval("" + function f(proxy) {
+ proxy["foo"];
+ assertEq(dbg.hasDebuggee(GLOBAL), true);
+ });
+
+ g.eval("(" + function test() {
+ var handler = { get: function () { dbg.addDebuggee(GLOBAL); } };
+ var proxy = new Proxy({}, handler);
+ f(proxy);
+ } + ")();");
+})();
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-24.js b/js/src/jit-test/tests/debug/Debugger-debuggees-24.js
new file mode 100644
index 0000000000..9243c247d1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-24.js
@@ -0,0 +1,55 @@
+// Turning debugger on for a particular global with on-stack scripts shouldn't
+// make other globals' scripts observable.
+
+var g1 = newGlobal({newCompartment: true});
+var g2 = newGlobal({newCompartment: true});
+var g3 = newGlobal({newCompartment: true});
+
+g1.eval("" + function f() {
+ var name = "f";
+ g();
+ return name;
+});
+g2.eval("" + function g() {
+ var name = "g";
+ h();
+ return name;
+});
+g3.eval("" + function h() {
+ var name = "h";
+ toggle();
+ return name;
+});
+
+g1.g = g2.g;
+g2.h = g3.h;
+
+function name(f) {
+ return f.environment.getVariable("name");
+}
+
+var dbg = new Debugger;
+g3.toggle = function () {
+ var frame;
+
+ // Only f should be visible.
+ dbg.addDebuggee(g1);
+ frame = dbg.getNewestFrame();
+ assertEq(name(frame), "f");
+
+ // Now h should also be visible.
+ dbg.addDebuggee(g3);
+ frame = dbg.getNewestFrame();
+ assertEq(name(frame), "h");
+ assertEq(name(frame.older), "f");
+
+ // Finally everything should be visible.
+ dbg.addDebuggee(g2);
+ frame = dbg.getNewestFrame();
+ assertEq(name(frame), "h");
+ assertEq(name(frame.older), "g");
+ assertEq(name(frame.older.older), "f");
+};
+
+g1.eval("(" + function () { f(); } + ")();");
+
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-25.js b/js/src/jit-test/tests/debug/Debugger-debuggees-25.js
new file mode 100644
index 0000000000..f066e5a3ef
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-25.js
@@ -0,0 +1,48 @@
+// Turning debugger off global at a time.
+
+var g1 = newGlobal({newCompartment: true});
+var g2 = newGlobal({newCompartment: true});
+var g3 = newGlobal({newCompartment: true});
+
+g1.eval("" + function f() {
+ var name = "f";
+ g();
+ return name;
+});
+g2.eval("" + function g() {
+ var name = "g";
+ h();
+ return name;
+});
+g3.eval("" + function h() {
+ var name = "h";
+ toggle();
+ return name;
+});
+
+g1.g = g2.g;
+g2.h = g3.h;
+
+function name(f) {
+ return f.environment.getVariable("name");
+}
+
+var dbg = new Debugger;
+g3.toggle = function () {
+ var frame;
+
+ // Add all globals.
+ dbg.addDebuggee(g1);
+ dbg.addDebuggee(g3);
+ dbg.addDebuggee(g2);
+
+ // Remove one at a time.
+ dbg.removeDebuggee(g3);
+ assertEq(name(dbg.getNewestFrame()), "g");
+ dbg.removeDebuggee(g2);
+ assertEq(name(dbg.getNewestFrame()), "f");
+ dbg.removeDebuggee(g1);
+};
+
+g1.eval("(" + function () { f(); } + ")();");
+
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-26.js b/js/src/jit-test/tests/debug/Debugger-debuggees-26.js
new file mode 100644
index 0000000000..d2e7f610b1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-26.js
@@ -0,0 +1,34 @@
+// Ion can bail in-place when throwing exceptions with debug mode toggled on.
+
+load(libdir + "jitopts.js");
+
+if (!jitTogglesMatch(Opts_Ion2NoOffthreadCompilation))
+ quit();
+
+withJitOptions(Opts_Ion2NoOffthreadCompilation, function () {
+ var g = newGlobal({newCompartment: true});
+ var dbg = new Debugger;
+
+ g.toggle = function toggle(x, d) {
+ if (d) {
+ dbg.addDebuggee(g);
+ var frame = dbg.getNewestFrame().older;
+ assertEq(frame.callee.name, "f");
+ assertEq(frame.implementation, "ion");
+ throw 42;
+ }
+ };
+
+ g.eval("" + function f(x, d) { g(x, d); });
+ g.eval("" + function g(x, d) { toggle(x, d); });
+
+ try {
+ g.eval("(" + function test() {
+ for (var i = 0; i < 5; i++)
+ f(42, false);
+ f(42, true);
+ } + ")();");
+ } catch (exc) {
+ assertEq(exc, 42);
+ }
+});
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-27.js b/js/src/jit-test/tests/debug/Debugger-debuggees-27.js
new file mode 100644
index 0000000000..7046b68385
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-27.js
@@ -0,0 +1,19 @@
+// Test that we can OSR with the same script on the stack multiple times.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+
+g.toggle = function toggle() {
+ dbg.addDebuggee(g);
+ var frame = dbg.getNewestFrame();
+}
+
+g.eval("" + function f(x) {
+ if (x == 0) {
+ toggle();
+ return;
+ }
+ f(x - 1);
+});
+
+g.eval("f(3);");
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-28.js b/js/src/jit-test/tests/debug/Debugger-debuggees-28.js
new file mode 100644
index 0000000000..a5c11201cb
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-28.js
@@ -0,0 +1,109 @@
+// Test that on->off->on and off->on->off toggles don't crash.
+
+function addRemove(dbg, g) {
+ dbg.addDebuggee(g);
+ var f = dbg.getNewestFrame();
+ while (f)
+ f = f.older;
+ dbg.removeDebuggee(g);
+}
+
+function removeAdd(dbg, g) {
+ dbg.removeDebuggee(g);
+ dbg.addDebuggee(g);
+ var f = dbg.getNewestFrame();
+ while (f)
+ f = f.older;
+}
+
+function newGlobalDebuggerPair(toggleSeq) {
+ var g = newGlobal({newCompartment: true});
+ var dbg = new Debugger;
+
+ if (toggleSeq == removeAdd)
+ dbg.addDebuggee(g);
+
+ g.eval("" + function f() { return g(); });
+ g.eval("" + function g() { return h(); });
+ g.eval("line0 = Error().lineNumber;");
+ g.eval("" + function h() {
+ for (var i = 0; i < 100; i++)
+ interruptIf(i == 95);
+ debugger;
+ return i;
+ });
+
+ setInterruptCallback(function () { return true; });
+
+ return [g, dbg];
+}
+
+function testInterrupt(toggleSeq) {
+ var [g, dbg] = newGlobalDebuggerPair(toggleSeq);
+
+ setInterruptCallback(function () {
+ toggleSeq(dbg, g);
+ return true;
+ });
+
+ assertEq(g.f(), 100);
+}
+
+function testPrologue(toggleSeq) {
+ var [g, dbg] = newGlobalDebuggerPair(toggleSeq);
+
+ dbg.onEnterFrame = function (f) {
+ if (f.callee && f.callee.name == "h")
+ toggleSeq(dbg, g);
+ };
+
+ assertEq(g.f(), 100);
+}
+
+function testEpilogue(toggleSeq) {
+ var [g, dbg] = newGlobalDebuggerPair(toggleSeq);
+
+ dbg.onEnterFrame = function (f) {
+ if (f.callee && f.callee.name == "h") {
+ f.onPop = function () {
+ toggleSeq(dbg, g);
+ };
+ }
+ };
+
+ assertEq(g.f(), 100);
+}
+
+function testTrap(toggleSeq) {
+ var [g, dbg] = newGlobalDebuggerPair(toggleSeq);
+
+ dbg.onEnterFrame = function (f) {
+ if (f.callee && f.callee.name == "h") {
+ var offs = f.script.getLineOffsets(g.line0 + 2);
+ assertEq(offs.length > 0, true);
+ f.script.setBreakpoint(offs[0], { hit: function () {
+ toggleSeq(dbg, g);
+ }});
+ }
+ };
+
+ assertEq(g.f(), 100);
+}
+
+function testDebugger(toggleSeq) {
+ var [g, dbg] = newGlobalDebuggerPair(toggleSeq);
+
+ dbg.onDebuggerStatement = function () {
+ toggleSeq(dbg, g);
+ };
+
+ assertEq(g.f(), 100);
+}
+
+testInterrupt(addRemove);
+testInterrupt(removeAdd);
+
+testPrologue(removeAdd);
+testEpilogue(removeAdd);
+testTrap(removeAdd);
+testDebugger(removeAdd);
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-29.js b/js/src/jit-test/tests/debug/Debugger-debuggees-29.js
new file mode 100644
index 0000000000..fb05edc884
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-29.js
@@ -0,0 +1,6 @@
+// Debugger.prototype.addDebuggee should not accept invisible-to-debugger globals.
+load(libdir + 'asserts.js');
+
+var g = newGlobal({ newCompartment: true, invisibleToDebugger: true });
+
+assertThrowsInstanceOf(() => { new Debugger(g); }, TypeError);
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-30.js b/js/src/jit-test/tests/debug/Debugger-debuggees-30.js
new file mode 100644
index 0000000000..2287c38d8b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-30.js
@@ -0,0 +1,32 @@
+// Debugger and debuggees must be in different compartments.
+
+load(libdir + "asserts.js");
+
+function testConstructor() {
+ var g = newGlobal({sameCompartmentAs: this});
+ assertTypeErrorMessage(() => new Debugger(g),
+ "Debugger: argument must be an object from a different compartment");
+}
+testConstructor();
+
+function testAddDebuggee() {
+ var g = newGlobal({sameCompartmentAs: this});
+ var dbg = new Debugger();
+ assertTypeErrorMessage(() => dbg.addDebuggee(this),
+ "debugger and debuggee must be in different compartments");
+}
+testAddDebuggee();
+
+function testAddAllGlobalsAsDebuggees() {
+ var g1 = newGlobal({sameCompartmentAs: this});
+ var g2 = newGlobal({newCompartment: true});
+ var g3 = newGlobal({sameCompartmentAs: g2});
+ var g4 = newGlobal({newCompartment: true, sameZoneAs: this});
+ var dbg = new Debugger();
+ dbg.addAllGlobalsAsDebuggees();
+ assertEq(dbg.hasDebuggee(g1), false);
+ assertEq(dbg.hasDebuggee(g2), true);
+ assertEq(dbg.hasDebuggee(g3), true);
+ assertEq(dbg.hasDebuggee(g4), true);
+}
+testAddAllGlobalsAsDebuggees();
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-31.js b/js/src/jit-test/tests/debug/Debugger-debuggees-31.js
new file mode 100644
index 0000000000..0eb7f65212
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-31.js
@@ -0,0 +1,30 @@
+// |jit-test| skip-if: !hasFunction["gczeal"]
+// Removing and adding debuggees during an incremental sweep should not confuse
+// the generatorFrames map.
+
+// Let any ongoing incremental GC finish, and then clear any ambient zeal
+// settings established by the harness (the JS_GC_ZEAL env var, shell arguments,
+// etc.)
+gczeal(0);
+
+let g = newGlobal({newCompartment: true});
+g.eval('function* f() { yield 123; }');
+
+let dbg = Debugger(g);
+dbg.onEnterFrame = frame => {
+ dbg.removeDebuggee(g);
+ dbg.addDebuggee(g);
+};
+
+// Select GCZeal mode 10 (IncrementalMultipleSlices: Incremental GC in many
+// slices) and use long slices, to make sure that the debuggee removal occurs
+// during a slice.
+gczeal(10, 0);
+gcslice(1);
+while (gcstate() !== "NotAcctive" && gcstate() !== "Sweep") {
+ gcslice(1000);
+}
+
+let genObj = g.f();
+genObj.return();
+assertEq(gcstate(), "Sweep");
diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-32.js b/js/src/jit-test/tests/debug/Debugger-debuggees-32.js
new file mode 100644
index 0000000000..4d5bab9e1a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-32.js
@@ -0,0 +1,13 @@
+let g = newGlobal({newCompartment: true});
+g.eval('function* f() { yield 123; }');
+
+let dbg = Debugger(g);
+let genObj = g.f();
+
+dbg.onEnterFrame = frame => {
+ frame.onStep = () => {};
+ dbg.removeDebuggee(g);
+ dbg.addDebuggee(g);
+};
+
+genObj.return();
diff --git a/js/src/jit-test/tests/debug/Debugger-findAllGlobals-01.js b/js/src/jit-test/tests/debug/Debugger-findAllGlobals-01.js
new file mode 100644
index 0000000000..7400ef9ea1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findAllGlobals-01.js
@@ -0,0 +1,24 @@
+// Debugger.prototype.findAllGlobals surface.
+
+load(libdir + 'asserts.js');
+
+var dbg = new Debugger;
+var d = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(dbg), 'findAllGlobals');
+assertEq(d.configurable, true);
+assertEq(d.enumerable, false);
+assertEq(d.writable, true);
+assertEq(typeof d.value, 'function');
+assertEq(dbg.findAllGlobals.length, 0);
+assertEq(dbg.findAllGlobals.name, 'findAllGlobals');
+
+// findAllGlobals can only be applied to real Debugger instances.
+assertThrowsInstanceOf(function() {
+ Debugger.prototype.findAllGlobals.call(Debugger.prototype);
+ },
+ TypeError);
+var a = dbg.findAllGlobals();
+assertEq(a instanceof Array, true);
+assertEq(a.length > 0, true);
+for (g of a) {
+ assertEq(g instanceof Debugger.Object, true);
+}
diff --git a/js/src/jit-test/tests/debug/Debugger-findAllGlobals-02.js b/js/src/jit-test/tests/debug/Debugger-findAllGlobals-02.js
new file mode 100644
index 0000000000..a44516128a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findAllGlobals-02.js
@@ -0,0 +1,26 @@
+// Debugger.prototype.findAllGlobals finds ALL the globals!
+
+var g1 = newGlobal({newCompartment: true}); // Created before the Debugger; debuggee.
+var g2 = newGlobal({newCompartment: true}); // Created before the Debugger; not debuggee.
+
+var dbg = new Debugger;
+
+var g3 = newGlobal({newCompartment: true}); // Created after the Debugger; debuggee.
+var g4 = newGlobal({newCompartment: true}); // Created after the Debugger; not debuggee.
+
+var g1w = dbg.addDebuggee(g1);
+var g3w = dbg.addDebuggee(g3);
+
+var a = dbg.findAllGlobals();
+
+// Get Debugger.Objects around global objects
+var g2w = dbg.makeGlobalObjectReference(g2);
+var g4w = dbg.makeGlobalObjectReference(g4);
+var thisw = dbg.makeGlobalObjectReference(this);
+
+// Check that they're all there.
+assertEq(a.indexOf(g1w) != -1, true);
+assertEq(a.indexOf(g2w) != -1, true);
+assertEq(a.indexOf(g3w) != -1, true);
+assertEq(a.indexOf(g4w) != -1, true);
+assertEq(a.indexOf(thisw) != -1, true);
diff --git a/js/src/jit-test/tests/debug/Debugger-findObjects-01.js b/js/src/jit-test/tests/debug/Debugger-findObjects-01.js
new file mode 100644
index 0000000000..4ed43310a9
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findObjects-01.js
@@ -0,0 +1,4 @@
+// In a debugger with no debuggees, findObjects should return no objects.
+
+var dbg = new Debugger;
+assertEq(dbg.findObjects().length, 0);
diff --git a/js/src/jit-test/tests/debug/Debugger-findObjects-02.js b/js/src/jit-test/tests/debug/Debugger-findObjects-02.js
new file mode 100644
index 0000000000..b3bdbbf519
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findObjects-02.js
@@ -0,0 +1,18 @@
+// In a debuggee with live objects, findObjects finds those objects.
+
+var g = newGlobal({newCompartment: true});
+
+let defObject = v => g.eval(`this.${v} = { toString: () => "[object ${v}]" }`);
+defObject("a");
+defObject("b");
+defObject("c");
+
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+var aw = gw.makeDebuggeeValue(g.a);
+var bw = gw.makeDebuggeeValue(g.b);
+var cw = gw.makeDebuggeeValue(g.c);
+
+assertEq(dbg.findObjects().indexOf(aw) != -1, true);
+assertEq(dbg.findObjects().indexOf(bw) != -1, true);
+assertEq(dbg.findObjects().indexOf(cw) != -1, true);
diff --git a/js/src/jit-test/tests/debug/Debugger-findObjects-03.js b/js/src/jit-test/tests/debug/Debugger-findObjects-03.js
new file mode 100644
index 0000000000..0daf850754
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findObjects-03.js
@@ -0,0 +1,12 @@
+// findObjects' result includes objects referenced by other objects.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+
+g.eval('this.a = { b: {} };');
+
+var bw = gw.makeDebuggeeValue(g.a.b);
+
+var objects = dbg.findObjects();
+assertEq(objects.indexOf(bw) != -1, true);
diff --git a/js/src/jit-test/tests/debug/Debugger-findObjects-04.js b/js/src/jit-test/tests/debug/Debugger-findObjects-04.js
new file mode 100644
index 0000000000..ac62575637
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findObjects-04.js
@@ -0,0 +1,16 @@
+// findObjects' result includes objects captured by closures.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+
+g.eval(`
+ this.f = (function () {
+ let a = { foo: () => {} };
+ return () => a;
+ }());
+`);
+
+let objects = dbg.findObjects();
+let aw = gw.makeDebuggeeValue(g.f());
+assertEq(objects.indexOf(aw) != -1, true);
diff --git a/js/src/jit-test/tests/debug/Debugger-findObjects-05.js b/js/src/jit-test/tests/debug/Debugger-findObjects-05.js
new file mode 100644
index 0000000000..7d581ca98c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findObjects-05.js
@@ -0,0 +1,10 @@
+// findObjects' result doesn't include any duplicates.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+dbg.addDebuggee(g);
+
+let objects = dbg.findObjects();
+let set = new Set(objects);
+
+assertEq(objects.length, set.size);
diff --git a/js/src/jit-test/tests/debug/Debugger-findObjects-06.js b/js/src/jit-test/tests/debug/Debugger-findObjects-06.js
new file mode 100644
index 0000000000..2f8d5a11f6
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findObjects-06.js
@@ -0,0 +1,14 @@
+// In a debugger with multiple debuggees, findObjects finds objects from all debuggees.
+
+var g1 = newGlobal({newCompartment: true});
+var g2 = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var g1w = dbg.addDebuggee(g1);
+var g2w = dbg.addDebuggee(g2);
+
+g1.eval('this.a = {};');
+g2.eval('this.b = {};');
+
+var objects = dbg.findObjects();
+assertEq(objects.indexOf(g1w.makeDebuggeeValue(g1.a)) != -1, true);
+assertEq(objects.indexOf(g2w.makeDebuggeeValue(g2.b)) != -1, true);
diff --git a/js/src/jit-test/tests/debug/Debugger-findObjects-07.js b/js/src/jit-test/tests/debug/Debugger-findObjects-07.js
new file mode 100644
index 0000000000..5d4549cc0e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findObjects-07.js
@@ -0,0 +1,22 @@
+// findObjects can filter objects by class name.
+
+var g = newGlobal({newCompartment: true});
+
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+
+g.eval('this.re = /foo/;');
+g.eval('this.d = new Date();');
+
+var rew = gw.makeDebuggeeValue(g.re);
+var dw = gw.makeDebuggeeValue(g.d);
+
+var objects;
+
+objects = dbg.findObjects({ class: "RegExp" });
+assertEq(objects.indexOf(rew) != -1, true);
+assertEq(objects.indexOf(dw) == -1, true);
+
+objects = dbg.findObjects({ class: "Date" });
+assertEq(objects.indexOf(dw) != -1, true);
+assertEq(objects.indexOf(rew) == -1, true);
diff --git a/js/src/jit-test/tests/debug/Debugger-findObjects-08.js b/js/src/jit-test/tests/debug/Debugger-findObjects-08.js
new file mode 100644
index 0000000000..fd6ceb1031
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findObjects-08.js
@@ -0,0 +1,12 @@
+// Passing bad query properties to Debugger.prototype.findScripts throws.
+
+load(libdir + 'asserts.js');
+
+var dbg = new Debugger();
+var g = newGlobal();
+
+assertThrowsInstanceOf(() => dbg.findObjects({ class: null }), TypeError);
+assertThrowsInstanceOf(() => dbg.findObjects({ class: true }), TypeError);
+assertThrowsInstanceOf(() => dbg.findObjects({ class: 1337 }), TypeError);
+assertThrowsInstanceOf(() => dbg.findObjects({ class: /re/ }), TypeError);
+assertThrowsInstanceOf(() => dbg.findObjects({ class: {} }), TypeError);
diff --git a/js/src/jit-test/tests/debug/Debugger-findObjects-09.js b/js/src/jit-test/tests/debug/Debugger-findObjects-09.js
new file mode 100644
index 0000000000..4d95d3abf9
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findObjects-09.js
@@ -0,0 +1,9 @@
+// We don't return objects where our query's class name is the prefix of the
+// object's class name and vice versa.
+
+var dbg = new Debugger();
+var g = newGlobal({newCompartment: true});
+var gw = dbg.addDebuggee(g);
+
+assertEq(dbg.findObjects({ class: "Objec" }).length, 0);
+assertEq(dbg.findObjects({ class: "Objectttttt" }).length, 0);
diff --git a/js/src/jit-test/tests/debug/Debugger-findObjects-10.js b/js/src/jit-test/tests/debug/Debugger-findObjects-10.js
new file mode 100644
index 0000000000..d490dac26c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findObjects-10.js
@@ -0,0 +1,5 @@
+// Debugger.prototype.findObjects should not expose internal JSFunction objects.
+
+var g = newGlobal({newCompartment: true});
+g.eval(`function f() { return function() {}; }`);
+new Debugger(g).findObjects();
diff --git a/js/src/jit-test/tests/debug/Debugger-findObjects-11.js b/js/src/jit-test/tests/debug/Debugger-findObjects-11.js
new file mode 100644
index 0000000000..8c50a95ed6
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findObjects-11.js
@@ -0,0 +1,7 @@
+// This shouldn't segfault.
+
+var g = newGlobal({newCompartment: true});
+g.eval(`function f() { return function() {
+ function g() {}
+}; }`);
+new Debugger(g).findObjects();
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-01.js b/js/src/jit-test/tests/debug/Debugger-findScripts-01.js
new file mode 100644
index 0000000000..e39691605b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-01.js
@@ -0,0 +1,4 @@
+// In a debugger with no debuggees, findScripts should return no scripts.
+
+var dbg = new Debugger;
+assertEq(dbg.findScripts().length, 0);
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-02.js b/js/src/jit-test/tests/debug/Debugger-findScripts-02.js
new file mode 100644
index 0000000000..5a2b7cbfbf
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-02.js
@@ -0,0 +1,16 @@
+// In a debuggee with functions, findScripts finds those functions' scripts.
+
+var g = newGlobal({newCompartment: true});
+g.eval('function f(){}');
+g.eval('function g(){}');
+g.eval('function h(){}');
+
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+var fw = gw.makeDebuggeeValue(g.f);
+var gw = gw.makeDebuggeeValue(g.g);
+var hw = gw.makeDebuggeeValue(g.h);
+
+assertEq(dbg.findScripts().indexOf(fw.script) != -1, true);
+assertEq(dbg.findScripts().indexOf(gw.script) != -1, true);
+assertEq(dbg.findScripts().indexOf(hw.script) != -1, true);
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-03.js b/js/src/jit-test/tests/debug/Debugger-findScripts-03.js
new file mode 100644
index 0000000000..2bc9d84de8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-03.js
@@ -0,0 +1,16 @@
+// While eval code is running, findScripts returns its script.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var log;
+
+g.check = function () {
+ log += 'c';
+ var frame = dbg.getNewestFrame();
+ assertEq(frame.type, "eval");
+ assertEq(dbg.findScripts().indexOf(frame.script) != -1, true);
+};
+
+log = '';
+g.eval('check()');
+assertEq(log, 'c');
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-04.js b/js/src/jit-test/tests/debug/Debugger-findScripts-04.js
new file mode 100644
index 0000000000..8d81d5174e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-04.js
@@ -0,0 +1,27 @@
+// Within a series of evals and calls, all their frames' scripts appear in findScripts' result.
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var log;
+
+g.check = function () {
+ log += 'c';
+ var scripts = dbg.findScripts();
+
+ var innerEvalFrame = dbg.getNewestFrame();
+ assertEq(innerEvalFrame.type, "eval");
+ assertEq(scripts.indexOf(innerEvalFrame.script) != -1, true);
+
+ var callFrame = innerEvalFrame.older;
+ assertEq(callFrame.type, "call");
+ assertEq(scripts.indexOf(callFrame.script) != -1, true);
+
+ var outerEvalFrame = callFrame.older;
+ assertEq(outerEvalFrame.type, "eval");
+ assertEq(scripts.indexOf(outerEvalFrame.script) != -1, true);
+ assertEq(innerEvalFrame != outerEvalFrame, true);
+};
+
+g.eval('function f() { eval("check();") }');
+log = '';
+g.eval('f();');
+assertEq(log, 'c');
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-05.js b/js/src/jit-test/tests/debug/Debugger-findScripts-05.js
new file mode 100644
index 0000000000..b82dfda34b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-05.js
@@ -0,0 +1,18 @@
+// findScripts' result includes scripts for nested functions.
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+var log;
+
+g.eval('function f() { return function g() { return function h() { return "Squee!"; } } }');
+var fw = gw.makeDebuggeeValue(g.f);
+var gw = gw.makeDebuggeeValue(g.f());
+var hw = gw.makeDebuggeeValue(g.f()());
+
+assertEq(fw.script != gw.script, true);
+assertEq(fw.script != hw.script, true);
+
+var scripts = dbg.findScripts();
+assertEq(scripts.indexOf(fw.script) != -1, true);
+assertEq(scripts.indexOf(gw.script) != -1, true);
+assertEq(scripts.indexOf(hw.script) != -1, true);
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-06.js b/js/src/jit-test/tests/debug/Debugger-findScripts-06.js
new file mode 100644
index 0000000000..129aa48423
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-06.js
@@ -0,0 +1,13 @@
+// In a debugger with multiple debuggees, findScripts finds scripts across all debuggees.
+var g1 = newGlobal({newCompartment: true});
+var g2 = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var g1w = dbg.addDebuggee(g1);
+var g2w = dbg.addDebuggee(g2);
+
+g1.eval('function f() {}');
+g2.eval('function g() {}');
+
+var scripts = dbg.findScripts();
+assertEq(scripts.indexOf(g1w.makeDebuggeeValue(g1.f).script) != -1, true);
+assertEq(scripts.indexOf(g2w.makeDebuggeeValue(g2.g).script) != -1, true);
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-07.js b/js/src/jit-test/tests/debug/Debugger-findScripts-07.js
new file mode 100644
index 0000000000..47132b1f13
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-07.js
@@ -0,0 +1,33 @@
+// findScripts can filter scripts by global.
+var g1 = newGlobal({newCompartment: true});
+var g2 = newGlobal({newCompartment: true});
+var g3 = newGlobal({newCompartment: true});
+
+var dbg = new Debugger();
+var g1w = dbg.addDebuggee(g1);
+var g2w = dbg.addDebuggee(g2);
+
+g1.eval('function f() {}');
+g2.eval('function g() {}');
+g2.eval('function h() {}');
+var g1fw = g1w.makeDebuggeeValue(g1.f);
+var g2gw = g2w.makeDebuggeeValue(g2.g);
+
+var scripts;
+
+scripts = dbg.findScripts({});
+assertEq(scripts.indexOf(g1fw.script) != -1, true);
+assertEq(scripts.indexOf(g2gw.script) != -1, true);
+
+scripts = dbg.findScripts({global: g1});
+assertEq(scripts.indexOf(g1fw.script) != -1, true);
+assertEq(scripts.indexOf(g2gw.script) != -1, false);
+
+scripts = dbg.findScripts({global: g2});
+assertEq(scripts.indexOf(g1fw.script) != -1, false);
+assertEq(scripts.indexOf(g2gw.script) != -1, true);
+
+scripts = dbg.findScripts({global: g3});
+// findScripts should only return debuggee scripts, and g3 isn't a
+// debuggee, so this should be completely empty.
+assertEq(scripts.length, 0);
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-08-script2 b/js/src/jit-test/tests/debug/Debugger-findScripts-08-script2
new file mode 100644
index 0000000000..40b3aafff5
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-08-script2
@@ -0,0 +1,3 @@
+// -*- mode: js2 -*-
+g1.eval('function g1g() {}');
+g2.eval('function g2g() {}');
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-08.js b/js/src/jit-test/tests/debug/Debugger-findScripts-08.js
new file mode 100644
index 0000000000..08b75b0216
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-08.js
@@ -0,0 +1,81 @@
+// Debugger.prototype.findScripts can filter scripts by URL.
+var g1 = newGlobal({newCompartment: true});
+var g2 = newGlobal({newCompartment: true});
+var g3 = newGlobal({newCompartment: true});
+
+// Define some functions whose url will be this test file.
+g1.eval('function g1f() {}');
+g2.eval('function g2f() {}');
+
+// Define some functions whose url will be a different file.
+url2 = scriptdir + "Debugger-findScripts-08-script2";
+load(url2);
+
+var dbg = new Debugger();
+var g1w = dbg.addDebuggee(g1);
+var g2w = dbg.addDebuggee(g2);
+var g3w = dbg.addDebuggee(g3);
+
+var g1fw = g1w.makeDebuggeeValue(g1.g1f);
+var g1gw = g1w.makeDebuggeeValue(g1.g1g);
+var g2fw = g2w.makeDebuggeeValue(g2.g2f);
+var g2gw = g2w.makeDebuggeeValue(g2.g2g);
+
+// Find the url of this file.
+url = g1fw.script.url;
+
+var scripts;
+
+scripts = dbg.findScripts({});
+assertEq(scripts.indexOf(g1fw.script) != -1, true);
+assertEq(scripts.indexOf(g1gw.script) != -1, true);
+assertEq(scripts.indexOf(g2fw.script) != -1, true);
+assertEq(scripts.indexOf(g2gw.script) != -1, true);
+
+scripts = dbg.findScripts({url:url});
+assertEq(scripts.indexOf(g1fw.script) != -1, true);
+assertEq(scripts.indexOf(g1gw.script) != -1, false);
+assertEq(scripts.indexOf(g2fw.script) != -1, true);
+assertEq(scripts.indexOf(g2gw.script) != -1, false);
+
+scripts = dbg.findScripts({url:url2});
+assertEq(scripts.indexOf(g1fw.script) != -1, false);
+assertEq(scripts.indexOf(g1gw.script) != -1, true);
+assertEq(scripts.indexOf(g2fw.script) != -1, false);
+assertEq(scripts.indexOf(g2gw.script) != -1, true);
+
+scripts = dbg.findScripts({url:url, global:g1});
+assertEq(scripts.indexOf(g1fw.script) != -1, true);
+assertEq(scripts.indexOf(g1gw.script) != -1, false);
+assertEq(scripts.indexOf(g2fw.script) != -1, false);
+assertEq(scripts.indexOf(g2gw.script) != -1, false);
+
+scripts = dbg.findScripts({url:url2, global:g1});
+assertEq(scripts.indexOf(g1fw.script) != -1, false);
+assertEq(scripts.indexOf(g1gw.script) != -1, true);
+assertEq(scripts.indexOf(g2fw.script) != -1, false);
+assertEq(scripts.indexOf(g2gw.script) != -1, false);
+
+scripts = dbg.findScripts({url:url, global:g2});
+assertEq(scripts.indexOf(g1fw.script) != -1, false);
+assertEq(scripts.indexOf(g1gw.script) != -1, false);
+assertEq(scripts.indexOf(g2fw.script) != -1, true);
+assertEq(scripts.indexOf(g2gw.script) != -1, false);
+
+scripts = dbg.findScripts({url:url2, global:g2});
+assertEq(scripts.indexOf(g1fw.script) != -1, false);
+assertEq(scripts.indexOf(g1gw.script) != -1, false);
+assertEq(scripts.indexOf(g2fw.script) != -1, false);
+assertEq(scripts.indexOf(g2gw.script) != -1, true);
+
+scripts = dbg.findScripts({url:"xlerb"}); // "XLERB"???
+assertEq(scripts.indexOf(g1fw.script) != -1, false);
+assertEq(scripts.indexOf(g1gw.script) != -1, false);
+assertEq(scripts.indexOf(g2fw.script) != -1, false);
+assertEq(scripts.indexOf(g2gw.script) != -1, false);
+
+scripts = dbg.findScripts({url:url, global:g3});
+assertEq(scripts.indexOf(g1fw.script) != -1, false);
+assertEq(scripts.indexOf(g1gw.script) != -1, false);
+assertEq(scripts.indexOf(g2fw.script) != -1, false);
+assertEq(scripts.indexOf(g2gw.script) != -1, false);
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-09.js b/js/src/jit-test/tests/debug/Debugger-findScripts-09.js
new file mode 100644
index 0000000000..b485415e9f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-09.js
@@ -0,0 +1,45 @@
+// Passing bad query properties to Debugger.prototype.findScripts throws.
+load(libdir + 'asserts.js');
+
+var dbg = new Debugger();
+var g = newGlobal();
+assertEq(dbg.findScripts().length, 0);
+assertEq(dbg.findScripts({}).length, 0);
+
+assertEq(dbg.findScripts({global:g}).length, 0);
+assertThrowsInstanceOf(function () { dbg.findScripts({global:null}); }, TypeError);
+assertThrowsInstanceOf(function () { dbg.findScripts({global:true}); }, TypeError);
+assertThrowsInstanceOf(function () { dbg.findScripts({global:4}); }, TypeError);
+assertThrowsInstanceOf(function () { dbg.findScripts({global:"I must have fruit!"}); }, TypeError);
+
+assertEq(dbg.findScripts({url:""}).length, 0);
+assertThrowsInstanceOf(function () { dbg.findScripts({url:null}); }, TypeError);
+assertThrowsInstanceOf(function () { dbg.findScripts({url:true}); }, TypeError);
+assertThrowsInstanceOf(function () { dbg.findScripts({url:4}); }, TypeError);
+assertThrowsInstanceOf(function () { dbg.findScripts({url:{}}); }, TypeError);
+
+assertEq(dbg.findScripts({url:"", line:1}).length, 0);
+assertEq(dbg.findScripts({url:"", line:numberToDouble(2)}).length, 0);
+
+// A 'line' property without a 'url' property is verboten.
+assertThrowsInstanceOf(function () { dbg.findScripts({line:1}); }, TypeError);
+
+assertThrowsInstanceOf(function () { dbg.findScripts({url:"",line:null}); }, TypeError);
+assertThrowsInstanceOf(function () { dbg.findScripts({url:"",line:{}}); }, TypeError);
+assertThrowsInstanceOf(function () { dbg.findScripts({url:"",line:true}); }, TypeError);
+assertThrowsInstanceOf(function () { dbg.findScripts({url:"",line:""}); }, TypeError);
+assertThrowsInstanceOf(function () { dbg.findScripts({url:"",line:0}); }, TypeError);
+assertThrowsInstanceOf(function () { dbg.findScripts({url:"",line:-1}); }, TypeError);
+assertThrowsInstanceOf(function () { dbg.findScripts({url:"",line:1.5}); }, TypeError);
+
+// Values of any type for 'innermost' are accepted.
+assertEq(dbg.findScripts({url:"", line:1, innermost:true}).length, 0);
+assertEq(dbg.findScripts({url:"", line:1, innermost:1}).length, 0);
+assertEq(dbg.findScripts({url:"", line:1, innermost:"yes"}).length, 0);
+assertEq(dbg.findScripts({url:"", line:1, innermost:{}}).length, 0);
+assertEq(dbg.findScripts({url:"", line:1, innermost:[]}).length, 0);
+
+// An 'innermost' property without 'url' and 'line' properties is verboten.
+assertThrowsInstanceOf(function () { dbg.findScripts({innermost:true}); }, TypeError);
+assertThrowsInstanceOf(function () { dbg.findScripts({innermost:true, line:1}); }, TypeError);
+assertThrowsInstanceOf(function () { dbg.findScripts({innermost:true, url:"foo"}); }, TypeError);
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-10.js b/js/src/jit-test/tests/debug/Debugger-findScripts-10.js
new file mode 100644
index 0000000000..f8ab68dda7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-10.js
@@ -0,0 +1,13 @@
+// Specifying a non-debuggee global in a Debugger.prototype.findScripts query should
+// cause the query to return no scripts.
+
+var g1 = newGlobal({newCompartment: true});
+g1.eval('function f(){}');
+
+var g2 = newGlobal({newCompartment: true});
+g2.eval('function g(){}');
+
+var dbg = new Debugger(g1);
+assertEq(dbg.findScripts({global:g1}).length > 0, true);
+assertEq(dbg.findScripts({global:g2}).length, 0);
+assertEq(dbg.findScripts({global:this}).length, 0);
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-11-script2 b/js/src/jit-test/tests/debug/Debugger-findScripts-11-script2
new file mode 100644
index 0000000000..7a170645e8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-11-script2
@@ -0,0 +1,18 @@
+// -*- mode: js2 -*-
+// Line numbers in this file are checked in Debugger-findScripts-11.js.
+
+// line 3
+
+var x = "";
+function f() {
+ x += "the map"; // line 8
+ return function g() {
+ return "to me what you have stolen"; // line 10
+ };
+}
+
+function h(x, y) {
+ if (x == 0) return y+1; // line 15
+ if (y == 0) return h(x-1, 1);
+ return h(x-1, h(x, y-1));
+}
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-11.js b/js/src/jit-test/tests/debug/Debugger-findScripts-11.js
new file mode 100644
index 0000000000..4cf9a54a88
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-11.js
@@ -0,0 +1,36 @@
+// Debugger.prototype.findScripts can filter scripts by line number.
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+
+var scriptname = scriptdir + 'Debugger-findScripts-11-script2';
+g.load(scriptname);
+
+var gfw = gw.makeDebuggeeValue(g.f);
+var ggw = gw.makeDebuggeeValue(g.f());
+var ghw = gw.makeDebuggeeValue(g.h);
+
+// Specifying a line outside of all functions screens out all function scripts.
+assertEq(dbg.findScripts({url:scriptname, line:3}).indexOf(gfw.script) != -1, false);
+assertEq(dbg.findScripts({url:scriptname, line:3}).indexOf(ggw.script) != -1, false);
+assertEq(dbg.findScripts({url:scriptname, line:3}).indexOf(ghw.script) != -1, false);
+
+// Specifying a different url screens out scripts, even when global and line match.
+assertEq(dbg.findScripts({url:"xlerb", line:8}).indexOf(gfw.script) != -1, false);
+assertEq(dbg.findScripts({url:"xlerb", line:8}).indexOf(ggw.script) != -1, false);
+assertEq(dbg.findScripts({url:"xlerb", line:8}).indexOf(ghw.script) != -1, false);
+
+// A line number within a function selects that function's script.
+assertEq(dbg.findScripts({url:scriptname, line:8}).indexOf(gfw.script) != -1, true);
+assertEq(dbg.findScripts({url:scriptname, line:8}).indexOf(ggw.script) != -1, false);
+assertEq(dbg.findScripts({url:scriptname, line:8}).indexOf(ghw.script) != -1, false);
+
+// A line number within a nested function selects all enclosing functions' scripts.
+assertEq(dbg.findScripts({url:scriptname, line:10}).indexOf(gfw.script) != -1, true);
+assertEq(dbg.findScripts({url:scriptname, line:10}).indexOf(ggw.script) != -1, true);
+assertEq(dbg.findScripts({url:scriptname, line:10}).indexOf(ghw.script) != -1, false);
+
+// A line number in a non-nested function selects that function.
+assertEq(dbg.findScripts({url:scriptname, line:15}).indexOf(gfw.script) != -1, false);
+assertEq(dbg.findScripts({url:scriptname, line:15}).indexOf(ggw.script) != -1, false);
+assertEq(dbg.findScripts({url:scriptname, line:15}).indexOf(ghw.script) != -1, true);
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-12-script1 b/js/src/jit-test/tests/debug/Debugger-findScripts-12-script1
new file mode 100644
index 0000000000..f9f484e970
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-12-script1
@@ -0,0 +1,19 @@
+// -*- mode: js2 -*-
+// Script for Debugger-findScripts-12.js to load.
+// Line numbers in this script are cited in the test.
+
+function f() {
+ // line 6
+ function ff() {
+ return "my wuv, I want you always beside me"; // line 8
+ };
+ ff.global = this;
+ return ff;
+};
+
+function g() {
+ return "to Oz"; // line 15
+}
+
+f.global = this;
+g.global = this;
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-12-script2 b/js/src/jit-test/tests/debug/Debugger-findScripts-12-script2
new file mode 100644
index 0000000000..3350c8ed29
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-12-script2
@@ -0,0 +1,19 @@
+// -*- mode: js2 -*-
+// Script for Debugger-findScripts-12.js to load.
+// Line numbers in this script are cited in the test, and must align with ...-script1.
+
+function h() {
+ // line 6
+ function hh() {
+ return "on investment"; // line 8
+ };
+ hh.global = this;
+ return hh;
+};
+
+function i() {
+ return "to innocence"; // line 15
+}
+
+h.global = this;
+i.global = this;
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-12.js b/js/src/jit-test/tests/debug/Debugger-findScripts-12.js
new file mode 100644
index 0000000000..912e3d3301
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-12.js
@@ -0,0 +1,129 @@
+// Debugger.prototype.findScripts can filter by global, url, and line number.
+
+// Two scripts, with different functions at the same line numbers.
+var url1 = scriptdir + 'Debugger-findScripts-12-script1';
+var url2 = scriptdir + 'Debugger-findScripts-12-script2';
+
+// Three globals: two with code, one with nothing.
+var g1 = newGlobal({newCompartment: true});
+g1.toSource = () => "[global g1]";
+g1.load(url1);
+g1.load(url2);
+var g2 = newGlobal({newCompartment: true});
+g2.toSource = () => "[global g2]";
+g2.load(url1);
+g2.load(url2);
+var g3 = newGlobal({newCompartment: true});
+
+var dbg = new Debugger(g1, g2, g3);
+
+function script(func) {
+ var gw = dbg.addDebuggee(func.global);
+ var script = gw.makeDebuggeeValue(func).script;
+ script.toString = function () {
+ return "[Debugger.Script for " + func.name + " in " + JSON.stringify(func.global) + "]";
+ };
+ return script;
+}
+
+// The function scripts we know of. There may be random eval scripts involved, but
+// we don't care about those.
+var allScripts = ([g1.f, g1.f(), g1.g, g1.h, g1.h(), g1.i,
+ g2.f, g2.f(), g2.g, g2.h, g2.h(), g2.i].map(script));
+
+// Search for scripts using |query|, expecting no members of allScripts
+// except those given in |expected| in the result. If |expected| is
+// omitted, expect no members of allScripts at all.
+function queryExpectOnly(query, expected) {
+ print();
+ print("queryExpectOnly(" + JSON.stringify(query) + ")");
+ var scripts = dbg.findScripts(query);
+ var present = allScripts.filter(function (s) { return scripts.indexOf(s) != -1; });
+ if (expected) {
+ expected = expected.map(script);
+ expected.forEach(function (s) {
+ if (present.indexOf(s) == -1)
+ assertEq(s + " not present", "is present");
+ });
+ present.forEach(function (s) {
+ if (expected.indexOf(s) == -1)
+ assertEq(s + " is present", "not present");
+ });
+ } else {
+ assertEq(present.length, 0);
+ }
+}
+
+// We have twelve functions: two globals, each with two urls, each
+// defining three functions. Show that all the different combinations of
+// query parameters select what they should.
+
+// There are gaps in the pattern:
+// - You can only filter by line if you're also filtering by url.
+// - You can't ask for only the innermost scripts unless you're filtering by line.
+
+// Filtering by global, url, and line produces one function, or two
+// where they are nested.
+queryExpectOnly({ global:g1, url:url1, line: 6 }, [g1.f ]);
+queryExpectOnly({ global:g1, url:url1, line: 8 }, [g1.f, g1.f()]);
+queryExpectOnly({ global:g1, url:url1, line: 15 }, [g1.g ]);
+queryExpectOnly({ global:g1, url:url2, line: 6 }, [g1.h ]);
+queryExpectOnly({ global:g1, url:url2, line: 8 }, [g1.h, g1.h()]);
+queryExpectOnly({ global:g1, url:url2, line: 15 }, [g1.i ]);
+queryExpectOnly({ global:g2, url:url1, line: 6 }, [g2.f ]);
+queryExpectOnly({ global:g2, url:url1, line: 8 }, [g2.f, g2.f()]);
+queryExpectOnly({ global:g2, url:url1, line: 15 }, [g2.g ]);
+queryExpectOnly({ global:g2, url:url2, line: 6 }, [g2.h ]);
+queryExpectOnly({ global:g2, url:url2, line: 8 }, [g2.h, g2.h()]);
+queryExpectOnly({ global:g2, url:url2, line: 15 }, [g2.i ]);
+
+// Filtering by global, url, and line, and requesting only the innermost
+// function at each point, should produce only one function.
+queryExpectOnly({ global:g1, url:url1, line: 6, innermost: true }, [g1.f ]);
+queryExpectOnly({ global:g1, url:url1, line: 8, innermost: true }, [g1.f()]);
+queryExpectOnly({ global:g1, url:url1, line: 15, innermost: true }, [g1.g ]);
+queryExpectOnly({ global:g1, url:url2, line: 6, innermost: true }, [g1.h ]);
+queryExpectOnly({ global:g1, url:url2, line: 8, innermost: true }, [g1.h()]);
+queryExpectOnly({ global:g1, url:url2, line: 15, innermost: true }, [g1.i ]);
+queryExpectOnly({ global:g2, url:url1, line: 6, innermost: true }, [g2.f ]);
+queryExpectOnly({ global:g2, url:url1, line: 8, innermost: true }, [g2.f()]);
+queryExpectOnly({ global:g2, url:url1, line: 15, innermost: true }, [g2.g ]);
+queryExpectOnly({ global:g2, url:url2, line: 6, innermost: true }, [g2.h ]);
+queryExpectOnly({ global:g2, url:url2, line: 8, innermost: true }, [g2.h()]);
+queryExpectOnly({ global:g2, url:url2, line: 15, innermost: true }, [g2.i ]);
+
+// Filtering by url and global should produce sets of three scripts.
+queryExpectOnly({ global:g1, url:url1 }, [g1.f, g1.f(), g1.g]);
+queryExpectOnly({ global:g1, url:url2 }, [g1.h, g1.h(), g1.i]);
+queryExpectOnly({ global:g2, url:url1 }, [g2.f, g2.f(), g2.g]);
+queryExpectOnly({ global:g2, url:url2 }, [g2.h, g2.h(), g2.i]);
+
+// Filtering by url and line, innermost-only, should produce sets of two scripts,
+// or four where there are nested functions.
+queryExpectOnly({ url:url1, line: 6 }, [g1.f, g2.f ]);
+queryExpectOnly({ url:url1, line: 8 }, [g1.f, g1.f(), g2.f, g2.f()]);
+queryExpectOnly({ url:url1, line:15 }, [g1.g, g2.g ]);
+queryExpectOnly({ url:url2, line: 6 }, [g1.h, g2.h ]);
+queryExpectOnly({ url:url2, line: 8 }, [g1.h, g1.h(), g2.h, g2.h()]);
+queryExpectOnly({ url:url2, line:15 }, [g1.i, g2.i ]);
+
+// Filtering by url and line, and requesting only the innermost scripts,
+// should always produce pairs of scripts.
+queryExpectOnly({ url:url1, line: 6, innermost: true }, [g1.f, g2.f ]);
+queryExpectOnly({ url:url1, line: 8, innermost: true }, [g1.f(), g2.f()]);
+queryExpectOnly({ url:url1, line:15, innermost: true }, [g1.g, g2.g ]);
+queryExpectOnly({ url:url2, line: 6, innermost: true }, [g1.h, g2.h ]);
+queryExpectOnly({ url:url2, line: 8, innermost: true }, [g1.h(), g2.h()]);
+queryExpectOnly({ url:url2, line:15, innermost: true }, [g1.i, g2.i ]);
+
+// Filtering by global only should produce sets of six scripts.
+queryExpectOnly({ global:g1 }, [g1.f, g1.f(), g1.g, g1.h, g1.h(), g1.i]);
+queryExpectOnly({ global:g2 }, [g2.f, g2.f(), g2.g, g2.h, g2.h(), g2.i]);
+
+// Filtering by url should produce sets of six scripts.
+queryExpectOnly({ url:url1 }, [g1.f, g1.f(), g1.g, g2.f, g2.f(), g2.g]);
+queryExpectOnly({ url:url2 }, [g1.h, g1.h(), g1.i, g2.h, g2.h(), g2.i]);
+
+// Filtering by no axes should produce all twelve scripts.
+queryExpectOnly({}, [g1.f, g1.f(), g1.g, g1.h, g1.h(), g1.i,
+ g2.f, g2.f(), g2.g, g2.h, g2.h(), g2.i]);
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-14.js b/js/src/jit-test/tests/debug/Debugger-findScripts-14.js
new file mode 100644
index 0000000000..f02a1b4e11
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-14.js
@@ -0,0 +1,30 @@
+// Debugger.prototype.findScripts can find the innermost script at a given
+// source location.
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+
+function script(f) {
+ return gw.makeDebuggeeValue(f).script;
+}
+
+function arrayIsOnly(array, element) {
+ return array.length == 1 && array[0] === element;
+}
+
+url = scriptdir + 'Debugger-findScripts-14.script1';
+g.load(url);
+
+var scripts;
+
+// When we're doing 'innermost' queries, we don't have to worry about finding
+// random eval scripts: we should get exactly one script, for the function
+// covering that line.
+scripts = dbg.findScripts({url:url, line:4, innermost:true});
+assertEq(arrayIsOnly(scripts, script(g.f)), true);
+
+scripts = dbg.findScripts({url:url, line:6, innermost:true});
+assertEq(arrayIsOnly(scripts, script(g.f())), true);
+
+scripts = dbg.findScripts({url:url, line:8, innermost:true});
+assertEq(arrayIsOnly(scripts, script(g.f()())), true);
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-14.script1 b/js/src/jit-test/tests/debug/Debugger-findScripts-14.script1
new file mode 100644
index 0000000000..00da459fc1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-14.script1
@@ -0,0 +1,12 @@
+// -*- mode:js2 -*-
+
+function f() {
+ var x = 1; // line 4
+ return function g() {
+ var y = 2; // line 6
+ return function h() {
+ var z = 3; // line 8
+ return x+y+z;
+ };
+ };
+}
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-15.js b/js/src/jit-test/tests/debug/Debugger-findScripts-15.js
new file mode 100644
index 0000000000..d9cd8fb966
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-15.js
@@ -0,0 +1,9 @@
+// findScripts finds non-compile-and-go scripts
+
+var g = newGlobal({newCompartment: true});
+g.evaluate("function f(x) { return x + 1; }");
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+var s = dbg.findScripts();
+var fw = gw.getOwnPropertyDescriptor("f").value;
+assertEq(s.indexOf(fw.script) !== -1, true);
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-16.js b/js/src/jit-test/tests/debug/Debugger-findScripts-16.js
new file mode 100644
index 0000000000..9d735d9186
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-16.js
@@ -0,0 +1,12 @@
+// Bug 744731 - findScripts() finds active debugger executeInGlobal scripts.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ hits++;
+ assertEq(dbg.findScripts().indexOf(dbg.getNewestFrame().script) !== -1, true);
+};
+gw.executeInGlobal("debugger;");
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-17.js b/js/src/jit-test/tests/debug/Debugger-findScripts-17.js
new file mode 100644
index 0000000000..3c1fc56937
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-17.js
@@ -0,0 +1,15 @@
+// Bug 744731 - findScripts() finds active debugger frame.eval scripts.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ dbg.onDebuggerStatement = function (frame) {
+ hits++;
+ assertEq(dbg.findScripts().indexOf(dbg.getNewestFrame().script) !== -1, true);
+ };
+ frame.eval("debugger;");
+};
+g.eval("debugger;");
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-18.js b/js/src/jit-test/tests/debug/Debugger-findScripts-18.js
new file mode 100644
index 0000000000..ea5ee0d318
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-18.js
@@ -0,0 +1,46 @@
+// In a debuggee with multiple scripts with varying displayURLs (aka //#
+// sourceURL), findScripts can filter by displayURL.
+
+var g = newGlobal({newCompartment: true});
+
+g.eval("function f(){} //# sourceURL=f.js");
+g.eval("function g(){} //# sourceURL=g.js");
+g.eval("function h(){}");
+
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+var fw = gw.makeDebuggeeValue(g.f);
+var ggw = gw.makeDebuggeeValue(g.g);
+var hw = gw.makeDebuggeeValue(g.h);
+
+var fScripts = dbg.findScripts({ displayURL: "f.js" });
+assertEq(fScripts.indexOf(fw.script) != -1, true);
+assertEq(fScripts.indexOf(ggw.script), -1);
+assertEq(fScripts.indexOf(hw.script), -1);
+
+
+fScripts = dbg.findScripts({ displayURL: "f.js",
+ line: 1 });
+assertEq(fScripts.indexOf(fw.script) != -1, true);
+assertEq(fScripts.indexOf(ggw.script), -1);
+assertEq(fScripts.indexOf(hw.script), -1);
+
+var gScripts = dbg.findScripts({ displayURL: "g.js" });
+assertEq(gScripts.indexOf(ggw.script) != -1, true);
+assertEq(gScripts.indexOf(fw.script), -1);
+assertEq(gScripts.indexOf(hw.script), -1);
+
+var allScripts = dbg.findScripts();
+assertEq(allScripts.indexOf(fw.script) != -1, true);
+assertEq(allScripts.indexOf(ggw.script) != -1, true);
+assertEq(allScripts.indexOf(hw.script) != -1, true);
+
+try {
+ dbg.findScripts({ displayURL: 3 });
+ // Should never get here because the above line should throw
+ // JSMSG_UNEXPECTED_TYPE.
+ assertEq(true, false);
+} catch(e) {
+ assertEq(e.name, "TypeError");
+ assertEq(e.message.includes("displayURL"), true);
+}
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-19.js b/js/src/jit-test/tests/debug/Debugger-findScripts-19.js
new file mode 100644
index 0000000000..46021a0bbc
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-19.js
@@ -0,0 +1,5 @@
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+try { g.eval('function drag(ev) {'); } catch (ex) { }
+for (s of dbg.findScripts())
+ s.lineCount;
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-20.js b/js/src/jit-test/tests/debug/Debugger-findScripts-20.js
new file mode 100644
index 0000000000..04c73c0972
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-20.js
@@ -0,0 +1,20 @@
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+
+g.eval('function f(){}');
+
+var o = gw.makeDebuggeeValue(g.f);
+
+var allScripts = dbg.findScripts();
+var scripts = dbg.findScripts({
+ source: o.script.source
+});
+assertEq(scripts.length, allScripts.length);
+assertEq(scripts.indexOf(o.script) !== -1, true);
+
+scripts = dbg.findScripts({
+ source: o.script.source,
+ line: 1
+});
+assertEq(scripts.indexOf(o.script) !== -1, true);
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-22.js b/js/src/jit-test/tests/debug/Debugger-findScripts-22.js
new file mode 100644
index 0000000000..9f2884a325
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-22.js
@@ -0,0 +1,8 @@
+// Bug 1239813: Don't let compartments get GC'd while findScripts is holding
+// them in its ScriptQuery's hash set.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+dbg.addDebuggee(g);
+g = newGlobal({sameZoneAs: g.Math});
+dbg.findScripts({get source() { gc(); return undefined; }});
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-23.js b/js/src/jit-test/tests/debug/Debugger-findScripts-23.js
new file mode 100644
index 0000000000..bb45365a69
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-23.js
@@ -0,0 +1,21 @@
+// |jit-test| skip-if: isLcovEnabled()
+
+// If a script is (re)lazified, findScripts should not delazify it.
+
+var dbg = new Debugger();
+
+var g = newGlobal({newCompartment: true});
+g.eval('function f(){}');
+assertEq(g.eval('isLazyFunction(f)'), true);
+
+dbg.addDebuggee(g);
+dbg.findScripts();
+assertEq(g.eval('isLazyFunction(f)'), true);
+
+dbg.removeAllDebuggees();
+relazifyFunctions();
+assertEq(g.eval('isLazyFunction(f)'), true);
+
+dbg.addDebuggee(g);
+var scripts = dbg.findScripts();
+assertEq(g.eval('isLazyFunction(f)'), true);
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-24.js b/js/src/jit-test/tests/debug/Debugger-findScripts-24.js
new file mode 100644
index 0000000000..54b37d811f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-24.js
@@ -0,0 +1,35 @@
+// findScripts should reject Debugger.Source objects from other Debuggers.
+
+load(libdir + 'asserts.js');
+
+var g = newGlobal({newCompartment: true});
+g.evaluate(`function f() { print("earth/heart/hater"); }`,
+ { lineNumber: 1800 });
+
+var dbg1 = new Debugger;
+var gDO1 = dbg1.addDebuggee(g);
+var fDO1 = gDO1.getOwnPropertyDescriptor('f').value;
+assertEq(fDO1.script.source instanceof Debugger.Source, true);
+
+var dbg2 = new Debugger;
+var gDO2 = dbg2.addDebuggee(g);
+var fDO2 = gDO2.getOwnPropertyDescriptor('f').value;
+assertEq(fDO2.script.source instanceof Debugger.Source, true);
+
+assertEq(fDO1.script.source !== fDO2.script.source, true);
+
+// findScripts should reject Debugger.Source objects that don't belong to the
+// Debugger it's being invoked on.
+assertThrowsInstanceOf(() => dbg1.findScripts({ source: fDO2.script.source }),
+ TypeError);
+assertThrowsInstanceOf(() => dbg2.findScripts({ source: fDO1.script.source }),
+ TypeError);
+
+// findScripts should reject Debugger.Source.prototype.
+assertThrowsInstanceOf(() => dbg1.findScripts({ source: Debugger.Source.prototype }),
+ TypeError);
+
+// These should not throw, and should return something, but we're not going to
+// be picky about exactly what, given findScript's sensitivity to GC.
+dbg1.findScripts({ source: fDO1.script.source });
+dbg2.findScripts({ source: fDO2.script.source });
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-25.js b/js/src/jit-test/tests/debug/Debugger-findScripts-25.js
new file mode 100644
index 0000000000..3985b78c53
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-25.js
@@ -0,0 +1,14 @@
+// In a debugger with multiple debuggees, findScripts finds scripts across all debuggees.
+var g1 = newGlobal({newCompartment: true});
+var g2 = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var g1w = dbg.addDebuggee(g1);
+var g2w = dbg.addDebuggee(g2);
+
+// Add eval() to make functions non-relazifiable.
+g1.eval('function f() { eval(""); }');
+g2.eval('function g() { eval(""); }');
+
+var scripts = dbg.findScripts();
+assertEq(scripts.indexOf(g1w.makeDebuggeeValue(g1.f).script) != -1, true);
+assertEq(scripts.indexOf(g2w.makeDebuggeeValue(g2.g).script) != -1, true);
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-26.js b/js/src/jit-test/tests/debug/Debugger-findScripts-26.js
new file mode 100644
index 0000000000..1c1510aa4f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-26.js
@@ -0,0 +1,18 @@
+// |jit-test| skip-if: !('oomTest' in this)
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+
+g.eval(`
+function f() {
+ function inner() {
+ var x;
+ }
+}
+`);
+
+let url = thisFilename();
+let line = 4;
+
+oomTest(() => { dbg.findScripts({url, line}) });
+oomTest(() => { dbg.findScripts({url, line, innermost: true}) });
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-27.js b/js/src/jit-test/tests/debug/Debugger-findScripts-27.js
new file mode 100644
index 0000000000..2a6513011b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-27.js
@@ -0,0 +1,22 @@
+// |jit-test| skip-if: isLcovEnabled()
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+
+g.eval(`
+function f() {
+ function inner() {
+ var x;
+ }
+}
+`);
+
+// GC will collect top-level script of the eval
+gc();
+gc();
+
+let url = thisFilename();
+let line = 4;
+
+// Function `f` and `inner` should still match
+assertEq(dbg.findScripts({url, line}).length, 2);
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-28.js b/js/src/jit-test/tests/debug/Debugger-findScripts-28.js
new file mode 100644
index 0000000000..30097bac3d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-28.js
@@ -0,0 +1,26 @@
+// |jit-test| skip-if: isLcovEnabled()
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+
+g.eval(`
+function f() {
+ function inner() {
+ var x;
+ }
+ function asm_mod() {
+ "use asm";
+ function mtd() {}
+ return { mtd: mtd }
+ }
+}
+`);
+
+gc();
+gc();
+
+let url = thisFilename();
+let line = 4;
+
+// Function `f` and `inner` should still match
+assertEq(dbg.findScripts({url, line}).length, 2);
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-29.js b/js/src/jit-test/tests/debug/Debugger-findScripts-29.js
new file mode 100644
index 0000000000..c0a4d5e604
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-29.js
@@ -0,0 +1,21 @@
+// |jit-test| skip-if: isLcovEnabled()
+
+// If the specified line is the next line after the function,
+// the function shouldn't match.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+
+g.eval(`
+function f() {
+}
+`);
+
+gc();
+gc();
+
+let url = thisFilename();
+
+assertEq(dbg.findScripts({url, line: 3}).length, 1);
+assertEq(dbg.findScripts({url, line: 4}).length, 0);
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-30.js b/js/src/jit-test/tests/debug/Debugger-findScripts-30.js
new file mode 100644
index 0000000000..01b7a624c2
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-30.js
@@ -0,0 +1,21 @@
+// |jit-test| skip-if: isLcovEnabled()
+
+// If the specified line is the next line after the function,
+// the function shouldn't match.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+
+g.eval(`
+var a = x => x +
+1;
+`);
+
+gc();
+gc();
+
+let url = thisFilename();
+
+assertEq(dbg.findScripts({url, line: 3}).length, 1);
+assertEq(dbg.findScripts({url, line: 4}).length, 0);
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-31.js b/js/src/jit-test/tests/debug/Debugger-findScripts-31.js
new file mode 100644
index 0000000000..6be8a2d2fd
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-31.js
@@ -0,0 +1,21 @@
+// |jit-test| skip-if: isLcovEnabled()
+
+// If the specified line is the next line after the function,
+// the function shouldn't match.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+
+g.eval(`
+var a = x => (x + 1
+);
+`);
+
+gc();
+gc();
+
+let url = thisFilename();
+
+assertEq(dbg.findScripts({url, line: 3}).length, 1);
+assertEq(dbg.findScripts({url, line: 4}).length, 0);
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-delazify.js b/js/src/jit-test/tests/debug/Debugger-findScripts-delazify.js
new file mode 100644
index 0000000000..554b5dd2c8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-delazify.js
@@ -0,0 +1,485 @@
+// |jit-test| skip-if: isLcovEnabled()
+
+// findScript should try to avoid delazifying unnecessarily.
+
+function newTestcase(code) {
+ var g = newGlobal({newCompartment: true});
+ var dbg = new Debugger();
+ var gw = dbg.addDebuggee(g);
+
+ var lines = code.split('\n');
+ // Returns the line number of the line with "<= line".
+ // + 1 for 1-origin.
+ var line = lines.findIndex(x => x.includes("<= line")) + 1;
+
+ g.eval(code);
+
+ // Functions are relazified, and the top-level script is dropped.
+ relazifyFunctions();
+
+ return [dbg, g, line];
+}
+
+var url = thisFilename();
+var dbg, g, line, scripts;
+
+// If the specified line is inside the function body, only the function should
+// be delazified.
+[dbg, g, line] = newTestcase(`
+function f1() {
+}
+function f2() {
+// <= line
+}
+function f3() {
+}
+`);
+
+assertEq(g.eval(`isLazyFunction(f1)`), true);
+assertEq(g.eval(`isLazyFunction(f2)`), true);
+assertEq(g.eval(`isLazyFunction(f3)`), true);
+
+scripts = dbg.findScripts({url, line});
+assertEq(scripts.length, 1);
+assertEq(scripts.map(s => s.displayName).sort().join(","), "f2");
+
+assertEq(g.eval(`isLazyFunction(f1)`), true);
+assertEq(g.eval(`isLazyFunction(f2)`), false);
+assertEq(g.eval(`isLazyFunction(f3)`), true);
+
+// If the functions starts at the specified line, the function shouldn't be
+// the first function to delazify.
+[dbg, g, line] = newTestcase(`
+function f1() {
+}
+function f2() {
+}
+function f3() { // <= line
+}
+function f4() {
+}
+`);
+
+assertEq(g.eval(`isLazyFunction(f1)`), true);
+assertEq(g.eval(`isLazyFunction(f2)`), true);
+assertEq(g.eval(`isLazyFunction(f3)`), true);
+assertEq(g.eval(`isLazyFunction(f4)`), true);
+
+scripts = dbg.findScripts({url, line});
+assertEq(scripts.length, 1);
+assertEq(scripts.map(s => s.displayName).sort().join(","), "f3");
+
+assertEq(g.eval(`isLazyFunction(f1)`), true);
+// f2 is delazified because f3 cannot be the first function to delazify.
+assertEq(g.eval(`isLazyFunction(f2)`), false);
+assertEq(g.eval(`isLazyFunction(f3)`), false);
+assertEq(g.eval(`isLazyFunction(f4)`), true);
+
+// Multiple functions in the specified line, and one of them starts before
+// the specified line.
+// All functions should be returned, and others shouldn't be delazified.
+[dbg, g, line] = newTestcase(`
+function f1() {}
+function f2() {
+} function f3() {} function f4() {} function f5() { // <= line
+}
+function f6() {}
+`);
+
+assertEq(g.eval(`isLazyFunction(f1)`), true);
+assertEq(g.eval(`isLazyFunction(f2)`), true);
+assertEq(g.eval(`isLazyFunction(f3)`), true);
+assertEq(g.eval(`isLazyFunction(f4)`), true);
+assertEq(g.eval(`isLazyFunction(f5)`), true);
+assertEq(g.eval(`isLazyFunction(f6)`), true);
+
+scripts = dbg.findScripts({url, line});
+assertEq(scripts.length, 4);
+assertEq(scripts.map(s => s.displayName).sort().join(","), "f2,f3,f4,f5");
+
+assertEq(g.eval(`isLazyFunction(f1)`), true);
+assertEq(g.eval(`isLazyFunction(f2)`), false);
+assertEq(g.eval(`isLazyFunction(f3)`), false);
+assertEq(g.eval(`isLazyFunction(f4)`), false);
+assertEq(g.eval(`isLazyFunction(f5)`), false);
+assertEq(g.eval(`isLazyFunction(f6)`), true);
+
+// The same rule should apply to inner functions.
+[dbg, g, line] = newTestcase(`
+function f1() {}
+function f2() {
+ function g1() {
+ }
+ function g2() {
+ function h1() {}
+ function h2() {
+ } function h3() {} function h4() {} function h5() { // <= line
+ }
+ function h6() {}
+
+ return [h1, h2, h3, h4, h5, h6];
+ }
+ function g3() {
+ }
+
+ return [g1, g2, g3];
+}
+function f3() {}
+`);
+
+assertEq(g.eval(`isLazyFunction(f1)`), true);
+assertEq(g.eval(`isLazyFunction(f2)`), true);
+assertEq(g.eval(`isLazyFunction(f3)`), true);
+
+scripts = dbg.findScripts({url, line});
+assertEq(scripts.length, 6);
+assertEq(scripts.map(s => s.displayName).sort().join(","), "f2,g2,h2,h3,h4,h5");
+
+assertEq(g.eval(`isLazyFunction(f1)`), true);
+assertEq(g.eval(`isLazyFunction(f2)`), false);
+assertEq(g.eval(`isLazyFunction(f3)`), true);
+g.eval(`var [g1, g2, g3] = f2();`);
+assertEq(g.eval(`isLazyFunction(g1)`), true);
+assertEq(g.eval(`isLazyFunction(g2)`), false);
+assertEq(g.eval(`isLazyFunction(g3)`), true);
+g.eval(`var [h1, h2, h3, h4, h5, h6] = g2();`);
+assertEq(g.eval(`isLazyFunction(h1)`), true);
+assertEq(g.eval(`isLazyFunction(h2)`), false);
+assertEq(g.eval(`isLazyFunction(h3)`), false);
+assertEq(g.eval(`isLazyFunction(h4)`), false);
+assertEq(g.eval(`isLazyFunction(h5)`), false);
+assertEq(g.eval(`isLazyFunction(h6)`), true);
+
+// The same rule should apply to functions inside parameter expression.
+[dbg, g, line] = newTestcase(`
+function f1(
+ a = function g1() {},
+ b = function g2() {
+ }, c = function g3() {}, d = function g4() { // <= line
+ },
+ e = function g5() {},
+) {
+ return [a, b, c, d, e];
+}
+`);
+
+assertEq(g.eval(`isLazyFunction(f1)`), true);
+
+scripts = dbg.findScripts({url, line});
+assertEq(scripts.length, 4);
+assertEq(scripts.map(s => s.displayName).sort().join(","), "f1,g2,g3,g4");
+
+assertEq(g.eval(`isLazyFunction(f1)`), false);
+
+g.eval(`var [g1, g2, g3, g4, g5] = f1();`);
+
+assertEq(g.eval(`isLazyFunction(g1)`), true);
+assertEq(g.eval(`isLazyFunction(g2)`), false);
+assertEq(g.eval(`isLazyFunction(g3)`), false);
+assertEq(g.eval(`isLazyFunction(g4)`), false);
+assertEq(g.eval(`isLazyFunction(g5)`), true);
+
+// The same should apply to function inside method with computed property.
+[dbg, g, line] = newTestcase(`
+var f1, f2, f3;
+var O = {
+ [(f1 = () => 0, "m1")]() {
+ },
+ [(f2 = () => 0, "m2")](p2) { // <= line
+ },
+ [(f3 = () => 0, "m3")]() {
+ },
+}
+`);
+
+assertEq(g.eval(`isLazyFunction(f1)`), true);
+assertEq(g.eval(`isLazyFunction(O.m2)`), true);
+assertEq(g.eval(`isLazyFunction(f2)`), true);
+assertEq(g.eval(`isLazyFunction(O.m1)`), true);
+assertEq(g.eval(`isLazyFunction(f3)`), true);
+assertEq(g.eval(`isLazyFunction(O.m3)`), true);
+
+scripts = dbg.findScripts({url, line});
+assertEq(scripts.length, 2);
+assertEq(scripts.map(s => s.displayName).sort().join(","), "f2,");
+// Use parameterNames because displayName isn't set for computed property.
+assertEq(scripts.map(s => `${s.displayName}(${s.parameterNames.join(",")})`)
+ .sort().join(","), "f2(),undefined(p2)");
+
+assertEq(g.eval(`isLazyFunction(f1)`), true);
+// m1 is delazified because f2 cannot be the first function to delazify.
+assertEq(g.eval(`isLazyFunction(O.m1)`), false);
+assertEq(g.eval(`isLazyFunction(f2)`), false);
+assertEq(g.eval(`isLazyFunction(O.m2)`), false);
+assertEq(g.eval(`isLazyFunction(f3)`), true);
+assertEq(g.eval(`isLazyFunction(O.m3)`), true);
+
+[dbg, g, line] = newTestcase(`
+var f1, f2, f3;
+var O = {
+ [(f1 = () => 0, "m1")]() {
+ },
+ [(f2 = () => 0 +
+ 1, "m2")](p2) { // <= line
+ },
+ [(f3 = () => 0, "m3")]() {
+ },
+}
+`);
+
+assertEq(g.eval(`isLazyFunction(f1)`), true);
+assertEq(g.eval(`isLazyFunction(O.m1)`), true);
+assertEq(g.eval(`isLazyFunction(f2)`), true);
+assertEq(g.eval(`isLazyFunction(O.m2)`), true);
+assertEq(g.eval(`isLazyFunction(f3)`), true);
+assertEq(g.eval(`isLazyFunction(O.m3)`), true);
+
+scripts = dbg.findScripts({url, line});
+assertEq(scripts.length, 2);
+assertEq(scripts.map(s => `${s.displayName}(${s.parameterNames.join(",")})`)
+ .sort().join(","), "f2(),undefined(p2)");
+
+assertEq(g.eval(`isLazyFunction(f1)`), true);
+assertEq(g.eval(`isLazyFunction(O.m1)`), true);
+assertEq(g.eval(`isLazyFunction(f2)`), false);
+assertEq(g.eval(`isLazyFunction(O.m2)`), false);
+assertEq(g.eval(`isLazyFunction(f3)`), true);
+assertEq(g.eval(`isLazyFunction(O.m3)`), true);
+
+// Class constructor shouldn't be delazified even if methods match.
+[dbg, g, line] = newTestcase(`
+// Use variable to access across eval.
+var C = class {
+ constructor() {
+ }
+ m1() {}
+ m2() {
+ } m3() {} m4() { // <= line
+ }
+ m5() {}
+}
+`);
+
+assertEq(g.eval(`isLazyFunction(C)`), true);
+assertEq(g.eval(`isLazyFunction(C.prototype.m1)`), true);
+assertEq(g.eval(`isLazyFunction(C.prototype.m2)`), true);
+assertEq(g.eval(`isLazyFunction(C.prototype.m3)`), true);
+assertEq(g.eval(`isLazyFunction(C.prototype.m4)`), true);
+assertEq(g.eval(`isLazyFunction(C.prototype.m5)`), true);
+
+scripts = dbg.findScripts({url, line});
+assertEq(scripts.length, 3);
+assertEq(scripts.map(s => s.displayName).sort().join(","), "m2,m3,m4");
+
+assertEq(g.eval(`isLazyFunction(C)`), true);
+assertEq(g.eval(`isLazyFunction(C.prototype.m1)`), true);
+assertEq(g.eval(`isLazyFunction(C.prototype.m2)`), false);
+assertEq(g.eval(`isLazyFunction(C.prototype.m3)`), false);
+assertEq(g.eval(`isLazyFunction(C.prototype.m4)`), false);
+assertEq(g.eval(`isLazyFunction(C.prototype.m5)`), true);
+
+// If the line is placed before sourceStart, the function shouldn't match,
+// and the function shouldn't be delazified.
+[dbg, g, line] = newTestcase(`
+function f1() {
+}
+function f2() {
+}
+function
+// <= line
+f3() {
+}
+function f4() {
+}
+`);
+
+assertEq(g.eval(`isLazyFunction(f1)`), true);
+assertEq(g.eval(`isLazyFunction(f2)`), true);
+assertEq(g.eval(`isLazyFunction(f3)`), true);
+assertEq(g.eval(`isLazyFunction(f4)`), true);
+
+scripts = dbg.findScripts({url, line});
+assertEq(scripts.length, 0);
+
+assertEq(g.eval(`isLazyFunction(f1)`), true);
+// f2 is delazified because it's the first function before the specified line.
+assertEq(g.eval(`isLazyFunction(f2)`), false);
+assertEq(g.eval(`isLazyFunction(f3)`), true);
+assertEq(g.eval(`isLazyFunction(f4)`), true);
+
+[dbg, g, line] = newTestcase(`
+function f1() {
+}
+function f2() {
+}
+function f3
+// <= line
+() {
+}
+function f4() {
+}
+`);
+
+assertEq(g.eval(`isLazyFunction(f1)`), true);
+assertEq(g.eval(`isLazyFunction(f2)`), true);
+assertEq(g.eval(`isLazyFunction(f3)`), true);
+assertEq(g.eval(`isLazyFunction(f4)`), true);
+
+scripts = dbg.findScripts({url, line});
+assertEq(scripts.length, 0);
+
+assertEq(g.eval(`isLazyFunction(f1)`), true);
+// f2 is delazified because it's the first function before the specified line.
+assertEq(g.eval(`isLazyFunction(f2)`), false);
+assertEq(g.eval(`isLazyFunction(f3)`), true);
+assertEq(g.eval(`isLazyFunction(f4)`), true);
+
+[dbg, g, line] = newTestcase(`
+function f1() {
+}
+function f2() {
+}
+function f3
+( // <= line
+) {
+}
+function f4() {
+}
+`);
+
+assertEq(g.eval(`isLazyFunction(f1)`), true);
+assertEq(g.eval(`isLazyFunction(f2)`), true);
+assertEq(g.eval(`isLazyFunction(f3)`), true);
+assertEq(g.eval(`isLazyFunction(f4)`), true);
+
+scripts = dbg.findScripts({url, line});
+assertEq(scripts.length, 1);
+assertEq(scripts.map(s => s.displayName).sort().join(","), "f3");
+
+assertEq(g.eval(`isLazyFunction(f1)`), true);
+// f2 is delazified because it's the first function _before_ the specified line.
+assertEq(g.eval(`isLazyFunction(f2)`), false);
+assertEq(g.eval(`isLazyFunction(f3)`), false);
+assertEq(g.eval(`isLazyFunction(f4)`), true);
+
+[dbg, g, line] = newTestcase(`
+function f1() {
+}
+function f2() {
+}
+function f3
+(
+// <= line
+) {
+}
+function f4() {
+}
+`);
+
+assertEq(g.eval(`isLazyFunction(f1)`), true);
+assertEq(g.eval(`isLazyFunction(f2)`), true);
+assertEq(g.eval(`isLazyFunction(f3)`), true);
+assertEq(g.eval(`isLazyFunction(f4)`), true);
+
+scripts = dbg.findScripts({url, line});
+assertEq(scripts.length, 1);
+assertEq(scripts.map(s => s.displayName).sort().join(","), "f3");
+
+assertEq(g.eval(`isLazyFunction(f1)`), true);
+assertEq(g.eval(`isLazyFunction(f2)`), true);
+assertEq(g.eval(`isLazyFunction(f3)`), false);
+assertEq(g.eval(`isLazyFunction(f4)`), true);
+
+// If the specified line is the next line after the function ends,
+// nothing should match, but the function should be delazified.
+[dbg, g, line] = newTestcase(`
+function f1() {
+}
+function f2() {
+}
+// <= line
+function f3() {
+}
+`);
+
+assertEq(g.eval(`isLazyFunction(f1)`), true);
+assertEq(g.eval(`isLazyFunction(f2)`), true);
+assertEq(g.eval(`isLazyFunction(f3)`), true);
+
+scripts = dbg.findScripts({url, line});
+assertEq(scripts.length, 0);
+
+assertEq(g.eval(`isLazyFunction(f1)`), true);
+assertEq(g.eval(`isLazyFunction(f2)`), false);
+assertEq(g.eval(`isLazyFunction(f3)`), true);
+
+// The matching non-lazy script should prevent the previous function's
+// delazification.
+[dbg, g, line] = newTestcase(`
+function f1() {
+}
+function f2() {
+}
+function f3() {
+ // <= line
+}
+function f4() {
+}
+`);
+
+assertEq(g.eval(`isLazyFunction(f1)`), true);
+assertEq(g.eval(`isLazyFunction(f2)`), true);
+assertEq(g.eval(`isLazyFunction(f3)`), true);
+assertEq(g.eval(`isLazyFunction(f4)`), true);
+
+// Delazify f3.
+g.eval(`f3()`);
+
+assertEq(g.eval(`isLazyFunction(f1)`), true);
+assertEq(g.eval(`isLazyFunction(f2)`), true);
+assertEq(g.eval(`isLazyFunction(f3)`), false);
+assertEq(g.eval(`isLazyFunction(f4)`), true);
+
+scripts = dbg.findScripts({url, line});
+assertEq(scripts.length, 1);
+assertEq(scripts.map(s => s.displayName).sort().join(","), "f3");
+
+assertEq(g.eval(`isLazyFunction(f1)`), true);
+assertEq(g.eval(`isLazyFunction(f2)`), true);
+assertEq(g.eval(`isLazyFunction(f3)`), false);
+assertEq(g.eval(`isLazyFunction(f4)`), true);
+
+// The non-matching non-lazy script should prevent the previous function's
+// delazification.
+[dbg, g, line] = newTestcase(`
+function f1() {
+}
+function f2() {
+}
+function f3() {
+}
+// <= line
+function f4() {
+}
+`);
+
+assertEq(g.eval(`isLazyFunction(f1)`), true);
+assertEq(g.eval(`isLazyFunction(f2)`), true);
+assertEq(g.eval(`isLazyFunction(f3)`), true);
+assertEq(g.eval(`isLazyFunction(f4)`), true);
+
+// Delazify f3.
+g.eval(`f3()`);
+
+assertEq(g.eval(`isLazyFunction(f1)`), true);
+assertEq(g.eval(`isLazyFunction(f2)`), true);
+assertEq(g.eval(`isLazyFunction(f3)`), false);
+assertEq(g.eval(`isLazyFunction(f4)`), true);
+
+scripts = dbg.findScripts({url, line});
+assertEq(scripts.length, 0);
+
+assertEq(g.eval(`isLazyFunction(f1)`), true);
+assertEq(g.eval(`isLazyFunction(f2)`), true);
+assertEq(g.eval(`isLazyFunction(f3)`), false);
+assertEq(g.eval(`isLazyFunction(f4)`), true);
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-optimized-out.js b/js/src/jit-test/tests/debug/Debugger-findScripts-optimized-out.js
new file mode 100644
index 0000000000..1c38bbbf4e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-optimized-out.js
@@ -0,0 +1,31 @@
+// Accessing Debugger.Script's properties which triggers delazification can
+// fail if the function for the script is optimized out.
+// It shouldn't crash but just throw an error.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+g.eval(`
+function enclosing() {
+ (function g1() {});
+ (function* g2() {});
+ (async function g3() {});
+ (async function* g4() {});
+ () => {};
+ async () => {};
+}
+`);
+
+for (const s of dbg.findScripts()) {
+ if (!s.displayName) {
+ continue;
+ }
+
+ try {
+ s.lineCount; // don't assert
+ } catch (exc) {
+ // If that didn't throw, it's fine. If it did, check the message.
+ assertEq(exc.message, "function is optimized out");
+ }
+}
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-uncompleted-01.js b/js/src/jit-test/tests/debug/Debugger-findScripts-uncompleted-01.js
new file mode 100644
index 0000000000..4a7e9139ce
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-uncompleted-01.js
@@ -0,0 +1,54 @@
+// |jit-test| skip-if: isLcovEnabled()
+
+// Uncompleted scripts and their inner scripts shouldn't be found in
+// findScripts.
+
+let g = newGlobal({newCompartment: true});
+let dbg = new Debugger(g);
+
+let message = "";
+try {
+ g.eval(`
+(function nonLazyOuter() {
+ (function nonLazyInner() {
+ function lazy1() {
+ function lazy2() {
+ }
+ }
+ })();
+})();
+
+(function uncompletedNonLazy() {
+ function lazyInUncompleted1() {
+ function lazyInUncompleted2() {
+ }
+ }
+ // LazyScripts for above 2 functions are created when parsing,
+ // and JSScript for uncompletedNonLazy is created at the beginning of
+ // compiling it, but it doesn't get code() since the following switch-case
+ // throws error while emitting bytecode.
+ switch (1) {
+ ${"case 1:".repeat(2**16+1)}
+ }
+})();
+`);
+} catch (e) {
+ message = e.message;
+}
+
+assertEq(message.includes("too many switch cases"), true,
+ "Error for switch-case should be thrown," +
+ "in order to test the case that uncompleted script is created");
+
+for (var script of dbg.findScripts()) {
+ // Since all of above scripts can be GCed, we cannot check the set of
+ // found scripts.
+ if (script.displayName) {
+ assertEq(script.displayName != "uncompletedNonLazy", true,
+ "Uncompiled script shouldn't be found");
+ assertEq(script.displayName != "lazyInUncompleted1", true,
+ "Scripts inside uncompiled script shouldn't be found");
+ assertEq(script.displayName != "lazyInUncompleted2", true,
+ "Scripts inside uncompiled script shouldn't be found");
+ }
+}
diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-uncompleted-02.js b/js/src/jit-test/tests/debug/Debugger-findScripts-uncompleted-02.js
new file mode 100644
index 0000000000..c1201a5c6f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-uncompleted-02.js
@@ -0,0 +1,49 @@
+// Uncompleted scripts shouldn't be found in findScripts.
+
+let g = newGlobal({newCompartment: true});
+let dbg = new Debugger(g);
+
+let message = "";
+try {
+ g.eval(`
+(function nonLazyOuter() {
+ (function nonLazyInner() {
+ function lazy1() {
+ function lazy2() {
+ }
+ }
+ })();
+})();
+
+(function uncompletedNonLazy() {
+ (function completedNonLazy() {
+ function lazyInCompleted1() {
+ function lazyInCompleted2() {
+ }
+ }
+ })();
+ // completedNonLazy and its inner scripts can be exposed to debugger since
+ // the compilation for completedNonLazy finishes, even if the enclosing
+ // uncompletedNonLazy fails to compile.
+ switch (1) {
+ ${"case 1:".repeat(2**16+1)}
+ }
+})();
+`);
+} catch (e) {
+ message = e.message;
+}
+
+assertEq(message.includes("too many switch cases"), true,
+ "Error for switch-case should be thrown," +
+ "in order to test the case that uncompleted script is created");
+
+let actualNames = [];
+for (var script of dbg.findScripts()) {
+ // Since all of above scripts can be GCed, we cannot check the set of
+ // found scripts.
+ if (script.displayName) {
+ assertEq(script.displayName != "uncompletedNonLazy", true,
+ "Uncompiled script shouldn't be found");
+ }
+}
diff --git a/js/src/jit-test/tests/debug/Debugger-findSourceURLs.js b/js/src/jit-test/tests/debug/Debugger-findSourceURLs.js
new file mode 100644
index 0000000000..d00ade6325
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findSourceURLs.js
@@ -0,0 +1,18 @@
+// findSourceURLs should return all URLs compiled in debuggee realms,
+// except when a shrinking GC has occurred.
+
+let g = newGlobal({newCompartment: true});
+let startNumber = gcparam("gcNumber");
+g.evaluate("function foo() {}", { fileName: "foo.js" });
+g.evaluate("function bar() {}", { fileName: "bar.js" });
+g.evaluate("function baz() {}", { fileName: "baz.js" });
+
+let dbg = new Debugger(g);
+let urls = dbg.findSourceURLs();
+
+let endNumber = gcparam("gcNumber");
+if (startNumber == endNumber) {
+ assertEq(urls.includes("foo.js"), true);
+ assertEq(urls.includes("bar.js"), true);
+ assertEq(urls.includes("baz.js"), true);
+}
diff --git a/js/src/jit-test/tests/debug/Debugger-findSources-01.js b/js/src/jit-test/tests/debug/Debugger-findSources-01.js
new file mode 100644
index 0000000000..4cac711e52
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findSources-01.js
@@ -0,0 +1,4 @@
+// In a debugger with no debuggees, findSources should return no scripts.
+
+const dbg = new Debugger;
+assertEq(dbg.findSources().length, 0);
diff --git a/js/src/jit-test/tests/debug/Debugger-findSources-02.js b/js/src/jit-test/tests/debug/Debugger-findSources-02.js
new file mode 100644
index 0000000000..532d39da12
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findSources-02.js
@@ -0,0 +1,15 @@
+// In a debugger with scripts, findSources finds the script source.
+
+const g = newGlobal({newCompartment: true});
+// Declare a function in order to keep the script source alive across GC.
+g.evaluate(`function fa() {}`, { fileName: "a.js" });
+g.evaluate(`function fb() {}`, { fileName: "b.js" });
+g.evaluate(`function fc() {}`, { fileName: "c.js" });
+
+const dbg = new Debugger();
+const gw = dbg.addDebuggee(g);
+
+const sources = dbg.findSources();
+assertEq(sources.filter(s => s.url == "a.js").length, 1);
+assertEq(sources.filter(s => s.url == "b.js").length, 1);
+assertEq(sources.filter(s => s.url == "c.js").length, 1);
diff --git a/js/src/jit-test/tests/debug/Debugger-findSources-03.js b/js/src/jit-test/tests/debug/Debugger-findSources-03.js
new file mode 100644
index 0000000000..9cda3a432a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findSources-03.js
@@ -0,0 +1,19 @@
+// In a debugger with multiple debuggees, findSources finds script sources across all debuggees.
+
+const g1 = newGlobal({newCompartment: true});
+const g2 = newGlobal({newCompartment: true});
+// Declare a function in order to keep the script source alive across GC.
+g1.evaluate(`function fa() {}`, { fileName: "a.js" });
+g1.evaluate(`function fb() {}`, { fileName: "b.js" });
+g2.evaluate(`function fc() {}`, { fileName: "c.js" });
+g2.evaluate(`function fd() {}`, { fileName: "d.js" });
+
+const dbg = new Debugger();
+const g1w = dbg.addDebuggee(g1);
+const g2w = dbg.addDebuggee(g2);
+
+const sources = dbg.findSources();
+assertEq(dbg.findSources().filter(s => s.url == "a.js").length, 1);
+assertEq(dbg.findSources().filter(s => s.url == "b.js").length, 1);
+assertEq(dbg.findSources().filter(s => s.url == "c.js").length, 1);
+assertEq(dbg.findSources().filter(s => s.url == "d.js").length, 1);
diff --git a/js/src/jit-test/tests/debug/Debugger-getNewestFrame-01.js b/js/src/jit-test/tests/debug/Debugger-getNewestFrame-01.js
new file mode 100644
index 0000000000..b1cd194a53
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-getNewestFrame-01.js
@@ -0,0 +1,20 @@
+// getNewestFrame basics.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+assertEq(dbg.getNewestFrame(), null);
+
+var global = this;
+var frame;
+function f() {
+ frame = dbg.getNewestFrame();
+ assertEq(frame instanceof Debugger.Frame, true);
+ assertEq(frame.type, "eval");
+ assertEq(frame.older, null);
+}
+g.h = this;
+g.eval("h.f()");
+assertEq(frame.onStack, false);
+assertThrowsInstanceOf(function () { frame.older; }, Error);
diff --git a/js/src/jit-test/tests/debug/Debugger-getNewestFrame-02.js b/js/src/jit-test/tests/debug/Debugger-getNewestFrame-02.js
new file mode 100644
index 0000000000..799159af32
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-getNewestFrame-02.js
@@ -0,0 +1,20 @@
+// Hooks and Debugger.prototype.getNewestFrame produce the same Frame object.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var hits = 0;
+var savedFrame, savedCallee;
+dbg.onDebuggerStatement = function (frame) {
+ assertEq(frame, savedFrame);
+ assertEq(frame.onStack, true);
+ assertEq(frame.callee, savedCallee);
+ hits++;
+};
+g.h = function () {
+ savedFrame = dbg.getNewestFrame();
+ savedCallee = savedFrame.callee;
+ assertEq(savedCallee.name, "f");
+};
+g.eval("function f() { h(); debugger; }");
+g.f();
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Debugger-getNewestFrame-03.js b/js/src/jit-test/tests/debug/Debugger-getNewestFrame-03.js
new file mode 100644
index 0000000000..b9396e2f92
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-getNewestFrame-03.js
@@ -0,0 +1,9 @@
+// Debugger.prototype.getNewestFrame() ignores dummy frames.
+// See bug 678086.
+
+var g = newGlobal({newCompartment: true});
+g.f = function () { return dbg.getNewestFrame(); };
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+var fw = gw.getOwnPropertyDescriptor("f").value;
+assertEq(fw.call().return, null);
diff --git a/js/src/jit-test/tests/debug/Debugger-getNewestFrame-generators-01.js b/js/src/jit-test/tests/debug/Debugger-getNewestFrame-generators-01.js
new file mode 100644
index 0000000000..908bd4f2f3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-getNewestFrame-generators-01.js
@@ -0,0 +1,49 @@
+// Generator/async frames can be created and revived by calling Debugger.getNewestFrame().
+//
+// Modified copy of Frame-older-generators-01.js.
+
+let g = newGlobal({newCompartment: true});
+g.eval(`
+ function* gen() {
+ f();
+ yield 1;
+ f();
+ }
+ function* genDefaults(x=f()) {
+ f();
+ }
+ async function af() {
+ f();
+ await 1;
+ f();
+ }
+ async function afDefaults(x=f()) {
+ await 1;
+ f();
+ }
+`);
+
+function test(expected, code) {
+ let dbg = Debugger(g);
+ let hits = 0;
+ let genFrame = null;
+ g.f = () => {
+ hits++;
+ let frame = dbg.getNewestFrame();
+ if (genFrame === null) {
+ genFrame = frame;
+ } else {
+ assertEq(frame, genFrame);
+ }
+ assertEq(genFrame.callee.name, expected);
+ }
+
+ g.eval(code);
+ assertEq(hits, 2);
+ dbg.removeDebuggee(g);
+}
+
+test("gen", "for (var x of gen()) {}");
+test("genDefaults", "for (var x of genDefaults()) {}");
+test("af", "af(); drainJobQueue();");
+test("afDefaults", "afDefaults(); drainJobQueue();")
diff --git a/js/src/jit-test/tests/debug/Debugger-isCompilableUnit.js b/js/src/jit-test/tests/debug/Debugger-isCompilableUnit.js
new file mode 100644
index 0000000000..dbfde0ef46
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-isCompilableUnit.js
@@ -0,0 +1,58 @@
+load(libdir + "asserts.js");
+
+const bad_types = [
+ 2112,
+ {geddy: "lee"},
+ () => 1,
+ [],
+ Array
+]
+
+// We only accept strings around here!
+for (var badType of bad_types) {
+ assertThrowsInstanceOf(() => {
+ Debugger.isCompilableUnit(badType);
+ }, TypeError);
+}
+
+const compilable_units = [
+ "wubba-lubba-dub-dub",
+ "'Get Schwifty!'",
+ "1 + 2",
+ "function f(x) {}",
+ "function x(...f,) {", // statements with bad syntax are always compilable
+ "let x = 100",
+ ";;;;;;;;",
+ "",
+ " ",
+ "\n",
+ "let x",
+]
+
+const non_compilable_units = [
+ "function f(x) {",
+ "(...d) =>",
+ "{geddy:",
+ "{",
+ "[1, 2",
+ "[",
+ "1 +",
+ "let x =",
+ "3 ==",
+]
+
+for (var code of compilable_units) {
+ assertEq(Debugger.isCompilableUnit(code), true);
+}
+
+for (var code of non_compilable_units) {
+ assertEq(Debugger.isCompilableUnit(code), false);
+}
+
+// Supplying no arguments should throw a type error
+assertThrowsInstanceOf(() => {
+ Debugger.isCompilableUnit();
+}, TypeError);
+
+// Supplying extra arguments should be fine
+assertEq(Debugger.isCompilableUnit("", 1, 2, 3, 4, {}, []), true);
diff --git a/js/src/jit-test/tests/debug/Debugger-multi-01.js b/js/src/jit-test/tests/debug/Debugger-multi-01.js
new file mode 100644
index 0000000000..c986accdbb
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-multi-01.js
@@ -0,0 +1,31 @@
+// When there are multiple debuggers, their hooks are called in order.
+
+var g = newGlobal({newCompartment: true});
+var log;
+var arr = [];
+
+function addDebug(msg) {
+ var dbg = new Debugger(g);
+ dbg.onDebuggerStatement = function (stack) { log += msg; };
+ arr.push(dbg);
+}
+
+addDebug('a');
+addDebug('b');
+addDebug('c');
+
+log = '';
+assertEq(g.eval("debugger; 0;"), 0);
+assertEq(log, 'abc');
+
+// Calling debugger hooks continues, even if one returns a resumption value
+// other than undefined.
+
+arr[0].onDebuggerStatement = function (stack) {
+ log += 'a';
+ return {return: 1};
+};
+
+log = '';
+assertEq(g.eval("debugger; 0;"), 1);
+assertEq(log, 'abc');
diff --git a/js/src/jit-test/tests/debug/Debugger-multi-02.js b/js/src/jit-test/tests/debug/Debugger-multi-02.js
new file mode 100644
index 0000000000..bff152b0df
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-multi-02.js
@@ -0,0 +1,32 @@
+// Test adding hooks during dispatch. The behavior is deterministic and "nice",
+// but mainly what we are checking here is that we do not crash due to
+// modifying a data structure while we're iterating over it.
+
+var g = newGlobal({newCompartment: true});
+var n = 0;
+var hits;
+
+function addDebugger() {
+ var dbg = new Debugger(g);
+ dbg.onDebuggerStatement = function (stack) {
+ hits++;
+ addDebugger();
+ };
+}
+
+addDebugger(); // now there is one enabled Debugger
+hits = 0;
+g.eval("debugger;"); // after this there are two
+assertEq(hits, 1);
+
+hits = 0;
+g.eval("debugger;"); // after this there are four
+assertEq(hits, 2);
+
+hits = 0;
+g.eval("debugger;"); // after this there are eight
+assertEq(hits, 4);
+
+hits = 0;
+g.eval("debugger;");
+assertEq(hits, 8);
diff --git a/js/src/jit-test/tests/debug/Debugger-multi-03.js b/js/src/jit-test/tests/debug/Debugger-multi-03.js
new file mode 100644
index 0000000000..cd89fdead1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-multi-03.js
@@ -0,0 +1,21 @@
+// Q: But who shall debug the debuggers? A: jimb
+
+var log = '';
+
+function addDebug(g, id) {
+ var debuggerGlobal = newGlobal({newCompartment: true});
+ debuggerGlobal.debuggee = g;
+ debuggerGlobal.id = id;
+ debuggerGlobal.print = function (s) { log += s; };
+ debuggerGlobal.eval(
+ 'var dbg = new Debugger(debuggee);\n' +
+ 'dbg.onDebuggerStatement = function () { print(id); debugger; print(id); };\n');
+ return debuggerGlobal;
+}
+
+var base = newGlobal({newCompartment: true});
+var top = base;
+for (var i = 0; i < 8; i++) // why have 2 debuggers when you can have 8
+ top = addDebug(top, i);
+base.eval("debugger;");
+assertEq(log, '0123456776543210');
diff --git a/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-01.js b/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-01.js
new file mode 100644
index 0000000000..b2f0b779cf
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-01.js
@@ -0,0 +1,45 @@
+// If debugger.onEnterFrame returns {return:val}, the frame returns.
+
+var g = newGlobal({newCompartment: true});
+g.set = false;
+g.eval("function f() {\n" +
+ " set = true;\n" +
+ " return 'fail';\n" +
+ "}\n");
+g.eval("function g() { return 'g ' + f(); }");
+g.eval("function h() { return 'h ' + g(); }");
+
+var dbg = Debugger(g);
+var savedFrame;
+dbg.onEnterFrame = function (frame) {
+ savedFrame = frame;
+ return {return: "pass"};
+};
+
+// Call g.f as a function.
+savedFrame = undefined;
+assertEq(g.f(), "pass");
+assertEq(savedFrame.onStack, false);
+assertEq(g.set, false);
+
+// Call g.f as a constructor.
+savedFrame = undefined;
+var r = new g.f;
+assertEq(typeof r, "object");
+assertEq(savedFrame.onStack, false);
+assertEq(g.set, false);
+
+var count = 0;
+dbg.onEnterFrame = function (frame) {
+ count++;
+ if (count == 3) {
+ savedFrame = frame;
+ return {return: "pass"};
+ }
+ return undefined;
+};
+g.set = false;
+savedFrame = undefined;
+assertEq(g.h(), "h g pass");
+assertEq(savedFrame.onStack, false);
+assertEq(g.set, false);
diff --git a/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-02.js b/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-02.js
new file mode 100644
index 0000000000..9a8778b9cc
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-02.js
@@ -0,0 +1,28 @@
+// If debugger.onEnterFrame returns {throw:val}, an exception is thrown in the debuggee.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+g.set = false;
+g.eval("function f() {\n" +
+ " set = true;\n" +
+ " return 'fail';\n" +
+ "}\n");
+
+var dbg = Debugger(g);
+var savedFrame;
+dbg.onEnterFrame = function (frame) {
+ savedFrame = frame;
+ return {throw: "pass"};
+};
+
+savedFrame = undefined;
+assertThrowsValue(g.f, "pass");
+assertEq(savedFrame.onStack, false);
+assertEq(g.set, false);
+
+savedFrame = undefined;
+assertThrowsValue(function () { new g.f; }, "pass");
+assertEq(savedFrame.onStack, false);
+assertEq(g.set, false);
+
diff --git a/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-03.js b/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-03.js
new file mode 100644
index 0000000000..0da20925b0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-03.js
@@ -0,0 +1,26 @@
+// If debugger.onEnterFrame returns null, the debuggee is terminated immediately.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+g.set = false;
+
+var dbg = Debugger(g);
+var savedFrame;
+dbg.onDebuggerStatement = function (frame) {
+ var innerSavedFrame;
+ dbg.onEnterFrame = function (frame) {
+ innerSavedFrame = frame;
+ return null;
+ };
+ // Using frame.eval lets us catch termination.
+ assertEq(frame.eval("set = true;"), null);
+ assertEq(innerSavedFrame.onStack, false);
+ savedFrame = frame;
+ return { return: "pass" };
+};
+
+savedFrame = undefined;
+assertEq(g.eval("debugger;"), "pass");
+assertEq(savedFrame.onStack, false);
+assertEq(g.set, false);
diff --git a/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-04.js b/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-04.js
new file mode 100644
index 0000000000..fae18adff7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-04.js
@@ -0,0 +1,16 @@
+// If debugger.onEnterFrame returns undefined, the frame should continue execution.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var hits = 0;
+var savedFrame;
+dbg.onEnterFrame = function (frame) {
+ hits++;
+ savedFrame = frame;
+ return undefined;
+};
+
+savedFrame = undefined;
+assertEq(g.eval("'pass';"), "pass");
+assertEq(savedFrame.onStack, false);
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-05.js b/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-05.js
new file mode 100644
index 0000000000..8885aa0d80
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-05.js
@@ -0,0 +1,98 @@
+// Exercise the call to ScriptDebugPrologue in js_InternalInterpret.
+
+// This may change, but as of this writing, inline caches (ICs) are
+// disabled in debug mode, and those are the only users of the out-of-line entry
+// points for JIT code (arityCheckEntry, argsCheckEntry, fastEntry); debug
+// mode uses only invokeEntry. This means most of the bytecode tails in
+// js_InternalInterpret that might call ScriptPrologue or ScriptEpilogue are
+// unreachable in debug mode: they're only called from the out-of-line entry
+// points.
+//
+// The exception is REJOIN_THIS_PROTOTYPE, which can be reached reliably if you
+// add a JS_GC call to stubs::GetPropNoCache. JIT code calls that stub to
+// retrieve the 'prototype' property of a function called as a constructor, if
+// TI can't establish the exact identity of that prototype's value at compile
+// time. Thus the preoccupation with constructors here.
+
+load(libdir + "asserts.js");
+
+var debuggee = newGlobal({newCompartment: true});
+var dbg = Debugger(debuggee);
+var hits, savedFrame;
+
+// Allow the constructor to return normally.
+dbg.onEnterFrame = function (frame) {
+ hits++;
+ if (frame.constructing) {
+ savedFrame = frame;
+ assertEq(savedFrame.onStack, true);
+ return undefined;
+ }
+ return undefined;
+};
+hits = 0;
+debuggee.hits = 0;
+savedFrame = undefined;
+assertEq(typeof debuggee.eval("function f(){ hits++; } f.prototype = {}; new f;"), "object");
+assertEq(hits, 2);
+assertEq(savedFrame.onStack, false);
+assertEq(debuggee.hits, 1);
+
+// Force an early return from the constructor.
+dbg.onEnterFrame = function (frame) {
+ hits++;
+ if (frame.constructing) {
+ savedFrame = frame;
+ assertEq(savedFrame.onStack, true);
+ return { return: "pass" };
+ }
+ return undefined;
+};
+hits = 0;
+debuggee.hits = 0;
+savedFrame = undefined;
+assertEq(typeof debuggee.eval("function f(){ hits++; } f.prototype = {}; new f;"), "object");
+assertEq(hits, 2);
+assertEq(savedFrame.onStack, false);
+assertEq(debuggee.hits, 0);
+
+// Force the constructor to throw an exception.
+dbg.onEnterFrame = function (frame) {
+ hits++;
+ if (frame.constructing) {
+ savedFrame = frame;
+ assertEq(savedFrame.onStack, true);
+ return { throw: "pass" };
+ }
+ return undefined;
+};
+hits = 0;
+debuggee.hits = 0;
+savedFrame = undefined;
+assertThrowsValue(function () {
+ debuggee.eval("function f(){ hits++ } f.prototype = {}; new f;");
+ }, "pass");
+assertEq(hits, 2);
+assertEq(savedFrame.onStack, false);
+assertEq(debuggee.hits, 0);
+
+// Ensure that forcing an early return only returns from one JS call.
+debuggee.eval("function g() { var result = new f; g_hits++; return result; }");
+dbg.onEnterFrame = function (frame) {
+ hits++;
+ if (frame.constructing) {
+ savedFrame = frame;
+ assertEq(savedFrame.onStack, true);
+ return { return: "pass" };
+ }
+ return undefined;
+};
+hits = 0;
+debuggee.hits = 0;
+debuggee.g_hits = 0;
+savedFrame = undefined;
+assertEq(typeof debuggee.eval("g();"), "object");
+assertEq(hits, 3);
+assertEq(savedFrame.onStack, false);
+assertEq(debuggee.hits, 0);
+assertEq(debuggee.g_hits, 1);
diff --git a/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-06.js b/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-06.js
new file mode 100644
index 0000000000..c515475e9d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-06.js
@@ -0,0 +1,31 @@
+// |jit-test| error:all-jobs-completed-successfully
+// Verifiy that onEnterFrame's force-return queues the promise microtask
+// to run in the debuggee's job queue, not the debugger's
+// AutoDebuggerJobQueueInterruption.
+
+let g = newGlobal({ newCompartment: true });
+g.eval(`
+ async function asyncFn(x) {
+ await Promise.resolve();
+ }
+ function enterDebuggee(){}
+`);
+const dbg = new Debugger(g);
+
+(async function() {
+ let it = g.asyncFn();
+
+ // Force-return when the await resumes.
+ dbg.onEnterFrame = () => {
+ dbg.onEnterFrame = undefined;
+ return { return: "exit" };
+ };
+
+ const result = await it;
+ assertEq(result, "exit");
+ // If execution here is resumed from the debugger's queue, this call will
+ // trigger DebuggeeWouldRun exception.
+ g.enterDebuggee();
+
+ throw "all-jobs-completed-successfully";
+})();
diff --git a/js/src/jit-test/tests/debug/Debugger-onNativeCall-01.js b/js/src/jit-test/tests/debug/Debugger-onNativeCall-01.js
new file mode 100644
index 0000000000..4ee9c6f387
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNativeCall-01.js
@@ -0,0 +1,64 @@
+// Test that the onNativeCall hook is called when expected.
+
+load(libdir + 'eqArrayHelper.js');
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var gdbg = dbg.addDebuggee(g);
+
+g.eval(`
+const x = [];
+Object.defineProperty(x, "a", {
+ get: print,
+ set: print,
+});
+function f() {
+ x.a++;
+ x.push(4);
+}
+`);
+
+for (let i = 0; i < 5; i++) {
+ g.f();
+}
+
+const rv = [];
+dbg.onNativeCall = (callee, reason) => { rv.push(callee.name, reason); };
+
+var dbg2 = Debugger(g);
+var gdbg2 = dbg2.addDebuggee(g);
+
+const fscript = gdbg.getOwnPropertyDescriptor('f').value.script;
+
+for (let i = 0; i < 5; i++) {
+ // The onNativeCall hook is called when doing global evaluations.
+ rv.length = 0;
+ gdbg.executeInGlobal(`f()`);
+ assertEqArray(rv, ["print", "get", "print", "set", "push", "call"]);
+
+ // The onNativeCall hook is called when doing frame evaluations.
+ let handlerCalled = false;
+ const handler = {
+ hit(frame) {
+ fscript.clearBreakpoint(handler);
+ rv.length = 0;
+ frame.eval(`f()`);
+ assertEqArray(rv, ["print", "get", "print", "set", "push", "call"]);
+ handlerCalled = true;
+ },
+ };
+ fscript.setBreakpoint(fscript.mainOffset, handler);
+ g.f();
+ assertEq(handlerCalled, true);
+
+ // The onNativeCall hook is *not* called when not in a debugger evaluation.
+ rv.length = 0;
+ g.f();
+ assertEqArray(rv, []);
+
+ // The onNativeCall hook is *not* called when in a debugger evaluation
+ // associated with a different debugger.
+ rv.length = 0;
+ gdbg2.executeInGlobal(`f()`);
+ assertEqArray(rv, []);
+}
diff --git a/js/src/jit-test/tests/debug/Debugger-onNativeCall-02.js b/js/src/jit-test/tests/debug/Debugger-onNativeCall-02.js
new file mode 100644
index 0000000000..9e372d179a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNativeCall-02.js
@@ -0,0 +1,61 @@
+// Test that the onNativeCall hook can control the call's behavior.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var gdbg = dbg.addDebuggee(g);
+
+g.eval(`
+var x = [];
+Object.defineProperty(x, "a", {
+ get: print,
+ set: print,
+});
+var rv;
+function f() {
+ x.a++;
+ try {
+ rv = x.push(4);
+ } catch (e) {
+ throw "rethrowing";
+ }
+}
+`);
+
+for (let i = 0; i < 5; i++) {
+ g.f();
+}
+
+for (let i = 0; i < 5; i++) {
+ // Test terminating execution.
+ dbg.onNativeCall = (callee, reason) => {
+ return null;
+ };
+ const len = g.x.length;
+ let v = gdbg.executeInGlobal(`f()`);
+ assertEq(v, null);
+ assertEq(g.x.length, len);
+
+ // Test throwing an exception.
+ dbg.onNativeCall = (callee, reason) => {
+ return { throw: "throwing" };
+ };
+ v = gdbg.executeInGlobal(`f()`);
+ assertEq(v.throw, "throwing");
+
+ // Test throwing an exception #2.
+ dbg.onNativeCall = (callee, reason) => {
+ if (callee.name == "push") {
+ return { throw: "throwing" };
+ }
+ };
+ v = gdbg.executeInGlobal(`f()`);
+ assertEq(v.throw, "rethrowing");
+
+ // Test returning a different value from the native.
+ dbg.onNativeCall = (callee, reason) => {
+ return { return: "value" };
+ };
+ v = gdbg.executeInGlobal(`f()`);
+ assertEq(v.return, undefined);
+ assertEq(g.rv, "value");
+}
diff --git a/js/src/jit-test/tests/debug/Debugger-onNativeCall-03.js b/js/src/jit-test/tests/debug/Debugger-onNativeCall-03.js
new file mode 100644
index 0000000000..265edb556c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNativeCall-03.js
@@ -0,0 +1,24 @@
+// Test onNativeCall's behavior when used with self-hosted functions.
+
+load(libdir + 'eqArrayHelper.js');
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var gdbg = dbg.addDebuggee(g);
+
+const rv = [];
+
+dbg.onEnterFrame = f => {
+ rv.push("EnterFrame");
+};
+
+dbg.onNativeCall = f => {
+ rv.push(f.displayName);
+};
+
+gdbg.executeInGlobal(`
+ var x = [1,3,2];
+ x.sort((a, b) => {print(a)});
+`);
+
+assertEqArray(rv, ["EnterFrame", "sort", "EnterFrame", "print", "EnterFrame", "print"]);
diff --git a/js/src/jit-test/tests/debug/Debugger-onNativeCall-04.js b/js/src/jit-test/tests/debug/Debugger-onNativeCall-04.js
new file mode 100644
index 0000000000..d3ee1377bf
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNativeCall-04.js
@@ -0,0 +1,26 @@
+// Test that onNativeCall behaves correctly when a debugger eval might enter the
+// JIT via OSR.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var gdbg = dbg.addDebuggee(g);
+
+g.eval(`
+const x = [];
+function f() {
+ for (let i = 0; i < 5; i++) {
+ x.push(i);
+ }
+}
+`);
+
+let numCalls = 0;
+dbg.onNativeCall = callee => { assertEq(callee.name, "push"); numCalls++; };
+
+var dbg2 = Debugger(g);
+
+for (let i = 0; i < 5; i++) {
+ numCalls = 0;
+ gdbg.executeInGlobal(`f()`);
+ assertEq(numCalls, 5);
+}
diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-01.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-01.js
new file mode 100644
index 0000000000..736d29ad03
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-01.js
@@ -0,0 +1,64 @@
+// Debugger.prototype.onNewGlobalObject surfaces.
+
+load(libdir + 'asserts.js');
+
+var dbg = new Debugger;
+
+function f() { }
+function g() { }
+
+assertEq(Object.getOwnPropertyDescriptor(dbg, 'onNewGlobalObject'), undefined);
+
+var d = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(dbg), 'onNewGlobalObject');
+assertEq(d.enumerable, false);
+assertEq(d.configurable, true);
+assertEq(typeof d.get, "function");
+assertEq(typeof d.set, "function");
+
+assertEq(dbg.onNewGlobalObject, undefined);
+
+assertThrowsInstanceOf(function () { dbg.onNewGlobalObject = ''; }, TypeError);
+assertEq(dbg.onNewGlobalObject, undefined);
+
+assertThrowsInstanceOf(function () { dbg.onNewGlobalObject = false; }, TypeError);
+assertEq(dbg.onNewGlobalObject, undefined);
+
+assertThrowsInstanceOf(function () { dbg.onNewGlobalObject = 0; }, TypeError);
+assertEq(dbg.onNewGlobalObject, undefined);
+
+assertThrowsInstanceOf(function () { dbg.onNewGlobalObject = Math.PI; }, TypeError);
+assertEq(dbg.onNewGlobalObject, undefined);
+
+assertThrowsInstanceOf(function () { dbg.onNewGlobalObject = null; }, TypeError);
+assertEq(dbg.onNewGlobalObject, undefined);
+
+assertThrowsInstanceOf(function () { dbg.onNewGlobalObject = {}; }, TypeError);
+assertEq(dbg.onNewGlobalObject, undefined);
+
+// But any function, even a useless one, is okay. How fair is that?
+dbg.onNewGlobalObject = f;
+assertEq(dbg.onNewGlobalObject, f);
+
+dbg.onNewGlobalObject = undefined;
+assertEq(dbg.onNewGlobalObject, undefined);
+
+var dbg2 = new Debugger;
+assertEq(dbg.onNewGlobalObject, undefined);
+assertEq(dbg2.onNewGlobalObject, undefined);
+
+dbg.onNewGlobalObject = f;
+assertEq(dbg.onNewGlobalObject, f);
+assertEq(dbg2.onNewGlobalObject, undefined);
+
+dbg2.onNewGlobalObject = g;
+assertEq(dbg.onNewGlobalObject, f);
+assertEq(dbg2.onNewGlobalObject, g);
+
+dbg.onNewGlobalObject = undefined;
+assertEq(dbg.onNewGlobalObject, undefined);
+assertEq(dbg2.onNewGlobalObject, g);
+
+// You shouldn't be able to apply the accessor to the prototype.
+assertThrowsInstanceOf(function () {
+ Debugger.prototype.onNewGlobalObject = function () { };
+ }, TypeError);
diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-02.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-02.js
new file mode 100644
index 0000000000..c8c3d74add
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-02.js
@@ -0,0 +1,23 @@
+// onNewGlobalObject handlers fire, until they are removed.
+
+var dbg = new Debugger;
+var log;
+
+log = '';
+newGlobal();
+assertEq(log, '');
+
+dbg.onNewGlobalObject = function (global) {
+ log += 'n';
+ assertEq(global.seen, undefined);
+ global.seen = true;
+};
+
+log = '';
+newGlobal();
+assertEq(log, 'n');
+
+log = '';
+dbg.onNewGlobalObject = undefined;
+newGlobal();
+assertEq(log, '');
diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-03.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-03.js
new file mode 100644
index 0000000000..76a3649a2f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-03.js
@@ -0,0 +1,40 @@
+// onNewGlobalObject handlers on different Debugger instances are independent.
+
+var dbg1 = new Debugger;
+var log1;
+function h1(global) {
+ log1 += 'n';
+ assertEq(global.seen, undefined);
+ global.seen = true;
+}
+
+var dbg2 = new Debugger;
+var log2;
+function h2(global) {
+ log2 += 'n';
+ assertEq(global.seen, undefined);
+ global.seen = true;
+}
+
+log1 = log2 = '';
+newGlobal();
+assertEq(log1, '');
+assertEq(log2, '');
+
+log1 = log2 = '';
+dbg1.onNewGlobalObject = h1;
+newGlobal();
+assertEq(log1, 'n');
+assertEq(log2, '');
+
+log1 = log2 = '';
+dbg2.onNewGlobalObject = h2;
+newGlobal();
+assertEq(log1, 'n');
+assertEq(log2, 'n');
+
+log1 = log2 = '';
+dbg1.onNewGlobalObject = undefined;
+newGlobal();
+assertEq(log1, '');
+assertEq(log2, 'n');
diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-04.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-04.js
new file mode 100644
index 0000000000..6e02d19bdc
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-04.js
@@ -0,0 +1,14 @@
+// onNewGlobalObject handlers only fire on enabled Debuggers.
+
+var dbg = new Debugger;
+var log;
+
+dbg.onNewGlobalObject = function (global) {
+ log += 'n';
+ assertEq(global.seen, undefined);
+ global.seen = true;
+};
+
+log = '';
+newGlobal();
+assertEq(log, 'n');
diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-05.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-05.js
new file mode 100644
index 0000000000..4ec3edc198
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-05.js
@@ -0,0 +1,13 @@
+// An onNewGlobalObject handler can disable itself.
+
+var dbg = new Debugger;
+var log;
+
+dbg.onNewGlobalObject = function (global) {
+ log += 'n';
+ dbg.onNewGlobalObject = undefined;
+};
+
+log = '';
+newGlobal();
+assertEq(log, 'n');
diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-06.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-06.js
new file mode 100644
index 0000000000..f58f4e7ee1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-06.js
@@ -0,0 +1,20 @@
+// One Debugger's onNewGlobalObject handler can disable another Debugger's handler.
+
+var dbg1 = new Debugger;
+var dbg2 = new Debugger;
+var dbg3 = new Debugger;
+var log;
+var hit;
+
+function handler(global) {
+ hit++;
+ log += hit;
+ if (hit == 2)
+ dbg1.onNewGlobalObject = dbg2.onNewGlobalObject = dbg3.onNewGlobalObject = undefined;
+};
+
+log = '';
+hit = 0;
+dbg1.onNewGlobalObject = dbg2.onNewGlobalObject = dbg3.onNewGlobalObject = handler;
+newGlobal();
+assertEq(log, '12');
diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-07.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-07.js
new file mode 100644
index 0000000000..eb79ea4fcc
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-07.js
@@ -0,0 +1,18 @@
+// One Debugger's onNewGlobalObject handler can disable other Debuggers.
+
+var dbg1 = new Debugger;
+var dbg2 = new Debugger;
+var dbg3 = new Debugger;
+var log;
+var hit;
+
+function handler(global) {
+ hit++;
+ log += hit;
+};
+
+log = '';
+hit = 0;
+dbg1.onNewGlobalObject = dbg2.onNewGlobalObject = dbg3.onNewGlobalObject = handler;
+newGlobal();
+assertEq(log, '123');
diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-08.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-08.js
new file mode 100644
index 0000000000..f981e8ae06
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-08.js
@@ -0,0 +1,26 @@
+// Creating a global within an onNewGlobalObject handler causes a recursive handler invocation.
+//
+// This isn't really desirable behavior, as presumably a global created while a
+// handler is running is one the debugger is creating for its own purposes and
+// should not be observed, but if this behavior changes, we sure want to know.
+
+var dbg = new Debugger;
+var log;
+var depth;
+
+dbg.onNewGlobalObject = function (global) {
+ log += '('; depth++;
+
+ assertEq(global.seen, undefined);
+ global.seen = true;
+
+ if (depth < 3)
+ newGlobal();
+
+ log += ')'; depth--;
+};
+
+log = '';
+depth = 0;
+newGlobal();
+assertEq(log, '((()))');
diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-09.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-09.js
new file mode 100644
index 0000000000..1c4f029720
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-09.js
@@ -0,0 +1,34 @@
+// Resumption values from onNewGlobalObject handlers are disallowed.
+
+load(libdir + 'asserts.js');
+
+var dbg = new Debugger;
+var log;
+
+dbg.onNewGlobalObject = function (g) { log += 'n'; return undefined; };
+log = '';
+assertEq(typeof newGlobal(), "object");
+assertEq(log, 'n');
+
+dbg.uncaughtExceptionHook = function (ex) { assertEq(/disallowed/.test(ex), true); log += 'u'; }
+dbg.onNewGlobalObject = function (g) { log += 'n'; return { return: "snoo" }; };
+log = '';
+assertEq(typeof newGlobal(), "object");
+assertEq(log, 'nu');
+
+dbg.onNewGlobalObject = function (g) { log += 'n'; return { throw: "snoo" }; };
+log = '';
+assertEq(typeof newGlobal(), "object");
+assertEq(log, 'nu');
+
+dbg.onNewGlobalObject = function (g) { log += 'n'; return null; };
+log = '';
+assertEq(typeof newGlobal(), "object");
+assertEq(log, 'nu');
+
+dbg.uncaughtExceptionHook = function (ex) { assertEq(/foopy/.test(ex), true); log += 'u'; }
+dbg.onNewGlobalObject = function (g) { log += 'n'; throw "foopy"; };
+log = '';
+assertEq(typeof newGlobal(), "object");
+assertEq(log, 'nu');
+
diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-10.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-10.js
new file mode 100644
index 0000000000..ce90ed541a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-10.js
@@ -0,0 +1,27 @@
+// An earlier onNewGlobalObject handler returning a 'throw' resumption
+// value causes later handlers not to run.
+
+load(libdir + 'asserts.js');
+
+var dbg1 = new Debugger;
+var dbg2 = new Debugger;
+var dbg3 = new Debugger;
+var log;
+var count;
+
+dbg1.onNewGlobalObject = dbg2.onNewGlobalObject = dbg3.onNewGlobalObject = function (global) {
+ count++;
+ log += count;
+ if (count == 2)
+ return { throw: "snoo" };
+ return undefined;
+};
+dbg2.uncaughtExceptionHook = function (exn) {
+ assertEq(/disallowed/.test(exn), true);
+ log += 'u';
+};
+
+log = '';
+count = 0;
+assertEq(typeof newGlobal(), "object");
+assertEq(log, '12u3');
diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-11.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-11.js
new file mode 100644
index 0000000000..33345980dd
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-11.js
@@ -0,0 +1,31 @@
+// Resumption values other than |undefined| from uncaughtExceptionHook from
+// onNewGlobalObject handlers are ignored (other than cancelling further hooks).
+
+load(libdir + 'asserts.js');
+
+var dbg = new Debugger;
+var log;
+
+dbg.onNewGlobalObject = function () {
+ log += 'n';
+ throw 'party';
+};
+
+dbg.uncaughtExceptionHook = function (ex) {
+ log += 'u';
+ assertEq(ex, 'party');
+ return { throw: 'fit' };
+};
+
+log = '';
+assertEq(typeof newGlobal(), 'object');
+assertEq(log, 'nu');
+
+dbg.uncaughtExceptionHook = function (ex) {
+ log += 'u';
+ assertEq(ex, 'party');
+};
+
+log = '';
+assertEq(typeof newGlobal(), 'object');
+assertEq(log, 'nu');
diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-12.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-12.js
new file mode 100644
index 0000000000..591d28f64d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-12.js
@@ -0,0 +1,25 @@
+// Resumption values from uncaughtExceptionHook from onNewGlobalObject
+// handlers do not affect the dispatch of the event to other Debugger instances.
+
+load(libdir + 'asserts.js');
+
+var dbg1 = new Debugger;
+var dbg2 = new Debugger;
+var dbg3 = new Debugger;
+var log;
+
+dbg1.onNewGlobalObject = dbg2.onNewGlobalObject = dbg3.onNewGlobalObject = function () {
+ log += 'n';
+ throw 'party';
+};
+
+dbg1.uncaughtExceptionHook = dbg2.uncaughtExceptionHook = dbg3.uncaughtExceptionHook =
+function (ex) {
+ log += 'u';
+ assertEq(ex, 'party');
+ return { throw: 'fit' };
+};
+
+log = '';
+assertEq(typeof newGlobal(), 'object');
+assertEq(log, 'nununu');
diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-13.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-13.js
new file mode 100644
index 0000000000..86cb252c8c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-13.js
@@ -0,0 +1,18 @@
+// onNewGlobalObject handlers receive the correct Debugger.Object instances.
+
+var dbg = new Debugger;
+
+var gw = null;
+dbg.onNewGlobalObject = function (global) {
+ assertEq(arguments.length, 1);
+ assertEq(this, dbg);
+ gw = global;
+};
+var g = newGlobal({newCompartment: true});
+assertEq(typeof gw, 'object');
+assertEq(dbg.addDebuggee(g), gw);
+
+// The Debugger.Objects passed to onNewGlobalObject are the global itself
+// without any cross-compartment wrappers.
+// NOTE: They also ignore any WindowProxy that may be associated with global.
+assertEq(gw.unwrap(), gw);
diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-14.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-14.js
new file mode 100644
index 0000000000..fb61fab526
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-14.js
@@ -0,0 +1,17 @@
+// Globals passed to onNewGlobalObject handers are ready for use immediately.
+
+var dbg = new Debugger;
+var log = '';
+dbg.onNewGlobalObject = function (global) {
+ log += 'n';
+ var gw = dbg.addDebuggee(global);
+ gw.defineProperty('x', { value: -1 });
+ // Check that the global's magic lazy properties are working.
+ assertEq(gw.executeInGlobalWithBindings('Math.atan2(y,x)', { y: 0 }).return, Math.PI);
+ // Check that the global's prototype is hooked up.
+ assertEq(gw.executeInGlobalWithBindings('x.toString()', { x: gw }).return, "[object global]");
+};
+
+newGlobal({newCompartment: true});
+
+assertEq(log, 'n');
diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-15.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-15.js
new file mode 100644
index 0000000000..e6035dd8e3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-15.js
@@ -0,0 +1,25 @@
+// Globals marked as invisibleToDebugger behave appropriately.
+
+load(libdir + "asserts.js");
+
+var dbg = new Debugger;
+var log = '';
+dbg.onNewGlobalObject = function (global) {
+ log += 'n';
+}
+
+assertEq(typeof newGlobal(), "object");
+assertEq(typeof newGlobal({invisibleToDebugger: false}), "object");
+assertEq(log, 'nn');
+
+log = '';
+assertEq(typeof newGlobal({newCompartment: true, invisibleToDebugger: true}), "object");
+assertEq(log, '');
+
+assertThrowsInstanceOf(() => dbg.addDebuggee(newGlobal({newCompartment: true, invisibleToDebugger: true})),
+ Error);
+
+var glob = newGlobal({newCompartment: true, invisibleToDebugger: true});
+dbg.addAllGlobalsAsDebuggees();
+dbg.onDebuggerStatement = function (frame) { assertEq(true, false); };
+glob.eval('debugger');
diff --git a/js/src/jit-test/tests/debug/Debugger-onNewPromise-01.js b/js/src/jit-test/tests/debug/Debugger-onNewPromise-01.js
new file mode 100644
index 0000000000..9788e027c9
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewPromise-01.js
@@ -0,0 +1,13 @@
+// Test that the onNewPromise hook gets called when promises are allocated in
+// the scope of debuggee globals.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+
+
+let promisesFound = [];
+dbg.onNewPromise = p => { promisesFound.push(p); };
+
+let p1 = new g.Promise(function (){});
+assertEq(promisesFound.indexOf(gw.makeDebuggeeValue(p1)) != -1, true);
diff --git a/js/src/jit-test/tests/debug/Debugger-onNewPromise-02.js b/js/src/jit-test/tests/debug/Debugger-onNewPromise-02.js
new file mode 100644
index 0000000000..61cb8f03ef
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewPromise-02.js
@@ -0,0 +1,24 @@
+// onNewPromise handlers fire, until they are removed.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var log;
+
+log = '';
+new g.Promise(function (){});
+assertEq(log, '');
+
+dbg.onNewPromise = function (promise) {
+ log += 'n';
+ assertEq(promise.seen, undefined);
+ promise.seen = true;
+};
+
+log = '';
+new g.Promise(function (){});
+assertEq(log, 'n');
+
+log = '';
+dbg.onNewPromise = undefined;
+new g.Promise(function (){});
+assertEq(log, '');
diff --git a/js/src/jit-test/tests/debug/Debugger-onNewPromise-03.js b/js/src/jit-test/tests/debug/Debugger-onNewPromise-03.js
new file mode 100644
index 0000000000..38890ae957
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewPromise-03.js
@@ -0,0 +1,41 @@
+// onNewPromise handlers on different Debugger instances are independent.
+
+var g = newGlobal({newCompartment: true});
+var dbg1 = new Debugger(g);
+var log1;
+function h1(promise) {
+ log1 += 'n';
+ assertEq(promise.seen, undefined);
+ promise.seen = true;
+}
+
+var dbg2 = new Debugger(g);
+var log2;
+function h2(promise) {
+ log2 += 'n';
+ assertEq(promise.seen, undefined);
+ promise.seen = true;
+}
+
+log1 = log2 = '';
+new g.Promise(function (){});
+assertEq(log1, '');
+assertEq(log2, '');
+
+log1 = log2 = '';
+dbg1.onNewPromise = h1;
+new g.Promise(function (){});
+assertEq(log1, 'n');
+assertEq(log2, '');
+
+log1 = log2 = '';
+dbg2.onNewPromise = h2;
+new g.Promise(function (){});
+assertEq(log1, 'n');
+assertEq(log2, 'n');
+
+log1 = log2 = '';
+dbg1.onNewPromise = undefined;
+new g.Promise(function (){});
+assertEq(log1, '');
+assertEq(log2, 'n');
diff --git a/js/src/jit-test/tests/debug/Debugger-onNewPromise-04.js b/js/src/jit-test/tests/debug/Debugger-onNewPromise-04.js
new file mode 100644
index 0000000000..298ccedd80
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewPromise-04.js
@@ -0,0 +1,14 @@
+// An onNewPromise handler can disable itself.
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var log;
+
+dbg.onNewPromise = function (promise) {
+ log += 'n';
+ dbg.onNewPromise = undefined;
+};
+
+log = '';
+new g.Promise(function (){});
+new g.Promise(function (){});
+assertEq(log, 'n');
diff --git a/js/src/jit-test/tests/debug/Debugger-onNewPromise-05.js b/js/src/jit-test/tests/debug/Debugger-onNewPromise-05.js
new file mode 100644
index 0000000000..08762de687
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewPromise-05.js
@@ -0,0 +1,24 @@
+// Creating a promise within an onNewPromise handler causes a recursive handler
+// invocation.
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+var log;
+var depth;
+
+dbg.onNewPromise = function (promise) {
+ log += '('; depth++;
+
+ assertEq(promise.seen, undefined);
+ promise.seen = true;
+
+ if (depth < 3)
+ gw.executeInGlobal(`new Promise(_=>{})`);
+
+ log += ')'; depth--;
+};
+
+log = '';
+depth = 0;
+new g.Promise(function (){});
+assertEq(log, '((()))');
diff --git a/js/src/jit-test/tests/debug/Debugger-onNewPromise-06.js b/js/src/jit-test/tests/debug/Debugger-onNewPromise-06.js
new file mode 100644
index 0000000000..0e24cdf372
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewPromise-06.js
@@ -0,0 +1,35 @@
+// Resumption values from onNewPromise handlers are disallowed.
+
+load(libdir + 'asserts.js');
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var log;
+
+dbg.onNewPromise = function (g) { log += 'n'; return undefined; };
+log = '';
+assertEq(typeof new g.Promise(function (){}), "object");
+assertEq(log, 'n');
+
+dbg.uncaughtExceptionHook = function (ex) { assertEq(/disallowed/.test(ex), true); log += 'u'; }
+dbg.onNewPromise = function (g) { log += 'n'; return { return: "snoo" }; };
+log = '';
+assertEq(typeof new g.Promise(function (){}), "object");
+assertEq(log, 'nu');
+
+dbg.onNewPromise = function (g) { log += 'n'; return { throw: "snoo" }; };
+log = '';
+assertEq(typeof new g.Promise(function (){}), "object");
+assertEq(log, 'nu');
+
+dbg.onNewPromise = function (g) { log += 'n'; return null; };
+log = '';
+assertEq(typeof new g.Promise(function (){}), "object");
+assertEq(log, 'nu');
+
+dbg.uncaughtExceptionHook = function (ex) { assertEq(/foopy/.test(ex), true); log += 'u'; }
+dbg.onNewPromise = function (g) { log += 'n'; throw "foopy"; };
+log = '';
+assertEq(typeof new g.Promise(function (){}), "object");
+assertEq(log, 'nu');
+
diff --git a/js/src/jit-test/tests/debug/Debugger-onNewPromise-07.js b/js/src/jit-test/tests/debug/Debugger-onNewPromise-07.js
new file mode 100644
index 0000000000..4ef41cc67c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewPromise-07.js
@@ -0,0 +1,13 @@
+// Errors in onNewPromise handlers are reported correctly, and don't mess up the
+// promise creation.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+let e;
+dbg.uncaughtExceptionHook = ee => { e = ee; };
+dbg.onNewPromise = () => { throw new Error("woops!"); };
+
+assertEq(typeof new g.Promise(function (){}), "object");
+assertEq(!!e, true);
+assertEq(!!e.message.match(/woops/), true);
diff --git a/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-01.js b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-01.js
new file mode 100644
index 0000000000..84f8323997
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-01.js
@@ -0,0 +1,18 @@
+// Test that the onPromiseSettled hook gets called when a promise settles.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+
+let log = "";
+let pw;
+dbg.onPromiseSettled = pw_ => {
+ pw = pw_;
+ log += "s";
+};
+
+let p = new g.Promise(function (){});
+g.settlePromiseNow(p);
+
+assertEq(log, "s");
+assertEq(pw, gw.makeDebuggeeValue(p));
diff --git a/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-02.js b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-02.js
new file mode 100644
index 0000000000..b78cbccf11
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-02.js
@@ -0,0 +1,24 @@
+// onPromiseSettled handlers fire, until they are removed.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var log;
+
+log = '';
+g.settlePromiseNow(new g.Promise(function (){}));
+assertEq(log, '');
+
+dbg.onPromiseSettled = function (promise) {
+ log += 's';
+ assertEq(promise.seen, undefined);
+ promise.seen = true;
+};
+
+log = '';
+g.settlePromiseNow(new g.Promise(function (){}));
+assertEq(log, 's');
+
+log = '';
+dbg.onPromiseSettled = undefined;
+g.settlePromiseNow(new g.Promise(function (){}));
+assertEq(log, '');
diff --git a/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-03.js b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-03.js
new file mode 100644
index 0000000000..2f11252480
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-03.js
@@ -0,0 +1,40 @@
+// onPromiseSettled handlers on different Debugger instances are independent.
+var g = newGlobal({newCompartment: true});
+var dbg1 = new Debugger(g);
+var log1;
+function h1(promise) {
+ log1 += 's';
+ assertEq(promise.seen, undefined);
+ promise.seen = true;
+}
+
+var dbg2 = new Debugger(g);
+var log2;
+function h2(promise) {
+ log2 += 's';
+ assertEq(promise.seen, undefined);
+ promise.seen = true;
+}
+
+log1 = log2 = '';
+g.settlePromiseNow(new g.Promise(function (){}));
+assertEq(log1, '');
+assertEq(log2, '');
+
+log1 = log2 = '';
+dbg1.onPromiseSettled = h1;
+g.settlePromiseNow(new g.Promise(function (){}));
+assertEq(log1, 's');
+assertEq(log2, '');
+
+log1 = log2 = '';
+dbg2.onPromiseSettled = h2;
+g.settlePromiseNow(new g.Promise(function (){}));
+assertEq(log1, 's');
+assertEq(log2, 's');
+
+log1 = log2 = '';
+dbg1.onPromiseSettled = undefined;
+g.settlePromiseNow(new g.Promise(function (){}));
+assertEq(log1, '');
+assertEq(log2, 's');
diff --git a/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-04.js b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-04.js
new file mode 100644
index 0000000000..6a5297223a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-04.js
@@ -0,0 +1,14 @@
+// An onPromiseSettled handler can disable itself.
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var log;
+
+dbg.onPromiseSettled = function (promise) {
+ log += 's';
+ dbg.onPromiseSettled = undefined;
+};
+
+log = '';
+g.settlePromiseNow(new g.Promise(function (){}));
+g.settlePromiseNow(new g.Promise(function (){}));
+assertEq(log, 's');
diff --git a/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-05.js b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-05.js
new file mode 100644
index 0000000000..8c99684619
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-05.js
@@ -0,0 +1,25 @@
+// Settling a promise within an onPromiseSettled handler causes a recursive
+// handler invocation.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+var log;
+var depth;
+
+dbg.onPromiseSettled = function (promise) {
+ log += '('; depth++;
+
+ assertEq(promise.seen, undefined);
+ promise.seen = true;
+
+ if (depth < 3) {
+ gw.executeInGlobal(`settlePromiseNow(new Promise(_=>{}));`);
+ }
+ log += ')'; depth--;
+};
+
+log = '';
+depth = 0;
+g.settlePromiseNow(new g.Promise(_=>{}));
+assertEq(log, '((()))');
diff --git a/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-06.js b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-06.js
new file mode 100644
index 0000000000..4ed8947ab8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-06.js
@@ -0,0 +1,35 @@
+// Resumption values from onPromiseSettled handlers are disallowed.
+
+load(libdir + 'asserts.js');
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var log;
+
+dbg.onPromiseSettled = function (g) { log += 's'; return undefined; };
+log = '';
+g.settlePromiseNow(new g.Promise(function (){}));
+assertEq(log, 's');
+
+dbg.uncaughtExceptionHook = function (ex) { assertEq(/disallowed/.test(ex), true); log += 'u'; }
+dbg.onPromiseSettled = function (g) { log += 's'; return { return: "snoo" }; };
+log = '';
+g.settlePromiseNow(new g.Promise(function (){}));
+assertEq(log, 'su');
+
+dbg.onPromiseSettled = function (g) { log += 's'; return { throw: "snoo" }; };
+log = '';
+g.settlePromiseNow(new g.Promise(function (){}));
+assertEq(log, 'su');
+
+dbg.onPromiseSettled = function (g) { log += 's'; return null; };
+log = '';
+g.settlePromiseNow(new g.Promise(function (){}));
+assertEq(log, 'su');
+
+dbg.uncaughtExceptionHook = function (ex) { assertEq(/foopy/.test(ex), true); log += 'u'; }
+dbg.onPromiseSettled = function (g) { log += 's'; throw "foopy"; };
+log = '';
+g.settlePromiseNow(new g.Promise(function (){}));
+assertEq(log, 'su');
+
diff --git a/js/src/jit-test/tests/debug/Debugger-setInstrumentation-01.js b/js/src/jit-test/tests/debug/Debugger-setInstrumentation-01.js
new file mode 100644
index 0000000000..44378e26df
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-setInstrumentation-01.js
@@ -0,0 +1,196 @@
+// Test breakpoint site instrumentation functionality with a variety of control
+// flow constructs.
+
+load(libdir + 'eqArrayHelper.js');
+
+var g = newGlobal({ newCompartment: true });
+var dbg = Debugger(g);
+var gdbg = dbg.addDebuggee(g);
+
+var allScripts = [];
+
+function setScriptId(script) {
+ script.setInstrumentationId(allScripts.length);
+ allScripts.push(script);
+
+ script.getChildScripts().forEach(setScriptId);
+}
+
+dbg.onNewScript = setScriptId;
+
+function getOffsetLine(scriptId, offset) {
+ const script = allScripts[scriptId];
+ return script.getOffsetMetadata(offset).lineNumber;
+}
+
+const executedLines = [];
+gdbg.setInstrumentation(
+ gdbg.makeDebuggeeValue((kind, script, offset) => {
+ executedLines.push(getOffsetLine(script, offset));
+ }),
+ ["breakpoint"]
+);
+
+function testFunction(fn, expected) {
+ for (var i = 0; i < 3; i++) {
+ fn();
+ }
+
+ assertEq(executedLines.length, 0);
+ gdbg.setInstrumentationActive(true);
+
+ for (var i = 0; i < 5; i++) {
+ executedLines.length = 0;
+ fn();
+ assertEqArray(executedLines, expected);
+ }
+
+ executedLines.length = 0;
+ gdbg.setInstrumentationActive(false);
+}
+
+g.eval(`
+function basic() {
+ var a = 0;
+ a++;
+}
+`);
+
+testFunction(() => g.basic(),
+ [3, 4, 5]);
+
+g.eval(`
+function cforloop() {
+ for (var i = 0;
+ i < 5;
+ i++) {
+ }
+}
+`);
+
+testFunction(() => g.cforloop(),
+ [3, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 7]);
+
+g.eval(`
+function ifstatement(n) {
+ if (n) {
+ return 1;
+ }
+ return 0;
+}
+`);
+
+testFunction(() => g.ifstatement(true), [3, 4]);
+testFunction(() => g.ifstatement(false), [3, 6]);
+
+g.eval(`
+function whileloop(n) {
+ while (n < 3) {
+ n++;
+ }
+}
+`);
+
+testFunction(() => g.whileloop(1),
+ [3, 4, 3, 4, 3, 6]);
+
+g.eval(`
+function dowhileloop(n) {
+ do {
+ if (++n == 3) {
+ break;
+ }
+ } while (true);
+}
+`);
+
+testFunction(() => g.dowhileloop(1),
+ [4, 7, 4, 5, 8]);
+
+g.eval(`
+function forinloop(v) {
+ for (let i in v) {
+ var a = i;
+ }
+}
+`);
+
+testFunction(() => g.forinloop([1,2,3]),
+ [3, 4, 4, 4, 6]);
+
+g.eval(`
+function forofloop(v) {
+ for (const i of v) {
+ var a = i;
+ }
+}
+`);
+
+testFunction(() => g.forofloop([1,2,3]),
+ [3, 4, 4, 4, 6]);
+
+g.eval(`
+function normalswitch(v) {
+ switch (v) {
+ case 3:
+ return 0;
+ case "three":
+ return 1;
+ default:
+ return 2;
+ }
+}
+`);
+
+testFunction(() => g.normalswitch(3), [3, 5]);
+testFunction(() => g.normalswitch("three"), [3, 7]);
+testFunction(() => g.normalswitch(2), [3, 9]);
+
+g.eval(`
+function tableswitch(v) {
+ switch (v) {
+ case 0:
+ case 1:
+ case 2:
+ break;
+ case 3:
+ return 1;
+ default:
+ return 2;
+ }
+}
+`);
+
+testFunction(() => g.tableswitch(0), [3, 7, 13]);
+testFunction(() => g.tableswitch(3), [3, 9]);
+testFunction(() => g.tableswitch(5), [3, 11]);
+
+g.eval(`
+function trycatch(f) {
+ try {
+ f();
+ } catch (e) {
+ return e;
+ }
+}
+`);
+
+testFunction(() => g.trycatch(() => { throw 3 }), [4, 6]);
+testFunction(() => g.trycatch(() => {}), [4, 8]);
+
+g.eval(`
+function tryfinally(f) {
+ var a;
+ try {
+ f();
+ } finally {
+ a = 0;
+ }
+ a = 1;
+}
+`);
+
+testFunction(() => {
+ try { g.tryfinally(() => { throw 3 }); } catch (e) {}
+}, [5, 7]);
+testFunction(() => g.tryfinally(() => {}), [5, 7, 9, 10]);
diff --git a/js/src/jit-test/tests/debug/Debugger-setInstrumentation-02.js b/js/src/jit-test/tests/debug/Debugger-setInstrumentation-02.js
new file mode 100644
index 0000000000..68111b192e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-setInstrumentation-02.js
@@ -0,0 +1,130 @@
+// Test main/entry/exit point instrumentation.
+
+load(libdir + 'eqArrayHelper.js');
+
+ignoreUnhandledRejections();
+
+var g = newGlobal({ newCompartment: true });
+var dbg = Debugger(g);
+var gdbg = dbg.addDebuggee(g);
+
+var allScripts = [];
+
+function setScriptId(script) {
+ script.setInstrumentationId(allScripts.length);
+ allScripts.push(script);
+
+ script.getChildScripts().forEach(setScriptId);
+}
+
+dbg.onNewScript = setScriptId;
+
+const executedPoints = [];
+function hitPoint(kind, scriptId) {
+ const name = allScripts[scriptId].displayName;
+ executedPoints.push(`${name}:${kind}`);
+}
+
+gdbg.setInstrumentation(
+ gdbg.makeDebuggeeValue(hitPoint),
+ ["main", "entry", "exit"]
+);
+
+function testFunction(fn, expected) {
+ for (var i = 0; i < 3; i++) {
+ try { fn(); } catch (e) {}
+ drainJobQueue();
+ }
+
+ assertEq(executedPoints.length, 0);
+ gdbg.setInstrumentationActive(true);
+
+ for (var i = 0; i < 5; i++) {
+ executedPoints.length = 0;
+ try { fn(); } catch (e) {}
+ drainJobQueue();
+ assertEqArray(executedPoints, expected);
+ }
+
+ executedPoints.length = 0;
+ gdbg.setInstrumentationActive(false);
+}
+
+g.eval(`
+function basic() {
+ var a = 0;
+ a++;
+}
+`);
+
+testFunction(() => g.basic(), ["basic:main", "basic:exit"]);
+
+g.eval(`
+function thrower(v) {
+ if (v) {
+ throw new Error();
+ }
+}
+`);
+
+testFunction(() => g.thrower(0), ["thrower:main", "thrower:exit"]);
+testFunction(() => g.thrower(1), ["thrower:main"]);
+
+g.eval(`
+function* yielder() { yield 1; }
+function iterator() {
+ for (var n of yielder()) {}
+}
+`);
+
+testFunction(() => g.iterator(), [
+ "iterator:main",
+ "yielder:main", "yielder:exit",
+ "yielder:entry", "yielder:exit",
+ "yielder:entry", "yielder:exit",
+ "iterator:exit"
+]);
+
+g.eval(`
+function promiser(n) {
+ return new Promise(function promiseInternal(resolve, reject) {
+ if (n) {
+ reject(new Error());
+ } else {
+ resolve();
+ }
+ });
+}
+function f1() {}
+async function asyncer(n) { await promiser(n) }
+async function asyncerCatch(n) { try { await promiser(n) } catch (e) { f1() } }
+async function asyncerFinally(n) { try { await promiser(n) } finally { f1() } }
+`);
+
+testFunction(() => g.asyncer(0), [
+ "asyncer:main",
+ "promiser:main", "promiseInternal:main", "promiseInternal:exit", "promiser:exit",
+ "asyncer:exit",
+ "asyncer:entry", "asyncer:exit"
+]);
+testFunction(() => g.asyncer(1), [
+ "asyncer:main",
+ "promiser:main", "promiseInternal:main", "promiseInternal:exit", "promiser:exit",
+ "asyncer:exit",
+ "asyncer:entry", // Note: no exit, GeneratorThrowOrReturn is called.
+ "asyncer:entry", "asyncer:exit"
+]);
+testFunction(() => g.asyncerCatch(1), [
+ "asyncerCatch:main",
+ "promiser:main", "promiseInternal:main", "promiseInternal:exit", "promiser:exit",
+ "asyncerCatch:exit",
+ "asyncerCatch:entry", // Note: no exit, GeneratorThrowOrReturn is called.
+ "asyncerCatch:entry", "f1:main", "f1:exit", "asyncerCatch:exit"
+]);
+testFunction(() => g.asyncerFinally(1), [
+ "asyncerFinally:main",
+ "promiser:main", "promiseInternal:main", "promiseInternal:exit", "promiser:exit",
+ "asyncerFinally:exit",
+ "asyncerFinally:entry", // Note: no exit, GeneratorThrowOrReturn is called.
+ "asyncerFinally:entry", "f1:main", "f1:exit", "asyncerFinally:entry", "asyncerFinally:exit"
+]);
diff --git a/js/src/jit-test/tests/debug/Debugger-setInstrumentation-03.js b/js/src/jit-test/tests/debug/Debugger-setInstrumentation-03.js
new file mode 100644
index 0000000000..aed1888718
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-setInstrumentation-03.js
@@ -0,0 +1,72 @@
+// Test instrumentation functionality when using generators and async functions.
+
+load(libdir + 'eqArrayHelper.js');
+
+var g = newGlobal({ newCompartment: true });
+var dbg = Debugger(g);
+var gdbg = dbg.addDebuggee(g);
+
+var allScripts = [];
+
+function setScriptId(script) {
+ script.setInstrumentationId(allScripts.length);
+ allScripts.push(script);
+
+ script.getChildScripts().forEach(setScriptId);
+}
+
+dbg.onNewScript = setScriptId;
+
+function getOffsetLine(scriptId, offset) {
+ const script = allScripts[scriptId];
+ return script.getOffsetMetadata(offset).lineNumber;
+}
+
+const executedLines = [];
+gdbg.setInstrumentation(
+ gdbg.makeDebuggeeValue((kind, script, offset) => {
+ executedLines.push(getOffsetLine(script, offset));
+ }),
+ ["breakpoint"]
+);
+
+function testFunction(fn, expected) {
+ gdbg.setInstrumentationActive(true);
+
+ for (var i = 0; i < 5; i++) {
+ executedLines.length = 0;
+ fn();
+ assertEqArray(executedLines, expected);
+ }
+
+ gdbg.setInstrumentationActive(false);
+}
+
+g.eval(`
+async function asyncfun() {
+ await Promise.resolve(0);
+ await Promise.resolve(1);
+ var a = 0;
+ await Promise.resolve(2);
+ a++;
+}
+`);
+
+testFunction(() => {
+ async function f() { await g.asyncfun(); }
+ f();
+ drainJobQueue();
+}, [3, 3, 4, 4, 5, 6, 6, 7]);
+
+g.eval(`
+function *generator() {
+ yield 1;
+ var a = 0;
+ yield 2;
+ a++;
+}
+`);
+
+testFunction(() => {
+ for (const i of g.generator()) {}
+}, [3, 4, 5, 6]);
diff --git a/js/src/jit-test/tests/debug/Debugger-setInstrumentation-04.js b/js/src/jit-test/tests/debug/Debugger-setInstrumentation-04.js
new file mode 100644
index 0000000000..0e86eb9096
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-setInstrumentation-04.js
@@ -0,0 +1,34 @@
+// Test that instrumenting a function does not interfere with script
+// disassembly when reporting errors.
+
+var g = newGlobal({ newCompartment: true });
+var dbg = Debugger(g);
+var gdbg = dbg.addDebuggee(g);
+
+function setScriptId(script) {
+ script.setInstrumentationId(0);
+ script.getChildScripts().forEach(setScriptId);
+}
+
+dbg.onNewScript = setScriptId;
+
+const executedLines = [];
+gdbg.setInstrumentation(
+ gdbg.makeDebuggeeValue(() => {}),
+ ["breakpoint"]
+);
+
+g.eval(`
+function foo(v) {
+ v.f().g();
+}
+`);
+
+var gotException = false;
+try {
+ g.foo({ f: () => { return { g: 0 } } });
+} catch (e) {
+ gotException = true;
+ assertEq(e.toString().includes("v.f().g is not a function"), true);
+}
+assertEq(gotException, true);
diff --git a/js/src/jit-test/tests/debug/Debugger-setInstrumentation-05.js b/js/src/jit-test/tests/debug/Debugger-setInstrumentation-05.js
new file mode 100644
index 0000000000..4a138c8480
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-setInstrumentation-05.js
@@ -0,0 +1,43 @@
+// Test some inputs that should fail instrumentation validation.
+
+var g = newGlobal({ newCompartment: true });
+var dbg = Debugger(g);
+var gdbg = dbg.addDebuggee(g);
+
+function assertThrows(fn, text) {
+ try {
+ fn();
+ assertEq(true, false);
+ } catch (e) {
+ if (!e.toString().includes(text)) {
+ print(`Expected ${text}, got ${e}`);
+ assertEq(true, false);
+ }
+ }
+}
+
+assertThrows(() => gdbg.setInstrumentation(undefined, []),
+ "Instrumentation callback must be an object");
+assertThrows(() => gdbg.setInstrumentation(gdbg.makeDebuggeeValue({}), ["foo"]),
+ "Unknown instrumentation kind");
+
+let lastScript = null;
+dbg.onNewScript = script => lastScript = script;
+
+assertThrows(() => {
+ g.eval(`x = 3`);
+ lastScript.setInstrumentationId("twelve");
+}, "Script instrumentation ID must be a number");
+
+assertThrows(() => {
+ g.eval(`x = 3`);
+ lastScript.setInstrumentationId(10);
+ lastScript.setInstrumentationId(11);
+}, "Script instrumentation ID is already set");
+
+dbg.onNewScript = undefined;
+gdbg.setInstrumentation(gdbg.makeDebuggeeValue({}), ["main"]);
+gdbg.setInstrumentationActive(true);
+
+assertThrows(() => { g.eval(`x = 3`) },
+ "Instrumentation ID not set for script");
diff --git a/js/src/jit-test/tests/debug/Debugger-setInstrumentation-06.js b/js/src/jit-test/tests/debug/Debugger-setInstrumentation-06.js
new file mode 100644
index 0000000000..14b97e5f81
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-setInstrumentation-06.js
@@ -0,0 +1,105 @@
+// Test instrumentation on property and element accesses.
+
+load(libdir + 'eqArrayHelper.js');
+
+var g = newGlobal({ newCompartment: true });
+var dbg = Debugger(g);
+var gdbg = dbg.addDebuggee(g);
+
+function setScriptId(script) {
+ script.setInstrumentationId(0);
+ script.getChildScripts().forEach(setScriptId);
+}
+
+dbg.onNewScript = setScriptId;
+
+function getOffsetLine(scriptId, offset) {
+ const script = allScripts[scriptId];
+ return script.getOffsetMetadata(offset).lineNumber;
+}
+
+const executedOperations = [];
+
+function propertyRead(script, offset, obj, prop) {
+ executedOperations.push(`read:${obj.label}:${prop}`);
+}
+
+function propertyWrite(script, offset, obj, prop, rhs) {
+ executedOperations.push(`write:${obj.label}:${prop}:${typeof rhs}`);
+}
+
+function callback(kind, ...args) {
+ switch (kind) {
+ case "getProperty":
+ case "getElement":
+ propertyRead(...args);
+ break;
+ case "setProperty":
+ case "setElement":
+ propertyWrite(...args);
+ }
+}
+
+gdbg.setInstrumentation(
+ gdbg.makeDebuggeeValue(callback),
+ ["getProperty", "getElement", "setProperty", "setElement"]
+);
+
+function testFunction(fn, expected) {
+ gdbg.setInstrumentationActive(true);
+ for (var i = 0; i < 5; i++) {
+ executedOperations.length = 0;
+ fn();
+ assertEqArray(executedOperations, expected);
+ }
+ gdbg.setInstrumentationActive(false);
+}
+
+g.eval(`
+function basic(o, i) {
+ o.x = o.x + 1;
+ o[i] = o[i] + 1;
+}
+`);
+
+testFunction(() => g.basic({ label: 0}, 3), [
+ "read:0:x",
+ "write:0:x:number",
+ "read:0:3",
+ "write:0:3:number",
+]);
+
+g.eval(`
+function incdec(o, i) {
+ o.x++;
+ o[i]++;
+}
+`);
+
+testFunction(() => g.incdec({ label: 0}, 3), [
+ "read:0:x",
+ "write:0:x:number",
+ "read:0:3",
+ "write:0:3:number",
+]);
+
+g.eval(`
+function destructure({ f, g }) {
+ const { h, i } = f;
+}
+`);
+
+testFunction(() => g.destructure({ label: 0, f: { label: 1 }}, 3), [
+ "read:0:f",
+ "read:0:g",
+ "read:1:h",
+ "read:1:i",
+]);
+
+g.eval(`
+function destructureArray([a, b]) {}
+`);
+
+// Array destructuring uses iterators instead of getElement accesses,
+// and no property accesses will be captured by instrumentation.
+testFunction(() => g.destructureArray([1, 2]), []);
diff --git a/js/src/jit-test/tests/debug/Debugger-setInstrumentation-bug1571169.js b/js/src/jit-test/tests/debug/Debugger-setInstrumentation-bug1571169.js
new file mode 100644
index 0000000000..6359d96cad
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-setInstrumentation-bug1571169.js
@@ -0,0 +1,18 @@
+// |jit-test| error: Error
+
+var lfOffThreadGlobal = newGlobal();
+evaluate(`
+ nukeAllCCWs();
+ var g92 = newGlobal({ newCompartment: true });
+ var dbg = Debugger(g92);
+ var gdbg = dbg.addDebuggee(g92);
+ gdbg.setInstrumentation(
+ gdbg.makeDebuggeeValue((kind, script, offset) => {}),
+ ["breakpoint"]
+ );
+ gdbg.setInstrumentationActive(true);
+ g92.eval(\`
+ function basic() {}
+ \`);
+`);
+
diff --git a/js/src/jit-test/tests/debug/Environment-01.js b/js/src/jit-test/tests/debug/Environment-01.js
new file mode 100644
index 0000000000..40f3b03570
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-01.js
@@ -0,0 +1,23 @@
+// A live Environment can observe the new variables introduced by ES5 non-strict direct eval.
+
+var g = newGlobal({newCompartment: true});
+g.eval("var x = 'global'; function f(s) { h(); eval(s); h(); }");
+g.eval("function h() { debugger; }");
+var dbg = Debugger(g);
+var env = undefined;
+var hits = 0;
+dbg.onDebuggerStatement = function (hframe) {
+ if (env === undefined) {
+ // First debugger statement.
+ env = hframe.older.environment;
+ assertEq(env.find("x") !== env, true);
+ assertEq(env.names().indexOf("x"), -1);
+ } else {
+ // Second debugger statement, post-eval.
+ assertEq(env.find("x"), env);
+ assertEq(env.names().indexOf("x") >= 0, true);
+ }
+ hits++;
+};
+g.f("var x = 'local';");
+assertEq(hits, 2);
diff --git a/js/src/jit-test/tests/debug/Environment-02.js b/js/src/jit-test/tests/debug/Environment-02.js
new file mode 100644
index 0000000000..77af721a29
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-02.js
@@ -0,0 +1,20 @@
+// The last Environment on the environment chain always has .type == "object" and .object === the global object.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+g.eval("function h() { debugger; }");
+var hits = 0;
+dbg.onDebuggerStatement = function (hframe) {
+ var env = hframe.older.environment;
+ while (env.parent)
+ env = env.parent;
+ assertEq(env.type, "object");
+ assertEq(env.object, gw);
+ hits++;
+};
+
+g.eval("h();");
+g.eval("(function () { h(); return []; })();");
+g.eval("with (Math) { h(-2 * PI); }");
+assertEq(hits, 3);
diff --git a/js/src/jit-test/tests/debug/Environment-03.js b/js/src/jit-test/tests/debug/Environment-03.js
new file mode 100644
index 0000000000..812ee21cbb
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-03.js
@@ -0,0 +1,10 @@
+// Test that getting a function's environment can unlazify scripts.
+
+var g = newGlobal({newCompartment: true});
+g.eval('function f() { }');
+var dbg = new Debugger;
+var gw = dbg.makeGlobalObjectReference(g);
+var fw = gw.getOwnPropertyDescriptor('f').value;
+gc();
+dbg.addDebuggee(g);
+var fenv = fw.environment;
diff --git a/js/src/jit-test/tests/debug/Environment-Function-prototype.js b/js/src/jit-test/tests/debug/Environment-Function-prototype.js
new file mode 100644
index 0000000000..97e54f62fc
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-Function-prototype.js
@@ -0,0 +1,7 @@
+// Don't crash when getting the Debugger.Environment of a frame inside
+// Function.prototype.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+dbg.onEnterFrame = function (frame) { frame.environment; };
+g.Function.prototype();
diff --git a/js/src/jit-test/tests/debug/Environment-bug-1431461.js b/js/src/jit-test/tests/debug/Environment-bug-1431461.js
new file mode 100644
index 0000000000..188f3ee5a7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-bug-1431461.js
@@ -0,0 +1,26 @@
+// Check that duplicate bindings are not created for let/const variables.
+
+let g = newGlobal({newCompartment: true});
+let dbg = new Debugger(g);
+
+g.eval(`
+function f(x, y=x) {
+ let z = "Z";
+ debugger;
+ return x + y + z;
+}
+`);
+
+let hits = 0;
+let names = [];
+
+dbg.onDebuggerStatement = frame => {
+ hits++;
+ for (let env = frame.environment; env.type !== "object"; env = env.parent) {
+ names.push(...env.names());
+ }
+};
+
+assertEq(g.f("X", "Y"), "XYZ");
+assertEq(hits, 1);
+assertEq(names.sort().join(", "), "arguments, x, y, z");
diff --git a/js/src/jit-test/tests/debug/Environment-calleeScript-01.js b/js/src/jit-test/tests/debug/Environment-calleeScript-01.js
new file mode 100644
index 0000000000..a5417b9070
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-calleeScript-01.js
@@ -0,0 +1,48 @@
+// Debugger.Environment.prototype.calleeScript reveals the script of function
+// environments.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+
+function check(code, expectedType, expectedCallee) {
+ print("check(" + JSON.stringify(code) + ")");
+ var hits;
+ dbg.onDebuggerStatement = function (frame) {
+ hits++;
+ var env = frame.environment;
+ assertEq(env.type, expectedType);
+ assertEq(env.calleeScript, expectedCallee ? expectedCallee.script : null);
+ };
+ hits = 0;
+ g.eval(code);
+ assertEq(hits, 1);
+}
+
+check('debugger;', 'declarative', null);
+check('with({}) { debugger; };', 'with', null);
+check('{ let x=1; debugger; };', 'declarative', null);
+
+g.eval('function f() { debugger; }');
+check('f()', 'declarative', gw.makeDebuggeeValue(g.f));
+
+g.eval('function g() { h(); }');
+g.eval('function h() { debugger; }');
+check('g()', 'declarative', gw.makeDebuggeeValue(g.h));
+
+// All evals get a lexical scope.
+check('"use strict"; eval("debugger");', 'declarative', null);
+g.eval('function j() { "use strict"; eval("debugger;"); }');
+check('j()', 'declarative', null);
+
+// All evals get a lexical scope.
+check('eval("debugger");', 'declarative', null);
+
+g.eval('function* m() { debugger; yield true; }');
+check('m().next();', 'declarative', gw.makeDebuggeeValue(g.m));
+
+g.eval('function n() { { let x = 1; debugger; } }');
+check('n()', 'declarative', null);
+
+g.eval('function* o() { debugger; yield true; }');
+check('o().next();', 'declarative', gw.makeDebuggeeValue(g.o));
diff --git a/js/src/jit-test/tests/debug/Environment-calleeScript-02.js b/js/src/jit-test/tests/debug/Environment-calleeScript-02.js
new file mode 100644
index 0000000000..d518c4b8a0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-calleeScript-02.js
@@ -0,0 +1,25 @@
+// Debugger.Environment.prototype.calleeScript gets the right script.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+
+g.eval('function f(x) { return function (y) { eval(""); debugger; return x + y; } }');
+g.eval('var g = f(2);');
+g.eval('var h = f(3);');
+
+function check(fun, label) {
+ print("check(" + label + ")");
+ var hits;
+ dbg.onDebuggerStatement = function (frame) {
+ hits++;
+ var env = frame.environment;
+ assertEq(env.calleeScript, gw.makeDebuggeeValue(fun).script);
+ };
+ hits = 0;
+ fun();
+ assertEq(hits, 1);
+}
+
+check(g.g, 'g.g');
+check(g.h, 'g.h');
diff --git a/js/src/jit-test/tests/debug/Environment-calleeScript-03.js b/js/src/jit-test/tests/debug/Environment-calleeScript-03.js
new file mode 100644
index 0000000000..f21522b4cc
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-calleeScript-03.js
@@ -0,0 +1,25 @@
+// Environments of different instances of the same generator have the same
+// calleeScript.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+
+function check(gen, label) {
+ print("check(" + label + ")");
+ var hits;
+ dbg.onDebuggerStatement = function (frame) {
+ hits++;
+ var env = frame.environment;
+ assertEq(env.calleeScript, gw.makeDebuggeeValue(g.f).script);
+ };
+ hits = 0;
+ gen.next();
+ assertEq(hits, 1);
+}
+
+g.eval('function* f(x) { debugger; yield x; }');
+g.eval('var g = f(2);');
+g.eval('var h = f(3);');
+check(g.g, 'g.g');
+check(g.h, 'g.h');
diff --git a/js/src/jit-test/tests/debug/Environment-find-01.js b/js/src/jit-test/tests/debug/Environment-find-01.js
new file mode 100644
index 0000000000..abce77df16
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-find-01.js
@@ -0,0 +1,19 @@
+// find sees that vars are hoisted out of with statements.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ assertEq(frame.environment.find("x").type, "with");
+ hits++;
+};
+
+assertEq(g.eval("(function () {\n" +
+ " function g() { x = 1; }\n" +
+ " with ({x: 2}) {\n" +
+ " var x;\n" +
+ " debugger;\n" +
+ " return x;\n" +
+ " }\n" +
+ "})();"), 2);
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Environment-find-02.js b/js/src/jit-test/tests/debug/Environment-find-02.js
new file mode 100644
index 0000000000..c8ab45b786
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-find-02.js
@@ -0,0 +1,18 @@
+// env.find() finds nonenumerable names in the global environment.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var hits = 0;
+g.h = function () {
+ var env = dbg.getNewestFrame().environment;
+ var last = env;
+ while (last.parent)
+ last = last.parent;
+
+ assertEq(env.find("Array"), last);
+ hits++;
+};
+
+g.eval("h();");
+g.eval("(function () { h(); })();");
+assertEq(hits, 2);
diff --git a/js/src/jit-test/tests/debug/Environment-find-03.js b/js/src/jit-test/tests/debug/Environment-find-03.js
new file mode 100644
index 0000000000..3800e247d8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-find-03.js
@@ -0,0 +1,20 @@
+// env.find() finds nonenumerable properties in with statements.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var hits = 0;
+g.h = function () {
+ var frame = dbg.getNewestFrame();
+ var target = frame.eval("obj").return;
+ var env = frame.environment.find("PI");
+ assertEq(env.object, target);
+ hits++;
+};
+
+g.obj = g.Math;
+g.eval("with (obj) h();");
+g.eval("with (Math) { let x = 12; h(); }");
+g.eval("obj = {};\n" +
+ "Object.defineProperty(obj, 'PI', {enumerable: false, value: 'Marlowe'});\n" +
+ "with (obj) h();\n");
+assertEq(hits, 3);
diff --git a/js/src/jit-test/tests/debug/Environment-find-04.js b/js/src/jit-test/tests/debug/Environment-find-04.js
new file mode 100644
index 0000000000..24a714e252
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-find-04.js
@@ -0,0 +1,21 @@
+// env.find throws a TypeError if the argument is not an identifier.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var hits = 0;
+g.h = function () {
+ var env = dbg.getNewestFrame().environment;
+ assertThrowsInstanceOf(function () { env.find(); }, TypeError);
+ assertThrowsInstanceOf(function () { env.find(""); }, TypeError);
+ assertThrowsInstanceOf(function () { env.find(" "); }, TypeError);
+ assertThrowsInstanceOf(function () { env.find(0); }, TypeError);
+ assertThrowsInstanceOf(function () { env.find("0"); }, TypeError);
+ assertThrowsInstanceOf(function () { env.find("0xc"); }, TypeError);
+ assertThrowsInstanceOf(function () { env.find("Anna Karenina"); }, TypeError);
+ hits++;
+};
+g.eval("h();");
+g.eval("with ([1]) h();");
+assertEq(hits, 2);
diff --git a/js/src/jit-test/tests/debug/Environment-find-05.js b/js/src/jit-test/tests/debug/Environment-find-05.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-find-05.js
diff --git a/js/src/jit-test/tests/debug/Environment-find-06.js b/js/src/jit-test/tests/debug/Environment-find-06.js
new file mode 100644
index 0000000000..b34321647e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-find-06.js
@@ -0,0 +1,47 @@
+// Environment.prototype.find finds bindings that are function arguments, 'let'
+// bindings, or FunctionExpression names.
+
+var g = newGlobal({newCompartment: true});
+g.eval("function h() { debugger; }");
+
+var dbg = new Debugger(g);
+
+function test1(code) {
+ var hits = 0;
+ dbg.onDebuggerStatement = function (frame) {
+ var env = frame.older.environment.find('X');
+ assertEq(env.names().indexOf('X') !== -1, true);
+ assertEq(env.type, 'declarative');
+ assertEq(env.parent !== null, true);
+ hits++;
+ };
+ g.eval(code);
+ assertEq(hits, 1);
+}
+
+var manyNames = '';
+for (var i = 0; i < 2048; i++)
+ manyNames += 'x' + i + ', ';
+manyNames += 'X';
+
+function test2(code) {
+ print(code + " : one");
+ test1(code.replace('@@', 'X'));
+ print(code + " : many");
+ test1(code.replace('@@', manyNames));
+}
+
+test2('function f(@@) { h(); } f(1);');
+test2('function f(@@) { h(); } f();');
+test2('function f(@@) { return function g() { h(X); }; } f(1)();');
+test2('function f(@@) { return function g() { h(X); }; } f()();');
+
+test2(' { let @@ = 0; h(); }');
+test2('function f(a, b, c) { let @@ = 0; h(); } f(1, 2, 3);');
+test2(' { let @@ = 0; { let y = 0; h(); } }');
+test2('function f() { let @@ = 0; { let y = 0; h(); } } f();');
+test2(' { for (let @@ = 0; X < 1; X++) h(); }');
+test2('function f() { for (let @@ = 0; X < 1; X++) h(); } f();');
+
+test1('(function X() { h(); })();');
+test1('(function X(a, b, c) { h(); })(1, 2, 3);');
diff --git a/js/src/jit-test/tests/debug/Environment-find-07.js b/js/src/jit-test/tests/debug/Environment-find-07.js
new file mode 100644
index 0000000000..a6b2def550
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-find-07.js
@@ -0,0 +1,22 @@
+// We can find into and from optimized out scopes.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+dbg.addDebuggee(g);
+
+g.eval("" + function f() {
+ var x = 42;
+ function g() { }
+ g();
+});
+
+dbg.onEnterFrame = function (f) {
+ if (f.callee && (f.callee.name === "g")) {
+ genv = f.environment.parent;
+ assertEq(genv.optimizedOut, true);
+ assertEq(genv.find("f").type, "object");
+ assertEq(f.environment.find("x"), genv);
+ }
+}
+
+g.f();
diff --git a/js/src/jit-test/tests/debug/Environment-gc-01.js b/js/src/jit-test/tests/debug/Environment-gc-01.js
new file mode 100644
index 0000000000..ec6804cacb
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-gc-01.js
@@ -0,0 +1,19 @@
+// An Environment keeps its referent alive.
+
+var g = newGlobal({newCompartment: true});
+g.eval("function f(x) { return 2 * x; }");
+var dbg = Debugger(g);
+var env;
+dbg.onEnterFrame = function (frame) { env = frame.environment; };
+assertEq(g.f(22), 44);
+dbg.onEnterFrame = undefined;
+
+assertEq(env.find("x"), env);
+assertEq(env.names().join(","), "arguments,x");
+
+gc();
+g.gc(g);
+gc(env);
+
+assertEq(env.find("x"), env);
+assertEq(env.names().join(","), "arguments,x");
diff --git a/js/src/jit-test/tests/debug/Environment-gc-02.js b/js/src/jit-test/tests/debug/Environment-gc-02.js
new file mode 100644
index 0000000000..0ed5a0d103
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-gc-02.js
@@ -0,0 +1,28 @@
+// A closure's .environment keeps the lexical environment alive even if the closure is destroyed.
+
+var N = 4;
+var g = newGlobal({newCompartment: true});
+g.eval("function add(a) { return function (b) { return eval('a + b'); }; }");
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+var aw = gw.getOwnPropertyDescriptor("add").value;
+
+// Create N closures and collect environments.
+var arr = [];
+for (var i = 0; i < N; i++)
+ arr[i] = aw.call(null, i).return.environment;
+
+// Test that they work now.
+function check() {
+ for (var i = 0; i < N; i++) {
+ assertEq(arr[i].find("b"), null);
+ assertEq(arr[i].find("a"), arr[i]);
+ }
+}
+check();
+
+// Test that they work after gc.
+gc();
+gc({});
+g.gc(g);
+check();
diff --git a/js/src/jit-test/tests/debug/Environment-gc-03.js b/js/src/jit-test/tests/debug/Environment-gc-03.js
new file mode 100644
index 0000000000..3243ec65ba
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-gc-03.js
@@ -0,0 +1,21 @@
+// Test that block scopes cannot be resurrected by onStep.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+dbg.onDebuggerStatement = function(frame) {
+ frame.onStep = (function() {
+ frame.environment;
+ });
+};
+
+g.eval("debugger; for (let i = 0; i < 1; i++) (function(){});");
+
+// If the last freshened block scope was incorrectly resurrected by onStep
+// above, GCing will assert.
+gc();
+
+g.eval("debugger; { let i = 0; (function(){ i = 42; }); }");
+gc();
+
+g.eval("debugger; try { throw 42; } catch (e) { };");
+gc();
diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-01.js b/js/src/jit-test/tests/debug/Environment-getVariable-01.js
new file mode 100644
index 0000000000..9b3bbd9e1c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-getVariable-01.js
@@ -0,0 +1,14 @@
+// Environment.prototype.getVariable does not see variables bound in enclosing scopes.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ assertEq(frame.environment.getVariable("x"), 13);
+ assertEq(frame.environment.getVariable("k"), undefined);
+ assertEq(frame.environment.find("k").getVariable("k"), 3);
+ hits++;
+};
+g.eval("var k = 3; function f(x) { debugger; }");
+g.f(13);
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-02.js b/js/src/jit-test/tests/debug/Environment-getVariable-02.js
new file mode 100644
index 0000000000..9d76a03957
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-getVariable-02.js
@@ -0,0 +1,18 @@
+// getVariable works in function scopes.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ var lexicalEnv = frame.environment;
+ var varEnv = lexicalEnv.parent;
+ assertEq(varEnv.getVariable("a"), 1);
+ assertEq(varEnv.getVariable("b"), 2);
+ assertEq(varEnv.getVariable("c"), 3);
+ assertEq(varEnv.getVariable("d"), 7);
+ assertEq(lexicalEnv.getVariable("e"), 8);
+ hits++;
+};
+g.eval("function f(a, [b, c]) { var d = a + b + c + 1; let e = d + 1; debugger; }");
+g.f(1, [2, 3]);
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-03.js b/js/src/jit-test/tests/debug/Environment-getVariable-03.js
new file mode 100644
index 0000000000..9475d1cce3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-getVariable-03.js
@@ -0,0 +1,21 @@
+// getVariable sees bindings in let-block scopes.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var log = '';
+dbg.onDebuggerStatement = function (frame) {
+ log += frame.environment.getVariable("x");
+};
+g.eval("function f() {\n" +
+ " let x = 'a';\n" +
+ " debugger;\n" +
+ " for (let x = 0; x < 2; x++)\n" +
+ " if (x === 0)\n" +
+ " debugger;\n" +
+ " else {\n" +
+ " let x = 'b'; debugger;\n" +
+ " }\n" +
+ "}\n");
+g.f();
+g.eval("{ let x = 'd'; debugger; }");
+assertEq(log, 'a0bd');
diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-04.js b/js/src/jit-test/tests/debug/Environment-getVariable-04.js
new file mode 100644
index 0000000000..850446f577
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-getVariable-04.js
@@ -0,0 +1,12 @@
+// getVariable sees variables in function scopes added by non-strict direct eval.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var v;
+dbg.onDebuggerStatement = function (frame) {
+ v = frame.environment.getVariable("x");
+};
+
+g.eval("function f(s) { eval(s); debugger; }");
+g.f("var x = 'Q';");
+assertEq(v, 'Q');
diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-05.js b/js/src/jit-test/tests/debug/Environment-getVariable-05.js
new file mode 100644
index 0000000000..cfed91d86c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-getVariable-05.js
@@ -0,0 +1,10 @@
+// getVariable sees global variables.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var log = '';
+dbg.onDebuggerStatement = function (frame) {
+ log += frame.environment.parent.getVariable("x") + frame.environment.parent.getVariable("y");
+};
+g.eval("var x = 'a'; this.y = 'b'; debugger;");
+assertEq(log, 'ab');
diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-06.js b/js/src/jit-test/tests/debug/Environment-getVariable-06.js
new file mode 100644
index 0000000000..898d0df333
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-getVariable-06.js
@@ -0,0 +1,12 @@
+// getVariable sees properties inherited from the global object's prototype chain.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var log = '';
+dbg.onDebuggerStatement = function (frame) {
+ log += frame.environment.parent.getVariable("x") + frame.environment.parent.getVariable("y");
+};
+g.eval("Object.getPrototypeOf(this).x = 'a';\n" +
+ "Object.prototype.y = 'b';\n" +
+ "debugger;\n");
+assertEq(log, 'ab');
diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-07.js b/js/src/jit-test/tests/debug/Environment-getVariable-07.js
new file mode 100644
index 0000000000..9f48d3208e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-getVariable-07.js
@@ -0,0 +1,10 @@
+// getVariable can get properties from with-block scopes.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var v;
+dbg.onDebuggerStatement = function (frame) {
+ v = frame.environment.getVariable("x");
+};
+g.eval("var x = 1; { let x = 2; with ({x: 3}) { debugger; } }");
+assertEq(v, 3);
diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-08.js b/js/src/jit-test/tests/debug/Environment-getVariable-08.js
new file mode 100644
index 0000000000..ca77d582a1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-getVariable-08.js
@@ -0,0 +1,10 @@
+// getVariable sees properties inherited from a with-object's prototype chain.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var v;
+dbg.onDebuggerStatement = function (frame) {
+ v = frame.environment.getVariable("x");
+};
+g.eval("var x = 1; { let x = 2; with (Object.create({x: 3})) { debugger; } }");
+assertEq(v, 3);
diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-09.js b/js/src/jit-test/tests/debug/Environment-getVariable-09.js
new file mode 100644
index 0000000000..5e2136b9d7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-getVariable-09.js
@@ -0,0 +1,13 @@
+// getVariable works on ancestors of frame.environment.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var log = '';
+dbg.onDebuggerStatement = function (frame) {
+ for (var env = frame.environment; env; env = env.parent) {
+ if (env.find("x") === env)
+ log += env.getVariable("x");
+ }
+};
+g.eval("var x = 1; { let x = 2; with (Object.create({x: 3})) { debugger; } }");
+assertEq(log, "321");
diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-10.js b/js/src/jit-test/tests/debug/Environment-getVariable-10.js
new file mode 100644
index 0000000000..57273cc628
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-getVariable-10.js
@@ -0,0 +1,27 @@
+// getVariable works on a heavyweight environment after control leaves its scope.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var envs = [];
+dbg.onDebuggerStatement = function (frame) {
+ envs.push(frame.environment);
+};
+g.eval("var f;\n" +
+ "for (var x = 0; x < 3; x++) {\n" +
+ " (function (x) {\n" +
+ " for (var y = 0; y < 3; y++) {\n" +
+ " (function (z) {\n" +
+ " eval(z); // force heavyweight\n" +
+ " debugger;\n" +
+ " })(x + y);\n" +
+ " }\n" +
+ " })(x);\n" +
+ "}");
+
+var i = 0;
+for (var x = 0; x < 3; x++) {
+ for (var y = 0; y < 3; y++) {
+ var e = envs[i++];
+ assertEq(e.getVariable("z"), x + y);
+ }
+}
diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-11.js b/js/src/jit-test/tests/debug/Environment-getVariable-11.js
new file mode 100644
index 0000000000..23dcdc5fe8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-getVariable-11.js
@@ -0,0 +1,15 @@
+// The value returned by getVariable can be a Debugger.Object.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ var a = frame.environment.parent.getVariable('Math');
+ assertEq(a instanceof Debugger.Object, true);
+ var b = gw.getOwnPropertyDescriptor('Math').value;
+ assertEq(a, b);
+ hits++;
+};
+g.eval("debugger;");
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-12.js b/js/src/jit-test/tests/debug/Environment-getVariable-12.js
new file mode 100644
index 0000000000..d31bd3b1b4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-getVariable-12.js
@@ -0,0 +1,61 @@
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ hits++;
+ assertEq(frame.environment.parent.getVariable('y'), true);
+};
+
+g.eval("var g;" +
+ "function f(x) {" +
+ " { let y = x; " +
+ " if (x)" +
+ " g = function() { eval('debugger') };" +
+ " else" +
+ " g();" +
+ " }" +
+ "}" +
+ "f(true);" +
+ "f(false);");
+assertEq(hits, 1);
+
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ hits++;
+ assertEq(frame.environment.parent.getVariable('y'), 1);
+ assertEq(frame.environment.parent.names().indexOf('z'), -1);
+};
+
+g.eval("var g;" +
+ "{ let y = 1; " +
+ " g = function () { eval(''); debugger; };" +
+ " { let z = 2; " +
+ " g();" +
+ " }"+
+ "}");
+assertEq(hits, 1);
+
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ hits++;
+ var e = frame.older.environment.parent;
+ assertEq(e.getVariable('z'), true);
+ e = e.parent;
+ assertEq(e.getVariable('y'), true);
+};
+
+g.eval("var g;" +
+ "function h() { eval(''); debugger; };" +
+ "for (var x of [true, false]) {" +
+ " { let y = x; " +
+ " { let z = x; " +
+ " if (x)" +
+ " g = function () { print(z); h() };" +
+ " else" +
+ " g();" +
+ " }" +
+ " }" +
+ "}");
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-13.js b/js/src/jit-test/tests/debug/Environment-getVariable-13.js
new file mode 100644
index 0000000000..37d5e90afc
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-getVariable-13.js
@@ -0,0 +1,50 @@
+// Tests that we can use debug scopes with Ion frames.
+//
+// Unfortunately these tests are brittle. They depend on opaque JIT heuristics
+// kicking in.
+
+load(libdir + "jitopts.js");
+
+// GCs can invalidate JIT code and cause this test to fail.
+gczeal(0);
+
+if (!jitTogglesMatch(Opts_Ion2NoOffthreadCompilation))
+ quit(0);
+
+withJitOptions(Opts_Ion2NoOffthreadCompilation, function () {
+ var g = newGlobal({newCompartment: true});
+ var dbg = new Debugger;
+
+ // Note that this *depends* on CCW scripted functions being opaque to Ion
+ // optimization and not deoptimizing the frames below the call to toggle.
+ g.toggle = function toggle(d) {
+ if (d) {
+ dbg.addDebuggee(g);
+ var frame = dbg.getNewestFrame();
+ assertEq(frame.implementation, "ion");
+ // g is heavyweight but its call object is optimized out, because its
+ // arguments and locals are unaliased.
+ //
+ // Calling frame.environment here should make a fake debug scope that
+ // gets things directly from the frame. Calling frame.arguments doesn't
+ // go through the scope object and reads directly off the frame. Assert
+ // that the two are equal.
+ assertEq(frame.environment.getVariable("x"), frame.arguments[1]);
+ }
+ };
+
+ g.eval("" + function f(d, x) { g(d, x); });
+ g.eval("" + function g(d, x) {
+ for (var i = 0; i < 100; i++);
+ function inner() { i = 42; };
+ toggle(d);
+ // Use x so it doesn't get optimized out.
+ x++;
+ });
+
+ g.eval("(" + function test() {
+ for (i = 0; i < 15; i++)
+ f(false, 42);
+ f(true, 42);
+ } + ")();");
+});
diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-14.js b/js/src/jit-test/tests/debug/Environment-getVariable-14.js
new file mode 100644
index 0000000000..2eff1690ff
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-getVariable-14.js
@@ -0,0 +1,18 @@
+// Debugger.Environment can reflect optimized out function scopes
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+dbg.addDebuggee(g);
+
+g.eval("" + function f() {
+ var x = 42;
+ function g() { }
+ g();
+});
+
+dbg.onEnterFrame = function (f) {
+ if (f.callee && (f.callee.name === "g"))
+ assertEq(f.environment.parent.getVariable("x").optimizedOut, true);
+}
+
+g.f();
diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-15.js b/js/src/jit-test/tests/debug/Environment-getVariable-15.js
new file mode 100644
index 0000000000..f0495cda17
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-getVariable-15.js
@@ -0,0 +1,31 @@
+// Don't hand out internal function objects via Debugger.Environment.prototype.getVariable.
+
+// When the real scope chain object holding the binding for 'f' in 'function f()
+// { ... }' is optimized out because it's never used, we whip up fake scope
+// chain objects for Debugger to use, if it looks. However, the value of the
+// variable f will be an internal function object, not a live function object,
+// since the latter was not recorded. Internal function objects should not be
+// exposed via Debugger.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+dbg.onDebuggerStatement = function (frame) {
+ var g_call_env = frame.older.environment; // g's locals
+ var g_decl_env = g_call_env.parent; // 'function g' binding
+ var f_call_env = g_decl_env.parent; // f's locals
+ var f_decl_env = f_call_env.parent; // 'function f' binding
+ assertEq(f_decl_env.getVariable('f').optimizedOut, true);
+}
+
+g.evaluate(`
+
+ function h() { debugger; }
+ (function f() {
+ return function g() {
+ h();
+ return 1;
+ }
+ })()();
+
+ `);
diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-WouldRun.js b/js/src/jit-test/tests/debug/Environment-getVariable-WouldRun.js
new file mode 100644
index 0000000000..c6bf3a8cb7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-getVariable-WouldRun.js
@@ -0,0 +1,17 @@
+// getVariable that would trigger a getter does not crash or explode.
+// It should throw WouldRunDebuggee, but that isn't implemented yet.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ assertThrowsInstanceOf(function () {
+ frame.environment.parent.parent.getVariable("x");
+ }, Error);
+ hits++;
+};
+g.eval("Object.defineProperty(this, 'x', {get: function () { throw new Error('fail'); }});\n" +
+ "debugger;");
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Environment-identity-01.js b/js/src/jit-test/tests/debug/Environment-identity-01.js
new file mode 100644
index 0000000000..7d0c5b134d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-identity-01.js
@@ -0,0 +1,40 @@
+// The value of frame.environment is the same Environment object at different
+// times within a single visit to a scope.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+g.eval("function h() { debugger; }");
+var hits, env;
+dbg.onDebuggerStatement = function (hframe) {
+ var frame = hframe.older;
+ var e = frame.environment;
+
+ // frame.environment is at least cached from one moment to the next.
+ assertEq(e, frame.environment);
+
+ // frame.environment is cached from statement to statement within a call frame.
+ if (env === undefined)
+ env = e;
+ else
+ assertEq(e, env);
+
+ hits++;
+};
+
+hits = 0;
+env = undefined;
+g.eval("function f() { (function () { var i = 0; h(); var j = 2; h(); })(); }");
+g.f();
+assertEq(hits, 2);
+
+hits = 0;
+env = undefined;
+g.eval("function f2() { { let i = 0; h(); let j = 2; h(); } }");
+g.f2();
+assertEq(hits, 2);
+
+hits = 0;
+env = undefined;
+g.eval("function f3() { { let i; for (i = 0; i < 2; i++) h(); } }");
+g.f3();
+assertEq(hits, 2);
diff --git a/js/src/jit-test/tests/debug/Environment-identity-02.js b/js/src/jit-test/tests/debug/Environment-identity-02.js
new file mode 100644
index 0000000000..a48f8b93bf
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-identity-02.js
@@ -0,0 +1,29 @@
+// frame.environment is different for different activations of a scope.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+g.eval("function h() { debugger; }");
+var arr;
+dbg.onDebuggerStatement = function (hframe) {
+ var e = hframe.older.environment;
+ assertEq(arr.indexOf(e), -1);
+ arr.push(e);
+};
+
+function test(code, expectedHits) {
+ arr = [];
+ g.eval(code);
+ assertEq(arr.length, expectedHits);
+}
+
+// two separate calls to a function
+test("(function () { var f = function (a) { h(); return a; }; f(1); f(2); })();", 2);
+
+// recursive calls to a function
+test("(function f(n) { h(); return n < 2 ? 1 : n * f(n - 1); })(3);", 3);
+
+// separate visits to a block in the same call frame
+test("(function () { for (var i = 0; i < 3; i++) { let j = i * 4; h(); }})();", 3);
+
+// two strict direct eval calls in the same function scope
+test("(function () { 'use strict'; for (var i = 0; i < 3; i++) eval('h();'); })();", 3);
diff --git a/js/src/jit-test/tests/debug/Environment-identity-03.js b/js/src/jit-test/tests/debug/Environment-identity-03.js
new file mode 100644
index 0000000000..a088f1cc53
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-identity-03.js
@@ -0,0 +1,106 @@
+// Two Environments nested in the same runtime scope share the correct tail of their parent chains.
+
+// The compiler must be allowed to elide empty scopes and so forth, so this
+// test does not check the number of unshared Environments. Instead, each test
+// case identifies the expected innermost shared scope by the name of a
+// variable in it.
+
+var g = newGlobal({newCompartment: true});
+g.eval("function h() { debugger; }");
+var dbg = Debugger(g);
+var hits, name, shared, unshared;
+dbg.onDebuggerStatement = function (hframe) {
+ var frame = hframe.older;
+
+ // Find name in frame.environment.
+ var env, child = null;
+ for (env = frame.environment; env !== null; env = env.parent) {
+ if (env.names().indexOf(name) != -1)
+ break;
+ child = env;
+ }
+ assertEq(env !== null, true, "expected '" + name + "' to be in scope");
+ assertEq(env, frame.environment.find(name),
+ "env.find should find the same frame as the written out search");
+
+ if (hits === 0) {
+ // First hit.
+ shared = env;
+ unshared = child;
+ } else {
+ // Subsequent hit.
+ assertEq(env, shared, "the environment containing '" + name + "' should be shared");
+ assertEq(child === null || unshared === null || unshared !== child, true,
+ "environments nested within the one containing '" + name + "' should not be shared");
+ }
+ hits++;
+};
+
+function test(sharedName, expectedHits, code) {
+ hits = 0;
+ name = sharedName;
+ shared = unshared = undefined;
+ g.eval(code);
+ assertEq(hits, expectedHits);
+}
+
+// Basic test cases.
+//
+// (The stray "a = b" assignments in these tests are to inhibit the flat closure
+// optimization, which Environments expose. There's nothing really wrong with
+// the optimization or with the debugger exposing it, but that's not what we
+// want to test here.)
+
+test("q", 2, "let q = function (a) { h(); }; q(1); q(2);");
+test("a", 2, "q = function (a) { (function (b) { h(); a = b; })(2); h(); }; q(1);");
+test("a", 2, "q = function (a) { h(); return function (b) { h(); a = b; }; }; q(1)(2);");
+test("n", 3, "q = function (n) { for (var i = 0; i < n; i++) { { let j = i; h(); } } }; q(3);");
+
+// A function with long dynamic and static chains.
+var N = 80;
+
+var code = "function f" + N + "(a" + N + ") {\neval('a0 + a1'); h();\n}\n";
+for (var i = N; --i >= 0;) {
+ var call = "f" + (i + 1) + "(a" + i + " - 1);\n";
+ code = ("function f" + i + "(a" + i + ") {\n" +
+ code +
+ call +
+ "if (a" + i + " === 0) " + call +
+ "}\n");
+}
+
+g.eval(code);
+test("a0", 2, "f0(0);");
+test("a17", 2, "f0(17);");
+test("a" + (N-2), 2, "f0(" + (N-2) + ");");
+test("a" + (N-1), 2, "f0(" + (N-1) + ");");
+
+// A function with a short dynamic chain and a long static chain.
+N = 60;
+
+function DeepStaticShallowDynamic(i, n) {
+ var code = "function f" + i + "(a" + i + ") {\n";
+ if (i >= n)
+ code += "eval('a1 + a2'); h();\n";
+ else
+ code += "return " + DeepStaticShallowDynamic(i+1, n) + ";\n";
+ code += "}";
+ return code;
+}
+g.eval(DeepStaticShallowDynamic(1, N));
+
+function* range(start, stop) {
+ for (var i = start; i < stop; i++)
+ yield i;
+}
+
+function DSSDsplit(s) {
+ return ("var mid = f1" + [...range(0, s)].map((i) => "(" + i + ")").join("") + ";\n" +
+ "mid" + [...range(s, N)].map((i) => "(" + i + ")").join("") + ";\n" +
+ "mid" + [...range(s, N)].map((i) => "(" + i + ")").join("") + ";\n");
+}
+
+test("a1", 2, DSSDsplit(1));
+test("a17", 2, DSSDsplit(17));
+test("a" + (N-2), 2, DSSDsplit(N-2));
+test("a" + (N-1), 2, DSSDsplit(N-1));
diff --git a/js/src/jit-test/tests/debug/Environment-identity-04.js b/js/src/jit-test/tests/debug/Environment-identity-04.js
new file mode 100644
index 0000000000..607b871817
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-identity-04.js
@@ -0,0 +1,19 @@
+// Observably different visits to the same with-statement produce distinct Environments.
+
+var g = newGlobal({newCompartment: true});
+g.eval("function f(a, obj) { with (obj) return function () { return a; }; }");
+var dbg = Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ // Even though the two visits to the with-statement have the same target
+ // object, Math, the environments are observably different.
+ var f1 = frame.eval("f(1, Math);").return;
+ var f2 = frame.eval("f(2, Math);").return;
+ assertEq(f1.environment !== f2.environment, true);
+ assertEq(f1.object, f2.object);
+ assertEq(f1.call().return, 1);
+ assertEq(f2.call().return, 2);
+ hits++;
+};
+g.eval("debugger;");
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Environment-identity-05.js b/js/src/jit-test/tests/debug/Environment-identity-05.js
new file mode 100644
index 0000000000..d72b5e76f3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-identity-05.js
@@ -0,0 +1,19 @@
+// Tests that freshened blocks behave correctly in Debugger.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var log = '';
+var oldEnv = null;
+dbg.onDebuggerStatement = function (frame) {
+ if (!oldEnv) {
+ oldEnv = frame.environment;
+ } else {
+ // Block has been freshened by |for (let ...)|, should be different
+ // identity.
+ log += (oldEnv === frame.environment);
+ }
+ log += frame.environment.getVariable("x");
+};
+g.eval("for (let x = 0; x < 2; x++) { eval(\"\"); debugger; }");
+gc();
+assertEq(log, "0false1");
diff --git a/js/src/jit-test/tests/debug/Environment-inspectable-01.js b/js/src/jit-test/tests/debug/Environment-inspectable-01.js
new file mode 100644
index 0000000000..7ced83aa41
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-inspectable-01.js
@@ -0,0 +1,80 @@
+// Environments are only inspectable while their globals are debuggees.
+
+load(libdir + 'asserts.js');
+
+var g1 = newGlobal({newCompartment: true});
+var g2 = newGlobal({newCompartment: true});
+g2.g1 = g1;
+g1.g2 = g2;
+
+g1.eval('function f(xf) { return function h(xh) { eval(""); debugger; } }');
+g1.eval('var h = f("value of xf");');
+
+// To ensure that xk gets located on the heap, and thus outlives its stack frame, we
+// store a function that captures it here. Kind of a kludge.
+g2.eval('var capture;');
+g2.eval('function k(xk) { capture = function () { return xk; }; g1.h("value of xh"); }');
+
+var dbg = new Debugger;
+dbg.addDebuggee(g1);
+dbg.addDebuggee(g2);
+
+dbg.onDebuggerStatement = debuggerHandler;
+
+var log = '';
+
+g1.eval('g2.k("value of xk");');
+
+var he, ke, ee;
+
+function debuggerHandler(frame) {
+ log += 'd';
+
+ assertEq(frame.type, 'call');
+ he = frame.environment;
+
+ assertEq(frame.older.type, 'call');
+ ke = frame.older.environment;
+
+ assertEq(frame.older.older.type, 'eval');
+ ee = frame.older.older.environment;
+
+ assertEq(he.inspectable, true);
+ assertEq(he.getVariable('xh'), 'value of xh');
+ assertEq(he.parent.parent.getVariable('xf'), 'value of xf');
+ assertEq(ke.inspectable, true);
+ assertEq(ke.getVariable('xk'), 'value of xk');
+ assertEq(ee.inspectable, true);
+ assertEq(ee.type, 'declarative');
+ assertEq(ee.parent.type, 'object');
+
+ dbg.removeDebuggee(g2);
+
+ assertEq(he.inspectable, true);
+ assertEq(he.type, 'declarative');
+ assertEq(ke.inspectable, false);
+ assertThrowsInstanceOf(() => ke.getVariable('xk'), Error);
+ assertEq(ee.inspectable, true);
+ assertEq(ee.type, 'declarative');
+ assertEq(ee.parent.type, 'object');
+
+ dbg.removeDebuggee(g1);
+
+ assertEq(he.inspectable, false);
+ assertThrowsInstanceOf(() => he.getVariable('xh'), Error);
+ assertEq(ke.inspectable, false);
+ assertThrowsInstanceOf(() => ke.getVariable('xk'), Error);
+ assertEq(ee.inspectable, false);
+ assertThrowsInstanceOf(() => ee.type, Error);
+}
+
+assertEq(log, 'd');
+
+dbg.addDebuggee(g2);
+
+assertEq(he.inspectable, false);
+assertThrowsInstanceOf(() => he.getVariable('xh'), Error);
+assertEq(ke.inspectable, true);
+assertEq(ke.getVariable('xk'), 'value of xk');
+assertEq(ee.inspectable, false);
+assertThrowsInstanceOf(() => ee.type, Error);
diff --git a/js/src/jit-test/tests/debug/Environment-module-01.js b/js/src/jit-test/tests/debug/Environment-module-01.js
new file mode 100644
index 0000000000..728c5f64bd
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-module-01.js
@@ -0,0 +1,26 @@
+// Debug environments for module environments should include variables that are
+// are not closed over or exported.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+dbg.onEnterFrame = function (frame) {
+ if (!frame.older) {
+ return;
+ }
+ const env = frame.older.environment;
+ assertEq(env.names().join(','), "foo,y,x,z");
+ assertEq(env.getVariable('x'), 0);
+ assertEq(env.getVariable('y'), 1);
+ assertEq(env.getVariable('z'), 2);
+ env.setVariable('x', 3);
+ assertEq(env.getVariable('x'), 3);
+};
+const m = g.parseModule(`
+ var x = 0;
+ export var y = 1;
+ const z = 2;
+ foo();
+ function foo() {}
+`);
+m.declarationInstantiation();
+m.evaluation();
diff --git a/js/src/jit-test/tests/debug/Environment-module-02.js b/js/src/jit-test/tests/debug/Environment-module-02.js
new file mode 100644
index 0000000000..442116c0a9
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-module-02.js
@@ -0,0 +1,30 @@
+// Debug environments for module environments should be able to access closed
+// over variables after the module script has executed.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+
+const m = g.parseModule(`
+ var x = 42;
+ export function foo() { return x; }
+ foo();
+`);
+m.declarationInstantiation();
+
+let fooFunction;
+dbg.onEnterFrame = function (frame) {
+ fooFunction = frame.callee;
+};
+
+m.evaluation();
+assertEq(fooFunction instanceof Debugger.Object, true);
+
+dbg.onEnterFrame = function (frame) {
+ const env = frame.environment.parent;
+ assertEq(env.names().join(','), "foo,x");
+ assertEq(env.getVariable('x'), 42);
+ env.setVariable('x', 3);
+ assertEq(env.getVariable('x'), 3);
+};
+
+fooFunction.call();
diff --git a/js/src/jit-test/tests/debug/Environment-names-01.js b/js/src/jit-test/tests/debug/Environment-names-01.js
new file mode 100644
index 0000000000..be053fe6e0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-names-01.js
@@ -0,0 +1,19 @@
+// env.names() lists nonenumerable names in with-statement environments.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var hits = 0;
+g.h = function () {
+ var env = dbg.getNewestFrame().environment;
+ var names = env.names();
+ assertEq(names.indexOf("a") !== -1, true);
+
+ // FIXME: Bug 748592 - proxies don't correctly propagate JSITER_HIDDEN
+ //assertEq(names.indexOf("b") !== -1, true);
+ //assertEq(names.indexOf("isPrototypeOf") !== -1, true);
+ hits++;
+};
+g.eval("var obj = {a: 1};\n" +
+ "Object.defineProperty(obj, 'b', {value: 2});\n" +
+ "with (obj) h();");
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Environment-names-02.js b/js/src/jit-test/tests/debug/Environment-names-02.js
new file mode 100644
index 0000000000..9dd87f49a3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-names-02.js
@@ -0,0 +1,34 @@
+// env.names() on object environments ignores property names that are not identifiers.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var withNames, globalNames;
+g.h = function () {
+ var env = dbg.getNewestFrame().environment;
+ withNames = env.names();
+ while (env.parent !== null)
+ env = env.parent;
+ globalNames = env.names();
+};
+
+g.eval("" +
+ function fill(obj) {
+ obj.sanityCheck = 1;
+ obj["0xcafe"] = 2;
+ obj[" "] = 3;
+ obj[""] = 4;
+ obj[0] = 5;
+ obj[Symbol.for("moon")] = 6;
+ return obj;
+ })
+g.eval("fill(this);\n" +
+ "with (fill({})) h();");
+
+for (var names of [withNames, globalNames]) {
+ assertEq(names.indexOf("sanityCheck") !== -1, true);
+ assertEq(names.indexOf("0xcafe"), -1);
+ assertEq(names.indexOf(" "), -1);
+ assertEq(names.indexOf(""), -1);
+ assertEq(names.indexOf("0"), -1);
+ assertEq(names.indexOf(Symbol.for("moon")), -1);
+}
diff --git a/js/src/jit-test/tests/debug/Environment-names-03.js b/js/src/jit-test/tests/debug/Environment-names-03.js
new file mode 100644
index 0000000000..cffc7cb461
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-names-03.js
@@ -0,0 +1,22 @@
+// Optimized out scopes should have working names().
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+dbg.addDebuggee(g);
+
+g.eval("" + function f() {
+ var x = 42;
+ function g() { }
+ g();
+});
+
+dbg.onEnterFrame = function (f) {
+ if (f.callee && (f.callee.name === "g")) {
+ var names = f.environment.parent.names();
+ assertEq(names.indexOf("x") !== -1, true);
+ assertEq(names.indexOf("g") !== -1, true);
+ assertEq(names.length, 3); // x,g,arguments
+ }
+}
+
+g.f();
diff --git a/js/src/jit-test/tests/debug/Environment-nondebuggee.js b/js/src/jit-test/tests/debug/Environment-nondebuggee.js
new file mode 100644
index 0000000000..32c36a8b84
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-nondebuggee.js
@@ -0,0 +1,40 @@
+// Using an environment that is not a debuggee should throw.
+
+load(libdir + 'asserts.js');
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+let gw = dbg.addDebuggee(g);
+var log;
+
+function check(env) {
+ assertEq(env.inspectable, false);
+ assertThrowsInstanceOf(() => env.type, Error);
+ assertThrowsInstanceOf(() => env.object, Error);
+ assertThrowsInstanceOf(() => env.parent, Error);
+ assertThrowsInstanceOf(() => env.calleeScript, Error);
+
+ assertThrowsInstanceOf(() => env.names(), Error);
+ assertThrowsInstanceOf(() => env.find('x'), Error);
+ assertThrowsInstanceOf(() => env.getVariable('x'), Error);
+ assertThrowsInstanceOf(() => env.setVariable('x'), Error);
+}
+
+dbg.onDebuggerStatement = function (frame) {
+ log += 'd';
+
+ let env = frame.environment;
+ dbg.removeDebuggee(g);
+ check(env);
+}
+
+log = '';
+g.eval('let x = 42; debugger;');
+assertEq(log, 'd');
+
+dbg.addDebuggee(g);
+g.eval('function f() { }');
+let env = gw.getOwnPropertyDescriptor('f').value.environment;
+assertEq(env.type, 'declarative');
+dbg.removeDebuggee(g);
+check(env);
diff --git a/js/src/jit-test/tests/debug/Environment-object-01.js b/js/src/jit-test/tests/debug/Environment-object-01.js
new file mode 100644
index 0000000000..c8f434bbb1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-object-01.js
@@ -0,0 +1,9 @@
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+dbg.onDebuggerStatement = (frame) => {
+ assertEq(frame.environment.parent.type, "with");
+ assertEq(frame.environment.parent.parent.type, "object");
+ assertEq(frame.environment.parent.parent.object.getOwnPropertyDescriptor("x").value, 42);
+}
+g.evalReturningScope("x = 42; debugger;");
diff --git a/js/src/jit-test/tests/debug/Environment-optimizedOut-01.js b/js/src/jit-test/tests/debug/Environment-optimizedOut-01.js
new file mode 100644
index 0000000000..964148af9a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-optimizedOut-01.js
@@ -0,0 +1,44 @@
+// Optimized out scopes should be considered optimizedOut.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+dbg.addDebuggee(g);
+
+g.eval("" + function f() {
+ var x = 42;
+ {
+ let y = 43;
+ (function () { })();
+ }
+});
+
+dbg.onEnterFrame = function (f) {
+ if (f.callee && (f.callee.name === undefined)) {
+ blockenv = f.environment.parent;
+ assertEq(blockenv.optimizedOut, true);
+ assertEq(blockenv.inspectable, true);
+ assertEq(blockenv.type, "declarative");
+ assertEq(blockenv.calleeScript, null);
+ assertEq(blockenv.names().indexOf("y") !== -1, true);
+
+ funenv = blockenv.parent;
+ assertEq(funenv.optimizedOut, true);
+ assertEq(funenv.inspectable, true);
+ assertEq(funenv.type, "declarative");
+ assertEq(funenv.calleeScript, f.older.script);
+ assertEq(funenv.names().indexOf("x") !== -1, true);
+
+ globalenv = funenv.parent.parent;
+ assertEq(globalenv.optimizedOut, false);
+ assertEq(globalenv.inspectable, true);
+ assertEq(globalenv.type, "object");
+ assertEq(globalenv.calleeScript, null);
+
+ dbg.removeDebuggee(g);
+
+ assertEq(blockenv.inspectable, false);
+ assertEq(funenv.inspectable, false);
+ }
+}
+
+g.f();
diff --git a/js/src/jit-test/tests/debug/Environment-parent-01.js b/js/src/jit-test/tests/debug/Environment-parent-01.js
new file mode 100644
index 0000000000..e50f0cc82d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-parent-01.js
@@ -0,0 +1,18 @@
+// The objects on the environment chain are all Debugger.Environment objects.
+// The environment chain ends in null.
+
+var g = newGlobal({newCompartment: true})
+g.eval("function f(a) { return function (b) { return function (c) { h(); return a + b + c; }; }; }");
+var dbg = Debugger(g);
+var hits = 0;
+g.h = function () {
+ var n = 0;
+ for (var env = dbg.getNewestFrame().environment; env !== null; env = env.parent) {
+ n++;
+ assertEq(env instanceof Debugger.Environment, true);
+ }
+ assertEq(n >= 4, true);
+ hits++;
+};
+assertEq(g.f(5)(7)(9), 21);
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Environment-scopeKind-01.js b/js/src/jit-test/tests/debug/Environment-scopeKind-01.js
new file mode 100644
index 0000000000..6f44cc51e2
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-scopeKind-01.js
@@ -0,0 +1,26 @@
+// Environment.prototype.scopeKind produces expected values.
+
+load(libdir + 'eqArrayHelper.js');
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+
+function getScopeKinds(text) {
+ const kinds = [];
+ dbg.onDebuggerStatement = frame => {
+ let env = frame.environment;
+ while (env) {
+ kinds.push(env.scopeKind);
+ env = env.parent;
+ }
+ };
+ g.eval(text);
+ return kinds;
+}
+
+assertEqArray(getScopeKinds("function f(x) { debugger; }; f()"),
+ ["function", null, null]);
+assertEqArray(getScopeKinds("function f(x) { let y = 0; debugger; }; f()"),
+ ["function lexical", "function", null, null]);
+assertEqArray(getScopeKinds("function f(x) { let y = 0; with(x) { debugger; } } f({})"),
+ [null, "function lexical", "function", null, null]);
diff --git a/js/src/jit-test/tests/debug/Environment-selfhosted-builtins.js b/js/src/jit-test/tests/debug/Environment-selfhosted-builtins.js
new file mode 100644
index 0000000000..1026a486f4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-selfhosted-builtins.js
@@ -0,0 +1,15 @@
+// The environment of self-hosted builtins is not exposed to the debugger and
+// instead is reported as |undefined| just like native builtins.
+
+let g = newGlobal({newCompartment: true});
+
+let dbg = new Debugger();
+let gw = dbg.addDebuggee(g);
+
+// Array is a known native builtin function.
+let nativeBuiltin = gw.makeDebuggeeValue(g.Array);
+assertEq(nativeBuiltin.environment, undefined);
+
+// Array.prototype[@@iterator] is a known self-hosted builtin function.
+let selfhostedBuiltin = gw.makeDebuggeeValue(g.Array.prototype[Symbol.iterator]);
+assertEq(selfhostedBuiltin.environment, undefined);
diff --git a/js/src/jit-test/tests/debug/Environment-setVariable-01.js b/js/src/jit-test/tests/debug/Environment-setVariable-01.js
new file mode 100644
index 0000000000..ed67774f79
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-setVariable-01.js
@@ -0,0 +1,9 @@
+// Environment.prototype.setVariable can set global variables.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+ frame.environment.parent.setVariable("x", 2);
+};
+g.eval("var x = 1; debugger;");
+assertEq(g.x, 2);
diff --git a/js/src/jit-test/tests/debug/Environment-setVariable-02.js b/js/src/jit-test/tests/debug/Environment-setVariable-02.js
new file mode 100644
index 0000000000..7fb7f5e02e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-setVariable-02.js
@@ -0,0 +1,10 @@
+// The argument to setVariable can be a Debugger.Object.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+dbg.onDebuggerStatement = function (frame) {
+ frame.environment.parent.setVariable("x", gw);
+};
+g.eval("var x = 1; debugger;");
+assertEq(g.x, g);
diff --git a/js/src/jit-test/tests/debug/Environment-setVariable-03.js b/js/src/jit-test/tests/debug/Environment-setVariable-03.js
new file mode 100644
index 0000000000..3fe0dee4d8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-setVariable-03.js
@@ -0,0 +1,16 @@
+// setVariable cannot create new global variables.
+// (Other kinds of environment are tested in Environment-variables.js.)
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ assertThrowsInstanceOf(function () { frame.environment.setVariable("x", 7); }, TypeError);
+ hits++;
+};
+g.eval("debugger");
+assertEq("x" in g, false);
+assertEq(hits, 1);
+
diff --git a/js/src/jit-test/tests/debug/Environment-setVariable-04.js b/js/src/jit-test/tests/debug/Environment-setVariable-04.js
new file mode 100644
index 0000000000..d4b2347c79
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-setVariable-04.js
@@ -0,0 +1,10 @@
+// setVariable can set variables and arguments in functions.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+ frame.environment.setVariable("a", 100);
+ frame.environment.setVariable("b", 200);
+};
+g.eval("function f(a) { var b = a + 1; debugger; return a + b; }");
+assertEq(g.f(1), 300);
diff --git a/js/src/jit-test/tests/debug/Environment-setVariable-05.js b/js/src/jit-test/tests/debug/Environment-setVariable-05.js
new file mode 100644
index 0000000000..d78edad3bf
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-setVariable-05.js
@@ -0,0 +1,14 @@
+// setVariable can change the types of variables and arguments in functions.
+
+var g = newGlobal({newCompartment: true});
+g.eval("function f(a) { var b = a + 1; debugger; return a + b; }");
+for (var i = 0; i < 20; i++)
+ assertEq(g.f(i), 2 * i + 1);
+
+var dbg = new Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+ frame.environment.setVariable("a", "xyz");
+ frame.environment.setVariable("b", "zy");
+};
+for (var i = 0; i < 10; i++)
+ assertEq(g.f(i), "xyzzy");
diff --git a/js/src/jit-test/tests/debug/Environment-setVariable-06.js b/js/src/jit-test/tests/debug/Environment-setVariable-06.js
new file mode 100644
index 0000000000..9b235dfe7a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-setVariable-06.js
@@ -0,0 +1,9 @@
+// setVariable on an argument works as expected with non-strict 'arguments'.
+
+var g = newGlobal({newCompartment: true});
+g.eval("function f(a) { debugger; return arguments[0]; }");
+var dbg = new Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+ frame.environment.setVariable("a", 2);
+};
+assertEq(g.f(1), 2);
diff --git a/js/src/jit-test/tests/debug/Environment-setVariable-07.js b/js/src/jit-test/tests/debug/Environment-setVariable-07.js
new file mode 100644
index 0000000000..2b58885335
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-setVariable-07.js
@@ -0,0 +1,14 @@
+// setVariable works on let-bindings.
+
+var g = newGlobal({newCompartment: true});
+function test(code, val) {
+ g.eval("function f() { " + code + " }");
+ var dbg = new Debugger(g);
+ dbg.onDebuggerStatement = function (frame) {
+ frame.environment.setVariable("a", val);
+ };
+ assertEq(g.f(), val);
+}
+
+test("let a = 1; debugger; return a;", "xyzzy");
+test("{ let a = 1; debugger; return a; }", "plugh");
diff --git a/js/src/jit-test/tests/debug/Environment-setVariable-08.js b/js/src/jit-test/tests/debug/Environment-setVariable-08.js
new file mode 100644
index 0000000000..35de70975b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-setVariable-08.js
@@ -0,0 +1,29 @@
+// setVariable throws if no binding exists.
+
+load(libdir + "asserts.js");
+
+function test(code) {
+ var g = newGlobal({newCompartment: true});
+ var dbg = new Debugger(g);
+ var hits = 0;
+ dbg.onDebuggerStatement = function (frame) {
+ var env = frame.older.environment;
+ assertThrowsInstanceOf(function () { env.setVariable("y", 2); }, Error);
+ hits++;
+ };
+ g.eval("var y = 0; function d() { debugger; }");
+
+ assertEq(g.eval(code), 0);
+
+ assertEq(g.y, 0);
+ assertEq(hits, 1);
+}
+
+// local scope of non-heavyweight function
+test("function f() { var x = 1; d(); return y; } f();");
+
+// block scope
+test("function h(x) { if (x) { let x = 1; d(); return y; } } h(3);");
+
+// strict eval scope
+test("'use strict'; eval('d(); y;');");
diff --git a/js/src/jit-test/tests/debug/Environment-setVariable-10.js b/js/src/jit-test/tests/debug/Environment-setVariable-10.js
new file mode 100644
index 0000000000..2ba46b06b3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-setVariable-10.js
@@ -0,0 +1,32 @@
+// setVariable works on non-innermost environments.
+
+// (The debuggee code here is a bit convoluted to defeat optimizations that
+// could make obj.b a null closure or obj.i a flat closure--that is, a function
+// that gets a frozen copy of i instead of a reference to the runtime
+// environment that contains it. setVariable does not currently detect this
+// flat closure case.)
+
+var g = newGlobal({newCompartment: true});
+g.eval("function d() { debugger; }\n" +
+ "var i = 'FAIL';\n" +
+ "function a() {\n" +
+ " var obj = {b: function (i) { d(obj); return i; },\n" +
+ " i: function () { return i; }};\n" +
+ " var i = 'FAIL2';\n" +
+ " return obj;\n" +
+ "}\n");
+
+var dbg = Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+ var x = 0;
+ for (var env = frame.older.environment; env; env = env.parent) {
+ if (env.getVariable("i") !== undefined)
+ env.setVariable("i", x++);
+ }
+};
+
+var obj = g.a();
+var r = obj.b('FAIL3');
+assertEq(r, 0);
+assertEq(obj.i(), 1);
+assertEq(g.i, 2);
diff --git a/js/src/jit-test/tests/debug/Environment-setVariable-11.js b/js/src/jit-test/tests/debug/Environment-setVariable-11.js
new file mode 100644
index 0000000000..d2030159d1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-setVariable-11.js
@@ -0,0 +1,16 @@
+// setVariable cannot modify the binding for a FunctionExpression's name.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ var env = frame.environment.find("f");
+ assertEq(env.getVariable("f"), frame.callee);
+ assertThrowsInstanceOf(function () { env.setVariable("f", 0) }, TypeError);
+ assertThrowsInstanceOf(function () { env.setVariable("f", frame.callee) }, TypeError);
+ hits++;
+};
+g.eval("(function f() { eval(\"\"); debugger; })();");
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Environment-setVariable-12.js b/js/src/jit-test/tests/debug/Environment-setVariable-12.js
new file mode 100644
index 0000000000..6806ad9196
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-setVariable-12.js
@@ -0,0 +1,21 @@
+// setVariable can create a new property on a with block's bindings object, if
+// it is shadowing an existing property on the prototype chain.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+ var env = frame.environment.find("x");
+ env.setVariable("x", 2);
+};
+g.eval("var obj1 = {x: 1}, obj2 = Object.create(obj1), z; with (obj2) { debugger; z = x; }");
+assertEq(g.obj1.x, 1);
+assertEq(g.obj2.x, 2);
+assertEq(g.z, 2);
+
+// The property created by setVariable is like the one created by ordinary
+// assignment in a with-block.
+var desc = Object.getOwnPropertyDescriptor(g.obj2, "x");
+assertEq(desc.configurable, true);
+assertEq(desc.enumerable, true);
+assertEq(desc.writable, true);
+assertEq(desc.value, 2);
diff --git a/js/src/jit-test/tests/debug/Environment-setVariable-13.js b/js/src/jit-test/tests/debug/Environment-setVariable-13.js
new file mode 100644
index 0000000000..9e9f1653b6
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-setVariable-13.js
@@ -0,0 +1,20 @@
+// Debugger.Environment should throw trying to setVariable on optimized out scope.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+dbg.addDebuggee(g);
+
+g.eval("" + function f() {
+ var x = 42;
+ function g() { }
+ g();
+});
+
+dbg.onEnterFrame = function (f) {
+ if (f.callee && (f.callee.name === "g"))
+ assertThrowsInstanceOf(function () { f.environment.parent.setVariable("x", 43) }, ReferenceError);
+}
+
+g.f();
diff --git a/js/src/jit-test/tests/debug/Environment-setVariable-WouldRun.js b/js/src/jit-test/tests/debug/Environment-setVariable-WouldRun.js
new file mode 100644
index 0000000000..a9d97dfacc
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-setVariable-WouldRun.js
@@ -0,0 +1,23 @@
+// setVariable triggering a setter throws WouldRunDebuggee.
+
+load(libdir + "asserts.js");
+
+function test(code) {
+ var g = newGlobal({newCompartment: true});
+ g.eval("function d() { debugger; }");
+ var dbg = Debugger(g);
+ var hits = 0;
+ dbg.onDebuggerStatement = function (frame) {
+ var env = frame.older.environment.find("x");
+ assertThrowsInstanceOf(
+ () => env.setVariable("x", 0),
+ Debugger.DebuggeeWouldRun);
+ hits++;
+ };
+ g.eval(code);
+ assertEq(hits, 1);
+}
+
+test("Object.defineProperty(this, 'x', {set: function (v) {}}); d();");
+test("Object.defineProperty(Object.prototype, 'x', {set: function (v) {}}); d();");
+test("with ({set x(v) {}}) eval(d());");
diff --git a/js/src/jit-test/tests/debug/Environment-type-01.js b/js/src/jit-test/tests/debug/Environment-type-01.js
new file mode 100644
index 0000000000..39b339cb39
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-type-01.js
@@ -0,0 +1,29 @@
+// env.type is 'object' in global environments and with-blocks, and 'declarative' otherwise.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+function test(code, expected) {
+ var actual = '';
+ g.h = function () { actual += dbg.getNewestFrame().environment.type; }
+ g.eval(code);
+ assertEq(actual, expected);
+}
+
+test("h();", 'declarative');
+test("(function (s) { eval(s); })('var v = h();')", 'declarative');
+test("(function (s) { h(); })();", 'declarative');
+test("{let x = 1, y = 2; h();}", 'declarative');
+test("with({x: 1, y: 2}) h();", 'with');
+test("(function (s) { with ({x: 1, y: 2}) h(); })();", 'with');
+test("{ let x = 1; h(); }", 'declarative');
+test("for (let x = 0; x < 1; x++) h();", 'declarative');
+test("for (let x in h()) ;", 'declarative');
+test("for (let x in {a:1}) h();", 'declarative');
+test("try { throw new Error; } catch (x) { h(x) }", 'declarative');
+test("'use strict'; eval('var z = 1; h();');", 'declarative');
+
+dbg.onDebuggerStatement = function (frame) {
+ assertEq(frame.eval("h(), 2 + 2;").return, 4);
+}
+test("debugger;", 'declarative');
+test("(function f() { debugger; })();", 'declarative');
diff --git a/js/src/jit-test/tests/debug/Environment-unscopables.js b/js/src/jit-test/tests/debug/Environment-unscopables.js
new file mode 100644
index 0000000000..d738fc11b3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-unscopables.js
@@ -0,0 +1,37 @@
+// An Environment for a `with` statement does not observe bindings ruled out by @@unscopables.
+
+load(libdir + "asserts.js");
+
+let g = newGlobal({newCompartment: true});
+g.eval(`
+ let x = 'global';
+ function f() {
+ let obj = {
+ x: 'obj',
+ y: 'obj',
+ [Symbol.unscopables]: {x: 1},
+ };
+ with (obj)
+ debugger;
+ }
+`);
+let dbg = Debugger(g);
+let hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ let env = frame.environment;
+
+ assertEq(env.find("x") !== env, true);
+ assertEq(env.names().indexOf("x"), -1);
+ assertEq(env.getVariable("x"), undefined);
+ assertThrowsInstanceOf(() => env.setVariable("x", 7), TypeError);
+
+ assertEq(env.find("y") === env, true);
+ assertEq(env.getVariable("y"), "obj");
+ env.setVariable("y", 8);
+
+ assertEq(frame.eval("x").return, "global");
+ assertEq(frame.eval("y").return, 8);
+ hits++;
+};
+g.f();
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Environment-variables.js b/js/src/jit-test/tests/debug/Environment-variables.js
new file mode 100644
index 0000000000..7a9b7072c4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-variables.js
@@ -0,0 +1,85 @@
+// Comprehensive test of get/setVariable on many kinds of environments and
+// bindings.
+
+load(libdir + "asserts.js");
+
+var cases = [
+ // global bindings and bindings on the global prototype chain
+ "x = VAL; @@",
+ "var x = VAL; @@",
+ "Object.prototype.x = VAL; @@",
+
+ // let and catch bindings
+ "let x = VAL; @@",
+ "{ let x = VAL; @@ }",
+ "try { throw VAL; } catch (x) { @@ }",
+ "try { throw VAL; } catch (x) { @@ }",
+ "for (let x of [VAL]) { @@ }",
+ "switch (0) { default: let x = VAL; @@ }",
+
+ // arguments
+ "function f(x) { @@ } f(VAL);",
+ "function f([w, x]) { @@ } f([0, VAL]);",
+ "function f({v: x}) { @@ } f({v: VAL});",
+ "function f([w, {v: x}]) { @@ } f([0, {v: VAL}]);",
+
+ // bindings in functions
+ "function f() { var x = VAL; @@ } f();",
+ "function f() { let x = VAL; @@ } f();",
+ "function f() { function x() {} x = VAL; @@ } f();",
+
+ // dynamic bindings
+ "function f(s) { eval(s); @@ } f('var x = VAL');",
+ "var x = VAL; function f(s) { eval('var x = 0;'); eval(s); @@ } f('delete x;');",
+ "function f(obj) { with (obj) { @@ } } f({x: VAL});",
+ "function f(obj) { with (obj) { @@ } } f(Object.create({x: VAL}));",
+ "function f(b) { if (b) { function x(){} } x = VAL; @@ } f(1);",
+];
+
+var nextval = 1000;
+
+function test(code, debugStmts, followupStmts) {
+ var val = nextval++;
+ var hits = 0;
+
+ var g = newGlobal({newCompartment: true});
+ g.eval("function debugMe() { var x = 'wrong-x'; eval(\"\"); debugger; }");
+ g.capture = null;
+
+ var dbg = Debugger(g);
+ dbg.onDebuggerStatement = function (frame) {
+ if (frame.callee !== null && frame.callee.name == 'debugMe')
+ frame = frame.older;
+ var env = frame.environment.find("x");
+ assertEq(env.getVariable("x"), val)
+ assertEq(env.setVariable("x", 'ok'), undefined);
+ assertEq(env.getVariable("x"), 'ok');
+
+ // setVariable cannot create new variables.
+ assertThrowsInstanceOf(function () { env.setVariable("newVar", 0); }, TypeError);
+ hits++;
+ };
+
+ code = code.replace("@@", debugStmts);
+ if (followupStmts !== undefined)
+ code += " " + followupStmts;
+ code = code.replace(/VAL/g, String(val));
+ g.eval(code);
+ assertEq(hits, 1);
+}
+
+for (var s of cases) {
+ // Test triggering the debugger right in the scope in which x is bound.
+ test(s, "eval(\"\"); debugger; assertEq(x, 'ok');");
+
+ // Test calling a function that triggers the debugger.
+ test(s, "debugMe(); assertEq(x, 'ok');");
+
+ // Test triggering the debugger from a scope nested in x's scope.
+ test(s, "{ let y = 'irrelevant'; (function (z) { { let zz = y; eval(\"\"); debugger; } })(); } assertEq(x, 'ok');"),
+
+ // Test closing over the variable and triggering the debugger later, after
+ // leaving the variable's scope.
+ test(s, "capture = {dbg: function () { eval(\"\"); debugger; }, get x() { return x; }};",
+ "assertEq(capture.x, VAL); capture.dbg(); assertEq(capture.x, 'ok');");
+}
diff --git a/js/src/jit-test/tests/debug/Frame-01.js b/js/src/jit-test/tests/debug/Frame-01.js
new file mode 100644
index 0000000000..9619fab8f0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-01.js
@@ -0,0 +1,34 @@
+// Test .type fields of topmost stack frame passed to onDebuggerStatement.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var expected, hits;
+dbg.onDebuggerStatement = function (f) {
+ assertEq(Object.getPrototypeOf(f), Debugger.Frame.prototype);
+ assertEq(f.type, expected.type);
+ assertEq(f.script.isGeneratorFunction, expected.generator);
+ assertEq(f.constructing, expected.constructing);
+ hits++;
+};
+
+function test(code, expectobj, expectedHits) {
+ expected = expectobj;
+ hits = 0;
+ g.evaluate(code);
+ assertEq(hits, arguments.length < 3 ? 1 : expectedHits);
+}
+
+test("debugger;", {type: "global", generator: false, constructing: false});
+test("(function () { debugger; })();", {type: "call", generator: false, constructing: false});
+test("new function() { debugger; };", {type: "call", generator: false, constructing: true});
+test("new function () { (function() { debugger; })(); }", {type: "call", generator: false, constructing: false});
+test("eval('debugger;');", {type: "eval", generator: false, constructing: false});
+test("this.eval('debugger;'); // indirect eval", {type: "eval", generator: false, constructing: false});
+test("(function () { eval('debugger;'); })();", {type: "eval", generator: false, constructing: false});
+test("new function () { eval('debugger'); }", {type: "eval", generator: false, constructing: false});
+test("function* gen() { debugger; yield 1; debugger; }\n" +
+ "for (var x of gen()) {}\n",
+ {type: "call", generator: true, constructing: false}, 2);
+test("var iter = (function* stargen() { debugger; yield 1; debugger; })();\n" +
+ "iter.next(); iter.next();",
+ {type: "call", generator: true, constructing: false}, 2);
diff --git a/js/src/jit-test/tests/debug/Frame-02.js b/js/src/jit-test/tests/debug/Frame-02.js
new file mode 100644
index 0000000000..dddb1e7024
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-02.js
@@ -0,0 +1,24 @@
+// When the debugger is triggered twice from the same stack frame, the same
+// Debugger.Frame object is passed to the hook both times.
+
+var g = newGlobal({newCompartment: true});
+var hits, frame;
+var dbg = Debugger(g);
+dbg.onDebuggerStatement = function (f) {
+ if (hits++ == 0)
+ frame = f;
+ else
+ assertEq(f, frame);
+};
+
+hits = 0;
+g.evaluate("debugger; debugger;");
+assertEq(hits, 2);
+
+hits = 0;
+g.evaluate("function f() { debugger; debugger; } f();");
+assertEq(hits, 2);
+
+hits = 0;
+g.evaluate("eval('debugger; debugger;');");
+assertEq(hits, 2);
diff --git a/js/src/jit-test/tests/debug/Frame-03.js b/js/src/jit-test/tests/debug/Frame-03.js
new file mode 100644
index 0000000000..19a300084a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-03.js
@@ -0,0 +1,19 @@
+// When the debugger is triggered from different stack frames that happen to
+// occupy the same memory, it delivers different Debugger.Frame objects.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var hits;
+var a = [];
+dbg.onDebuggerStatement = function (frame) {
+ for (var i = 0; i < a.length; i++)
+ assertEq(a[i] === frame, false);
+ a.push(frame);
+ hits++;
+};
+
+g.eval("function f() { debugger; }");
+g.eval("function h() { debugger; f(); }");
+hits = 0;
+g.eval("for (var i = 0; i < 4; i++) h();");
+assertEq(hits, 8);
diff --git a/js/src/jit-test/tests/debug/Frame-arguments-01.js b/js/src/jit-test/tests/debug/Frame-arguments-01.js
new file mode 100644
index 0000000000..29a68b7cf4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-arguments-01.js
@@ -0,0 +1,41 @@
+// Frame.prototype.arguments with primitive values
+
+var g = newGlobal({newCompartment: true});
+g.args = null;
+var dbg = new Debugger(g);
+var hits;
+var v;
+dbg.onDebuggerStatement = function (frame) {
+ hits++;
+ var args = frame.arguments;
+ assertEq(args instanceof Array, true);
+ assertEq(Array.isArray(args), false);
+ assertEq(args, frame.arguments);
+ assertEq(args.length, g.args.length);
+ for (var i = 0; i < args.length; i++)
+ assertEq(args[i], g.args[i]);
+};
+
+// no formal parameters
+g.eval("function f() { debugger; }");
+
+hits = 0;
+g.eval("args = []; f();");
+g.eval("this.f();");
+g.eval("var world = Symbol('world'); " +
+ "args = ['hello', world, 3.14, true, false, null, undefined]; " +
+ "f('hello', world, 3.14, true, false, null, undefined);");
+g.eval("f.apply(undefined, args);");
+g.eval("args = [-0, NaN, -1/0]; this.f(-0, NaN, -1/0);");
+assertEq(hits, 5);
+
+// with formal parameters
+g.eval("function f(a, b) { debugger; }");
+
+hits = 0;
+g.eval("args = []; f();");
+g.eval("this.f();");
+g.eval("args = ['a', 'b']; f('a', 'b');");
+g.eval("this.f('a', 'b');");
+g.eval("f.bind(null, 'a')('b');");
+assertEq(hits, 5);
diff --git a/js/src/jit-test/tests/debug/Frame-arguments-02.js b/js/src/jit-test/tests/debug/Frame-arguments-02.js
new file mode 100644
index 0000000000..145ec824d8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-arguments-02.js
@@ -0,0 +1,19 @@
+// Object arguments.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ var args = frame.arguments;
+ assertEq(args, frame.arguments);
+ assertEq(args instanceof Array, true);
+ assertEq(args.length, 2);
+ assertEq(args[0] instanceof Debugger.Object, true);
+ assertEq(args[0].class, args[1]);
+ hits++;
+};
+
+g.eval("function f(obj, cls) { debugger; }");
+g.eval("f({}, 'Object');");
+g.eval("f(Date, 'Function');");
+assertEq(hits, 2);
diff --git a/js/src/jit-test/tests/debug/Frame-arguments-03.js b/js/src/jit-test/tests/debug/Frame-arguments-03.js
new file mode 100644
index 0000000000..b2d36eb0b5
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-arguments-03.js
@@ -0,0 +1,34 @@
+// Destructuring arguments.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ var args = frame.arguments;
+ assertEq(args[0], 1);
+ assertEq(args.length, 4);
+
+ assertEq(args[1] instanceof Debugger.Object, true);
+ assertEq(args[1].class, "Array");
+ var getprop = frame.eval("(function (p) { return this[p]; })").return;
+ assertEq(getprop instanceof Debugger.Object, true);
+ assertEq(getprop.apply(args[1], ["length"]).return, 2);
+ assertEq(getprop.apply(args[1], [0]).return, 2);
+ assertEq(getprop.apply(args[1], [1]).return, 3);
+
+ assertEq(args[2] instanceof Debugger.Object, true);
+ assertEq(args[2].class, "Object");
+ var x = getprop.apply(args[2], ["x"]).return;
+ assertEq(x.class, "Array");
+ assertEq(getprop.apply(x, ["0"]).return, 4);
+ assertEq(getprop.apply(args[2], ["z"]).return, 5);
+
+ assertEq(args[3] instanceof Debugger.Object, true);
+ assertEq(args[3].class, "Object");
+ assertEq(getprop.apply(args[3], ["q"]).return, 6);
+ hits++;
+};
+
+g.eval("function f(a, [b, c], {x: [y], z: w}, {q}) { debugger; }");
+g.eval("f(1, [2, 3], {x: [4], z: 5}, {q: 6});");
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Frame-arguments-04.js b/js/src/jit-test/tests/debug/Frame-arguments-04.js
new file mode 100644
index 0000000000..21db3560aa
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-arguments-04.js
@@ -0,0 +1,18 @@
+// frame.arguments works for all live frames
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ for (var i = 0; i <= 4; i++) {
+ assertEq(frame.arguments.length, 1);
+ assertEq(frame.arguments[0], i);
+ frame = frame.older;
+ }
+ assertEq(frame, null);
+ hits++;
+};
+
+g.eval("function f(n) { if (n == 0) debugger; else f(n - 1); }");
+g.f(4);
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Frame-arguments-05.js b/js/src/jit-test/tests/debug/Frame-arguments-05.js
new file mode 100644
index 0000000000..bc34809b11
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-arguments-05.js
@@ -0,0 +1,19 @@
+// frame.arguments is "live" (it reflects assignments to arguments).
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var log = '';
+var args;
+dbg.onDebuggerStatement = function (frame) {
+ if (args === undefined)
+ args = frame.arguments;
+ else
+ assertEq(frame.arguments, args);
+ log += args[0];
+ assertEq(frame.eval("x = '0';").return, '0');
+ log += args[0];
+};
+
+g.eval("function f(x) { x = '2'; debugger; x = '3'; debugger; }");
+g.f("1");
+assertEq(log, "2030");
diff --git a/js/src/jit-test/tests/debug/Frame-arguments-06.js b/js/src/jit-test/tests/debug/Frame-arguments-06.js
new file mode 100644
index 0000000000..ade1b6ac8f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-arguments-06.js
@@ -0,0 +1,38 @@
+// Test extracting frame.arguments element getters and calling them in
+// various awkward ways.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var hits = 0;
+var fframe, farguments, fgetter;
+dbg.onDebuggerStatement = function (frame) {
+ if (hits === 0) {
+ fframe = frame;
+ farguments = frame.arguments;
+ fgetter = Object.getOwnPropertyDescriptor(farguments, "0").get;
+ assertEq(fgetter instanceof Function, true);
+
+ // Calling the getter without an appropriate this-object
+ // fails, but shouldn't assert or crash.
+ assertThrowsInstanceOf(function () { fgetter.call(Math); }, TypeError);
+ } else {
+ // Since fframe is still on the stack, fgetter can be applied to it.
+ assertEq(fframe.onStack, true);
+ assertEq(fgetter.call(farguments), 100);
+
+ // Since h was called without arguments, there is no argument 0.
+ assertEq(fgetter.call(frame.arguments), undefined);
+ }
+ hits++;
+};
+
+g.eval("function h() { debugger; }");
+g.eval("function f(x) { debugger; h(); }");
+g.f(100);
+assertEq(hits, 2);
+
+// Now that fframe is no longer live, trying to get its arguments should throw.
+assertEq(fframe.onStack, false);
+assertThrowsInstanceOf(function () { fgetter.call(farguments); }, Error);
diff --git a/js/src/jit-test/tests/debug/Frame-arguments-07.js b/js/src/jit-test/tests/debug/Frame-arguments-07.js
new file mode 100644
index 0000000000..a73e4e97ed
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-arguments-07.js
@@ -0,0 +1,24 @@
+// When argument[x] is assigned, where x > callee.length, frame.arguments reflects the change.
+
+var g = newGlobal({newCompartment: true});
+g.eval("function f(a, b) {\n" +
+ " for (var i = 0; i < arguments.length; i++)\n" +
+ " arguments[i] = i;\n" +
+ " eval(\"\");\n" +
+ " debugger;\n" +
+ "}\n");
+
+var dbg = Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ var argc = frame.eval("arguments.length").return;
+ var args = frame.arguments;
+ assertEq(args.length, argc);
+ for (var i = 0; i < argc; i++)
+ assertEq(args[i], i);
+ hits++;
+}
+
+g.f(9);
+g.f(9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9);
+assertEq(hits, 2);
diff --git a/js/src/jit-test/tests/debug/Frame-asyncPromise-01.js b/js/src/jit-test/tests/debug/Frame-asyncPromise-01.js
new file mode 100644
index 0000000000..d8ac80a6ce
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-asyncPromise-01.js
@@ -0,0 +1,72 @@
+// Testing the promise property on async function frames.
+
+load(libdir + 'asserts.js');
+
+var g = newGlobal({ newCompartment: true });
+var dbg = Debugger(g);
+g.eval(`
+async function f() {
+ debugger;
+ await Promise.resolve();
+ debugger;
+}
+`);
+
+let frame;
+const promises = [];
+dbg.onEnterFrame = function(f) {
+ frame = f;
+ promises.push(["start", frame.asyncPromise]);
+
+ dbg.onEnterFrame = function(f) {
+ assertEq(f, frame);
+ promises.push(["resume", frame.asyncPromise]);
+ };
+
+ frame.onPop = function() {
+ promises.push(["suspend", frame.asyncPromise]);
+
+ frame.onPop = function() {
+ promises.push(["finish", frame.asyncPromise]);
+ };
+ };
+
+ dbg.onDebuggerStatement = function(f) {
+ assertEq(f, frame);
+ promises.push(["dbg", frame.asyncPromise]);
+ };
+};
+
+(async () => {
+ const resultPromise = g.f();
+
+ const framePromise = frame.asyncPromise;
+ assertEq(framePromise instanceof Debugger.Object, true);
+ assertEq(framePromise.unsafeDereference(), resultPromise);
+
+ assertEq(promises.length, 3);
+ assertEq(promises[0][0], "start");
+ assertEq(promises[0][1], null);
+
+ assertEq(promises[1][0], "dbg");
+ assertEq(promises[1][1], framePromise);
+
+ assertEq(promises[2][0], "suspend");
+ assertEq(promises[2][1], framePromise);
+
+ await resultPromise;
+
+ // Frame has terminated, so accessing the property throws.
+ assertThrowsInstanceOf(() => frame.asyncPromise, Error);
+
+ assertEq(promises.length, 6);
+
+ assertEq(promises[3][0], "resume");
+ assertEq(promises[3][1], framePromise);
+
+ assertEq(promises[4][0], "dbg");
+ assertEq(promises[4][1], framePromise);
+
+ assertEq(promises[5][0], "finish");
+ assertEq(promises[5][1], framePromise);
+})();
diff --git a/js/src/jit-test/tests/debug/Frame-asyncPromise-02.js b/js/src/jit-test/tests/debug/Frame-asyncPromise-02.js
new file mode 100644
index 0000000000..c65d13f9c5
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-asyncPromise-02.js
@@ -0,0 +1,26 @@
+// Test the promise property on normal function frames.
+
+load(libdir + 'asserts.js');
+
+var g = newGlobal({ newCompartment: true });
+var dbg = Debugger(g);
+g.eval(`
+function f() {
+ debugger;
+}
+`);
+
+let frame;
+const promises = [];
+dbg.onDebuggerStatement = function(f) {
+ frame = f;
+ promises.push(frame.asyncPromise);
+};
+
+const resultPromise = g.f();
+
+// Frame has terminated, so accessing the property throws.
+assertThrowsInstanceOf(() => frame.asyncPromise, Error);
+
+assertEq(promises.length, 1);
+assertEq(promises[0], undefined);
diff --git a/js/src/jit-test/tests/debug/Frame-asyncPromise-03.js b/js/src/jit-test/tests/debug/Frame-asyncPromise-03.js
new file mode 100644
index 0000000000..8dee273c28
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-asyncPromise-03.js
@@ -0,0 +1,39 @@
+// Test the promise property on generator function frames.
+
+load(libdir + 'asserts.js');
+
+var g = newGlobal({ newCompartment: true });
+var dbg = Debugger(g);
+g.eval(`
+function* f() {
+ debugger;
+ yield;
+ debugger;
+}
+`);
+
+let frame;
+const promises = [];
+dbg.onDebuggerStatement = function(f) {
+ frame = f;
+ promises.push(frame.asyncPromise);
+};
+
+const it = g.f();
+
+assertEq(promises.length, 0);
+
+it.next();
+
+assertEq(frame.asyncPromise, undefined);
+
+assertEq(promises.length, 1);
+assertEq(promises[0], undefined);
+
+it.next();
+
+// Frame has terminated, so accessing the property throws.
+assertThrowsInstanceOf(() => frame.asyncPromise, Error);
+
+assertEq(promises.length, 2);
+assertEq(promises[1], undefined);
diff --git a/js/src/jit-test/tests/debug/Frame-asyncPromise-04.js b/js/src/jit-test/tests/debug/Frame-asyncPromise-04.js
new file mode 100644
index 0000000000..1d2a6bf55f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-asyncPromise-04.js
@@ -0,0 +1,104 @@
+// Test the promise property on async generator function frames.
+
+load(libdir + 'asserts.js');
+
+var g = newGlobal({ newCompartment: true });
+var dbg = Debugger(g);
+g.eval(`
+async function* f() {
+ debugger;
+ yield 42;
+ debugger;
+ return 50
+}
+`);
+
+let frame;
+const promises = [];
+dbg.onEnterFrame = function(f) {
+ frame = f;
+ promises.push(["enter", frame.asyncPromise]);
+
+ frame.onPop = function() {
+ promises.push(["leave", frame.asyncPromise]);
+ };
+
+ dbg.onDebuggerStatement = function(f) {
+ assertEq(f, frame);
+ promises.push(["dbg", frame.asyncPromise]);
+ };
+};
+
+
+(async () => {
+ const it = g.f();
+
+ // Promise is null until initial iteration.
+ assertEq(frame.asyncPromise, null);
+
+ assertEq(promises.length, 2);
+ assertEq(promises[0][0], "enter");
+ assertEq(promises[0][1], null);
+
+ assertEq(promises[1][0], "leave");
+ assertEq(promises[1][1], null);
+
+
+ const result1Promise = it.next();
+
+ const frameIt1Promise = frame.asyncPromise;
+ assertEq(frameIt1Promise instanceof Debugger.Object, true);
+ assertEq(frameIt1Promise.unsafeDereference(), result1Promise);
+
+ assertEq(promises.length, 5);
+ assertEq(promises[2][0], "enter");
+ assertEq(promises[2][1], frameIt1Promise);
+
+ assertEq(promises[3][0], "dbg");
+ assertEq(promises[3][1], frameIt1Promise);
+
+ assertEq(promises[4][0], "leave");
+ assertEq(promises[4][1], frameIt1Promise);
+
+ await result1Promise;
+
+ // Iteration step has fully completed, so promise state has been cleared.
+ assertEq(frame.asyncPromise, null);
+
+ assertEq(promises.length, 7);
+ assertEq(promises[5][0], "enter");
+ assertEq(promises[5][1], frameIt1Promise);
+
+ assertEq(promises[6][0], "leave");
+ assertEq(promises[6][1], frameIt1Promise);
+
+
+ const result2Promise = it.next();
+
+ const frameIt2Promise = frame.asyncPromise;
+ assertEq(frameIt2Promise instanceof Debugger.Object, true);
+ assertEq(frameIt2Promise.unsafeDereference(), result2Promise);
+ assertEq(frameIt1Promise !== frameIt2Promise, true);
+
+ assertEq(promises.length, 10);
+ assertEq(promises[7][0], "enter");
+ assertEq(promises[7][1], frameIt2Promise);
+
+ assertEq(promises[8][0], "dbg");
+ assertEq(promises[8][1], frameIt2Promise);
+
+ assertEq(promises[9][0], "leave");
+ assertEq(promises[9][1], frameIt2Promise);
+
+ await result2Promise;
+
+ // Frame has terminated, so accessing the property throws.
+ assertThrowsInstanceOf(() => frame.asyncPromise, Error);
+
+ assertEq(promises.length, 12);
+ assertEq(promises[10][0], "enter");
+ assertEq(promises[10][1], frameIt2Promise);
+
+ assertEq(promises[11][0], "leave");
+ assertEq(promises[11][1], frameIt2Promise);
+})();
diff --git a/js/src/jit-test/tests/debug/Frame-callee-01.js b/js/src/jit-test/tests/debug/Frame-callee-01.js
new file mode 100644
index 0000000000..05daf85784
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-callee-01.js
@@ -0,0 +1,26 @@
+// Frame.prototype.callee for a normal function frame.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({ newCompartment: true });
+var dbg = new Debugger(g);
+g.eval(`
+function f() {}
+`);
+
+const fObj = dbg.makeGlobalObjectReference(g).makeDebuggeeValue(g.f);
+let frame;
+let callee;
+dbg.onEnterFrame = function(f) {
+ frame = f;
+ callee = frame.callee;
+};
+
+g.f();
+
+assertEq(frame instanceof Debugger.Frame, true);
+assertEq(callee instanceof Debugger.Object, true);
+assertEq(callee, fObj);
+
+// The frame has finished evaluating, so the callee is no longer accessible.
+assertThrowsInstanceOf(() => frame.callee, Error);
diff --git a/js/src/jit-test/tests/debug/Frame-callee-02.js b/js/src/jit-test/tests/debug/Frame-callee-02.js
new file mode 100644
index 0000000000..ca1c979cca
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-callee-02.js
@@ -0,0 +1,37 @@
+// Frame.prototype.callee for a generator function frame.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({ newCompartment: true });
+var dbg = new Debugger(g);
+g.eval(`
+function* f() {}
+`);
+
+const fObj = dbg.makeGlobalObjectReference(g).makeDebuggeeValue(g.f);
+let frame;
+let callee;
+dbg.onEnterFrame = function(f) {
+ frame = f;
+ callee = frame.callee;
+};
+
+const it = g.f();
+
+assertEq(frame instanceof Debugger.Frame, true);
+assertEq(callee instanceof Debugger.Object, true);
+assertEq(callee, fObj);
+assertEq(frame.callee, callee);
+
+const lastFrame = frame;
+const lastCallee = callee;
+frame = null;
+callee = null;
+
+it.next();
+
+assertEq(frame, lastFrame);
+assertEq(callee, lastCallee);
+
+// The frame has finished evaluating, so the callee is no longer accessible.
+assertThrowsInstanceOf(() => frame.callee, Error);
diff --git a/js/src/jit-test/tests/debug/Frame-callee-03.js b/js/src/jit-test/tests/debug/Frame-callee-03.js
new file mode 100644
index 0000000000..fc0f6f51eb
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-callee-03.js
@@ -0,0 +1,37 @@
+// Frame.prototype.callee for an async function frame.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({ newCompartment: true });
+var dbg = new Debugger(g);
+g.eval(`
+async function f() { await Promise.resolve(); }
+`);
+
+const fObj = dbg.makeGlobalObjectReference(g).makeDebuggeeValue(g.f);
+let frame;
+let callee;
+dbg.onEnterFrame = function(f) {
+ frame = f;
+ callee = frame.callee;
+};
+
+const promise = g.f();
+
+assertEq(frame instanceof Debugger.Frame, true);
+assertEq(callee instanceof Debugger.Object, true);
+assertEq(callee, fObj);
+assertEq(frame.callee, callee);
+
+const lastFrame = frame;
+const lastCallee = callee;
+frame = null;
+callee = null;
+
+promise.then(() => {
+ assertEq(frame, lastFrame);
+ assertEq(callee, lastCallee);
+
+ // The frame has finished evaluating, so the callee is no longer accessible.
+ assertThrowsInstanceOf(() => frame.callee, Error);
+});
diff --git a/js/src/jit-test/tests/debug/Frame-callee-04.js b/js/src/jit-test/tests/debug/Frame-callee-04.js
new file mode 100644
index 0000000000..a260b7d9e6
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-callee-04.js
@@ -0,0 +1,47 @@
+// Frame.prototype.callee for an async generator function frame.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({ newCompartment: true });
+var dbg = new Debugger(g);
+g.eval(`
+async function* f() { await Promise.resolve(); }
+`);
+
+const fObj = dbg.makeGlobalObjectReference(g).makeDebuggeeValue(g.f);
+let frame;
+let callee;
+dbg.onEnterFrame = function(f) {
+ frame = f;
+ callee = frame.callee;
+};
+
+const it = g.f();
+
+assertEq(frame instanceof Debugger.Frame, true);
+assertEq(callee instanceof Debugger.Object, true);
+assertEq(callee, fObj);
+assertEq(frame.callee, callee);
+
+let lastFrame = frame;
+let lastCallee = callee;
+frame = null;
+callee = null;
+
+const promise = it.next();
+
+assertEq(callee, fObj);
+assertEq(frame.callee, callee);
+
+lastFrame = frame;
+lastCallee = callee;
+frame = null;
+callee = null;
+
+promise.then(() => {
+ assertEq(frame, lastFrame);
+ assertEq(callee, lastCallee);
+
+ // The frame has finished evaluating, so the callee is no longer accessible.
+ assertThrowsInstanceOf(() => frame.callee, Error);
+});
diff --git a/js/src/jit-test/tests/debug/Frame-constructing-01.js b/js/src/jit-test/tests/debug/Frame-constructing-01.js
new file mode 100644
index 0000000000..c17a454512
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-constructing-01.js
@@ -0,0 +1,27 @@
+// Debugger.Frame.prototype.constructing on a generator.
+
+load(libdir + "asserts.js");
+
+const g = newGlobal({ newCompartment: true });
+const dbg = Debugger(g);
+
+g.eval(`
+function* f() {}
+`);
+
+let frame;
+dbg.onEnterFrame = function(f) {
+ frame = f;
+ assertEq(frame.constructing, false);
+};
+
+const it = g.f();
+
+assertEq(frame.constructing, false);
+frame = null;
+
+it.next();
+
+assertEq(!!frame, true);
+// Throws because the frame has terminated.
+assertThrowsInstanceOf(() => frame.constructing, Error);
diff --git a/js/src/jit-test/tests/debug/Frame-constructing-02.js b/js/src/jit-test/tests/debug/Frame-constructing-02.js
new file mode 100644
index 0000000000..b1c0c846a7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-constructing-02.js
@@ -0,0 +1,31 @@
+// Debugger.Frame.prototype.constructing on an async function.
+
+load(libdir + "asserts.js");
+
+const g = newGlobal({ newCompartment: true });
+const dbg = Debugger(g);
+
+g.eval(`
+async function f() {
+ await Promise.resolve();
+}
+`);
+
+let frame;
+dbg.onEnterFrame = function(f) {
+ frame = f;
+ assertEq(frame.constructing, false);
+};
+
+(async () => {
+ const promise = g.f();
+
+ assertEq(frame.constructing, false);
+ frame = null;
+
+ await promise;
+
+ assertEq(!!frame, true);
+ // Throws because the frame has terminated.
+ assertThrowsInstanceOf(() => frame.constructing, Error);
+})();
diff --git a/js/src/jit-test/tests/debug/Frame-constructing-03.js b/js/src/jit-test/tests/debug/Frame-constructing-03.js
new file mode 100644
index 0000000000..4569314131
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-constructing-03.js
@@ -0,0 +1,35 @@
+// Debugger.Frame.prototype.constructing on an async generator.
+
+load(libdir + "asserts.js");
+
+const g = newGlobal({ newCompartment: true });
+const dbg = Debugger(g);
+
+g.eval(`
+async function* f() {
+ await Promise.resolve();
+}
+`);
+
+let frame;
+dbg.onEnterFrame = function(f) {
+ frame = f;
+ assertEq(frame.constructing, false);
+};
+
+(async () => {
+ const it = g.f();
+
+ assertEq(frame.constructing, false);
+ frame = null;
+
+ const promise = it.next();
+
+ assertEq(!!frame, true);
+ assertEq(frame.constructing, false);
+
+ await promise;
+
+ // Throws because the frame has terminated.
+ assertThrowsInstanceOf(() => frame.constructing, Error);
+})();
diff --git a/js/src/jit-test/tests/debug/Frame-environment-01.js b/js/src/jit-test/tests/debug/Frame-environment-01.js
new file mode 100644
index 0000000000..1168732e29
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-environment-01.js
@@ -0,0 +1,13 @@
+// frame.environment is a Debugger.Environment object
+
+var g = newGlobal({newCompartment: true})
+var dbg = Debugger(g);
+g.h = function () {
+ assertEq(dbg.getNewestFrame().environment instanceof Debugger.Environment, true);
+};
+
+g.eval("h()");
+g.evaluate("h()");
+g.eval("eval('h()')");
+g.eval("function f() { h(); }");
+g.f();
diff --git a/js/src/jit-test/tests/debug/Frame-environment-02.js b/js/src/jit-test/tests/debug/Frame-environment-02.js
new file mode 100644
index 0000000000..76283711be
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-environment-02.js
@@ -0,0 +1,12 @@
+// dbg.getNewestFrame().environment works.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+g.h = function () {
+ var env = dbg.getNewestFrame().environment.parent;
+ assertEq(env instanceof Debugger.Environment, true);
+ assertEq(env.object, gw);
+ assertEq(env.parent, null);
+};
+g.eval("h()");
diff --git a/js/src/jit-test/tests/debug/Frame-environment-03.js b/js/src/jit-test/tests/debug/Frame-environment-03.js
new file mode 100644
index 0000000000..71f176260c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-environment-03.js
@@ -0,0 +1,11 @@
+// If !frame.onStack, frame.environment throws.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var frame;
+g.h = function () { frame = dbg.getNewestFrame(); };
+g.eval("h();");
+assertEq(frame.onStack, false);
+assertThrowsInstanceOf(function () { frame.environment; }, Error);
diff --git a/js/src/jit-test/tests/debug/Frame-environment-04.js b/js/src/jit-test/tests/debug/Frame-environment-04.js
new file mode 100644
index 0000000000..280b8a7c49
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-environment-04.js
@@ -0,0 +1,12 @@
+// frame.environment can be called from the onEnterFrame hook.
+
+var g = newGlobal({newCompartment: true});
+g.eval("function f(x) { return 2 * x; }");
+var dbg = Debugger(g);
+var hits = 0;
+dbg.onEnterFrame = function (frame) {
+ assertEq(frame.environment.names().join(","), "arguments,x");
+ hits++;
+};
+assertEq(g.f(22), 44);
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Frame-environment-05.js b/js/src/jit-test/tests/debug/Frame-environment-05.js
new file mode 100644
index 0000000000..e39140b438
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-environment-05.js
@@ -0,0 +1,9 @@
+// Test that Debugger.Frame.prototype.environment works at all pcs of a script
+// with an aliased block scope.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+ frame.onStep = (function () { frame.environment; });
+};
+g.eval("debugger; for (let i of [1,2,3]) print(i);");
diff --git a/js/src/jit-test/tests/debug/Frame-environment-06.js b/js/src/jit-test/tests/debug/Frame-environment-06.js
new file mode 100644
index 0000000000..6faea6ad52
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-environment-06.js
@@ -0,0 +1,105 @@
+// |jit-test| error:all-jobs-completed-successfully
+// Test that Debugger.Frame.prototype.environment works on suspended
+// async function.
+
+load(libdir + "asserts.js");
+
+const g = newGlobal({ newCompartment: true });
+const dbg = new Debugger(g);
+
+g.eval(`
+var resolveTop;
+var resolveBlock;
+var resolveLoop;
+var resolveCatch;
+
+async function f() {
+ var promises = {
+ top: new Promise(r => { resolveTop = r; }),
+ block: new Promise(r => { resolveBlock = r; }),
+ loop: new Promise(r => { resolveLoop = r; }),
+ catch: new Promise(r => { resolveCatch = r; }),
+ };
+
+ var value = 42;
+ Promise.resolve().then(resolveTop);
+ await promises.top;
+ {
+ let block = "block";
+ Promise.resolve().then(resolveBlock);
+ await promises.block;
+ }
+ for (let loop of ["loop"]) {
+ Promise.resolve().then(resolveLoop);
+ await promises.loop;
+ }
+ try {
+ throw "err";
+ } catch (err) {
+ Promise.resolve().then(resolveCatch);
+ await promises.catch;
+ }
+ return value;
+}
+`);
+
+const waitForOnPop = frame => new Promise(r => {
+ assertEq(frame.onPop, undefined);
+ frame.onPop = () => {
+ frame.onPop = undefined;
+ r();
+ };
+});
+
+let frame;
+dbg.onEnterFrame = f => {
+ frame = f;
+ dbg.onEnterFrame = undefined;
+};
+
+(async () => {
+ const promise = g.f();
+
+ assertEq(!!frame, true);
+ assertEq(
+ JSON.stringify(frame.environment.names()),
+ JSON.stringify(["arguments", "promises", "value"])
+ );
+ assertEq(frame.environment.getVariable("value"), 42);
+
+ frame.environment.setVariable("value", 43);
+
+ assertEq(frame.environment.getVariable("value"), 43);
+
+ await waitForOnPop(frame);
+
+ assertEq(
+ JSON.stringify(frame.environment.names()),
+ JSON.stringify(["block"])
+ );
+ assertEq(frame.environment.getVariable("block"), "block");
+
+ await waitForOnPop(frame);
+
+ assertEq(
+ JSON.stringify(frame.environment.names()),
+ JSON.stringify(["loop"])
+ );
+ assertEq(frame.environment.getVariable("loop"), "loop");
+
+ await waitForOnPop(frame);
+
+ assertEq(
+ JSON.stringify(frame.environment.names()),
+ JSON.stringify(["err"])
+ );
+ assertEq(frame.environment.getVariable("err"), "err");
+
+ const result = await promise;
+
+ assertEq(result, 43);
+
+ assertThrowsInstanceOf(() => frame.environment, Error);
+
+ throw "all-jobs-completed-successfully";
+})();
diff --git a/js/src/jit-test/tests/debug/Frame-environment-07.js b/js/src/jit-test/tests/debug/Frame-environment-07.js
new file mode 100644
index 0000000000..5e51ba79af
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-environment-07.js
@@ -0,0 +1,83 @@
+// Test that Debugger.Frame.prototype.environment works on suspended generators.
+
+load(libdir + "asserts.js");
+
+const g = newGlobal({ newCompartment: true });
+const dbg = new Debugger(g);
+
+g.eval(`
+function* f() {
+ var value = 42;
+ yield;
+ {
+ let block = "block";
+ yield;
+ }
+ for (let loop of ["loop"]) {
+ yield;
+ }
+ try {
+ throw "err";
+ } catch (err) {
+ yield;
+ }
+ return value;
+}
+`);
+
+let frame;
+dbg.onEnterFrame = f => {
+ frame = f;
+ dbg.onEnterFrame = undefined;
+};
+
+const it = g.f();
+
+assertEq(!!frame, true);
+
+let result = it.next();
+
+assertEq(result.done, false);
+assertEq(result.value, undefined);
+
+assertEq(
+ JSON.stringify(frame.environment.names()),
+ JSON.stringify(["arguments", "value"])
+);
+
+assertEq(frame.environment.getVariable("value"), 42);
+
+frame.environment.setVariable("value", 43);
+
+assertEq(frame.environment.getVariable("value"), 43);
+
+result = it.next();
+
+assertEq(
+ JSON.stringify(frame.environment.names()),
+ JSON.stringify(["block"])
+);
+assertEq(frame.environment.getVariable("block"), "block");
+
+result = it.next();
+
+assertEq(
+ JSON.stringify(frame.environment.names()),
+ JSON.stringify(["loop"])
+);
+assertEq(frame.environment.getVariable("loop"), "loop");
+
+result = it.next();
+
+assertEq(
+ JSON.stringify(frame.environment.names()),
+ JSON.stringify(["err"])
+);
+assertEq(frame.environment.getVariable("err"), "err");
+
+result = it.next();
+
+assertEq(result.done, true);
+assertEq(result.value, 43);
+
+assertThrowsInstanceOf(() => frame.environment, Error);
diff --git a/js/src/jit-test/tests/debug/Frame-environment-08.js b/js/src/jit-test/tests/debug/Frame-environment-08.js
new file mode 100644
index 0000000000..f4d10d0547
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-environment-08.js
@@ -0,0 +1,108 @@
+// |jit-test| error:all-jobs-completed-successfully
+// Test that Debugger.Frame.prototype.environment works on suspended
+// async generators.
+
+load(libdir + "asserts.js");
+
+const g = newGlobal({ newCompartment: true });
+const dbg = new Debugger(g);
+
+g.eval(`
+var resolveTop;
+var resolveBlock;
+var resolveLoop;
+var resolveCatch;
+
+async function* f() {
+ var promises = {
+ top: new Promise(r => { resolveTop = r; }),
+ block: new Promise(r => { resolveBlock = r; }),
+ loop: new Promise(r => { resolveLoop = r; }),
+ catch: new Promise(r => { resolveCatch = r; }),
+ };
+
+ var value = 42;
+ await promises.top;
+ {
+ let block = "block";
+ await promises.block;
+ }
+ for (let loop of ["loop"]) {
+ await promises.loop;
+ }
+ try {
+ throw "err";
+ } catch (err) {
+ await promises.catch;
+ }
+ return value;
+}
+`);
+
+const waitForOnPop = frame => new Promise(r => {
+ assertEq(frame.onPop, undefined);
+ frame.onPop = () => {
+ frame.onPop = undefined;
+ r();
+ };
+});
+
+let frame;
+dbg.onEnterFrame = f => {
+ frame = f;
+ dbg.onEnterFrame = undefined;
+};
+
+(async () => {
+ const it = g.f();
+
+ assertEq(!!frame, true);
+
+ let promise = it.next();
+
+ assertEq(
+ JSON.stringify(frame.environment.names()),
+ JSON.stringify(["arguments", "promises", "value"])
+ );
+
+ //FIXME assertEq(frame.environment.getVariable("value"), 42);
+
+ frame.environment.setVariable("value", 43);
+
+ g.resolveTop();
+ await waitForOnPop(frame);
+
+ assertEq(
+ JSON.stringify(frame.environment.names()),
+ JSON.stringify(["block"])
+ );
+ assertEq(frame.environment.getVariable("block"), "block");
+
+ g.resolveBlock();
+ await waitForOnPop(frame);
+
+ assertEq(
+ JSON.stringify(frame.environment.names()),
+ JSON.stringify(["loop"])
+ );
+ assertEq(frame.environment.getVariable("loop"), "loop");
+
+ g.resolveLoop();
+ await waitForOnPop(frame);
+
+ assertEq(
+ JSON.stringify(frame.environment.names()),
+ JSON.stringify(["err"])
+ );
+ assertEq(frame.environment.getVariable("err"), "err");
+
+ g.resolveCatch();
+ const result = await promise;
+
+ assertEq(result.done, true);
+ //FIXME assertEq(result.value, 43);
+
+ assertThrowsInstanceOf(() => frame.environment, Error);
+
+ throw "all-jobs-completed-successfully";
+})();
diff --git a/js/src/jit-test/tests/debug/Frame-eval-01.js b/js/src/jit-test/tests/debug/Frame-eval-01.js
new file mode 100644
index 0000000000..ae1a63cdb7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-01.js
@@ -0,0 +1,8 @@
+// simplest possible test of Debugger.Frame.prototype.eval
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var c;
+dbg.onDebuggerStatement = function (frame) { c = frame.eval("2 + 2"); };
+g.eval("debugger;");
+assertEq(c.return, 4);
diff --git a/js/src/jit-test/tests/debug/Frame-eval-02.js b/js/src/jit-test/tests/debug/Frame-eval-02.js
new file mode 100644
index 0000000000..79b5ff8a15
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-02.js
@@ -0,0 +1,10 @@
+// frame.eval() throws if frame is not live
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var f;
+dbg.onDebuggerStatement = function (frame) { f = frame; };
+g.eval("debugger;");
+assertThrowsInstanceOf(function () { f.eval("2 + 2"); }, Error);
diff --git a/js/src/jit-test/tests/debug/Frame-eval-03.js b/js/src/jit-test/tests/debug/Frame-eval-03.js
new file mode 100644
index 0000000000..9b640b7c13
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-03.js
@@ -0,0 +1,19 @@
+// Test eval-ing names in a topmost script frame
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ assertEq(frame.eval("a").return, 2);
+ assertEq(frame.eval("c").return, 4);
+ var exc = frame.eval("d").throw;
+ assertEq(exc instanceof Debugger.Object, true);
+ assertEq(exc.proto, frame.eval("ReferenceError.prototype").return);
+ hits++;
+};
+g.eval("function f(a, b) { var c = a + b; debugger; eval('debugger;'); }");
+g.eval("f(2, 2);");
+g.eval("var a = 2, b = 2, c = a + b; debugger;");
+assertEq(hits, 3);
diff --git a/js/src/jit-test/tests/debug/Frame-eval-04.js b/js/src/jit-test/tests/debug/Frame-eval-04.js
new file mode 100644
index 0000000000..83ec7945eb
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-04.js
@@ -0,0 +1,11 @@
+// frame.eval SyntaxErrors are reflected, not thrown
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var exc, SEp;
+dbg.onDebuggerStatement = function (frame) {
+ exc = frame.eval("#$@!").throw;
+ SEp = frame.eval("SyntaxError.prototype").return;
+};
+g.eval("debugger;");
+assertEq(exc.proto, SEp);
diff --git a/js/src/jit-test/tests/debug/Frame-eval-05.js b/js/src/jit-test/tests/debug/Frame-eval-05.js
new file mode 100644
index 0000000000..44fdf77701
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-05.js
@@ -0,0 +1,14 @@
+// var declarations in strict frame.eval do not modify the frame
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var cv;
+dbg.onDebuggerStatement = function (frame) {
+ cv = frame.eval("'use strict'; var a = 2; h();");
+};
+g.a = 1;
+g.eval("function f(s) { function h() { return a; } eval(s); debugger; } ");
+g.eval("f('0');");
+assertEq(cv.return, 1);
+g.eval("f('var a = 3;');");
+assertEq(cv.return, 3);
diff --git a/js/src/jit-test/tests/debug/Frame-eval-06.js b/js/src/jit-test/tests/debug/Frame-eval-06.js
new file mode 100644
index 0000000000..811ea8a926
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-06.js
@@ -0,0 +1,19 @@
+// frame.eval throws if frame is a generator frame that isn't currently on the stack
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+g.eval("function* gen(a) { debugger; yield a; }");
+g.eval("function test() { debugger; }");
+var dbg = new Debugger(g);
+var genframe;
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ if (frame.callee.name == 'gen')
+ genframe = frame;
+ else
+ assertThrowsInstanceOf(function () { genframe.eval("a"); }, Error);
+ hits++;
+};
+g.eval("var it = gen(42); assertEq(it.next().value, 42); test();");
+assertEq(hits, 2);
diff --git a/js/src/jit-test/tests/debug/Frame-eval-07.js b/js/src/jit-test/tests/debug/Frame-eval-07.js
new file mode 100644
index 0000000000..6770a40f32
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-07.js
@@ -0,0 +1,31 @@
+// test frame.eval in non-top frames
+
+var g = newGlobal({newCompartment: true});
+var N = g.N = 12; // must be even
+assertEq(N % 2, 0);
+var dbg = new Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ var n = frame.eval("n").return;
+ if (n === 0) {
+ for (var i = 0; i <= N; i++) {
+ assertEq(frame.type, 'call');
+ assertEq(frame.callee.name, i % 2 === 0 ? 'even' : 'odd');
+ assertEq(frame.eval("n").return, i);
+ frame = frame.older;
+ }
+ assertEq(frame.type, 'call');
+ assertEq(frame.callee.name, undefined);
+ frame = frame.older;
+ assertEq(frame.type, 'eval');
+ hits++;
+ }
+};
+
+var result = g.eval("(" + function () {
+ function odd(n) { return n > 0 && !even(n - 1); }
+ function even(n) { debugger; return n == 0 || !odd(n - 1); }
+ return even(N);
+ } + ")();");
+assertEq(result, true);
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Frame-eval-08.js b/js/src/jit-test/tests/debug/Frame-eval-08.js
new file mode 100644
index 0000000000..a6a12a5375
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-08.js
@@ -0,0 +1,23 @@
+// The arguments can escape from a function via a debugging hook.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+// capture arguments object and test function
+var args, testfn;
+dbg.onDebuggerStatement = function (frame) {
+ args = frame.eval("arguments").return;
+ testfn = frame.eval("test").return;
+};
+g.eval("function f() { debugger; }");
+g.eval("var test = " + function test(args) {
+ assertEq(args.length, 3);
+ assertEq(args[0], this);
+ assertEq(args[1], f);
+ assertEq(args[2].toString(), "[object Object]");
+ return 42;
+ } + ";");
+g.eval("f(this, f, {});");
+
+var cv = testfn.apply(null, [args]);
+assertEq(cv.return, 42);
diff --git a/js/src/jit-test/tests/debug/Frame-eval-09.js b/js/src/jit-test/tests/debug/Frame-eval-09.js
new file mode 100644
index 0000000000..cb7c0b8ca7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-09.js
@@ -0,0 +1,21 @@
+// assigning to local variables in frame.eval code
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+ frame.eval("outerarg = 1; outervar = 2; innerarg = 3; innervar = 4;");
+};
+
+var result = g.eval("(" + function outer(outerarg) {
+ var outervar = 200;
+ function inner(innerarg) {
+ var innervar = 400;
+ eval("");
+ debugger;
+ return innerarg + innervar;
+ }
+ var innersum = inner(300);
+ return outerarg + outervar + innersum;
+ } + ")(100)");
+
+assertEq(result, 10);
diff --git a/js/src/jit-test/tests/debug/Frame-eval-10.js b/js/src/jit-test/tests/debug/Frame-eval-10.js
new file mode 100644
index 0000000000..7605aefd58
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-10.js
@@ -0,0 +1,13 @@
+// frame.eval returns null if the eval code fails with an uncatchable error.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ if (hits++ === 0)
+ assertEq(frame.eval("debugger;"), null);
+ else
+ return null;
+};
+assertEq(g.eval("debugger; 'ok';"), "ok");
+assertEq(hits, 2);
diff --git a/js/src/jit-test/tests/debug/Frame-eval-11.js b/js/src/jit-test/tests/debug/Frame-eval-11.js
new file mode 100644
index 0000000000..4e76a1a5b9
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-11.js
@@ -0,0 +1,15 @@
+// The arguments can escape from a function via a debugging hook.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+// capture arguments object and test function
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ assertEq(frame.older.eval('arguments[0]').return, 'ponies');
+ hits++;
+};
+g.eval("function g() { debugger; }");
+g.eval("function f() { g(); }");
+g.eval("f('ponies')");
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Frame-eval-12.js b/js/src/jit-test/tests/debug/Frame-eval-12.js
new file mode 100644
index 0000000000..498e7a07a2
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-12.js
@@ -0,0 +1,13 @@
+// The arguments can escape from a function via a debugging hook.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+// capture arguments object and test function
+dbg.onDebuggerStatement = function (frame) {
+ var args = frame.older.environment.parent.getVariable('arguments');
+ assertEq(args.missingArguments, true);
+};
+g.eval("function h() { debugger; }");
+g.eval("function f() { var x = 0; return function() { x++; h() } }");
+g.eval("f('ponies')()");
diff --git a/js/src/jit-test/tests/debug/Frame-eval-13.js b/js/src/jit-test/tests/debug/Frame-eval-13.js
new file mode 100644
index 0000000000..8aebc4f1cf
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-13.js
@@ -0,0 +1,13 @@
+// The debugger may add new bindings into existing scopes
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+dbg.onDebuggerStatement = function(frame) {
+ assertEq(frame.eval("var x = 3; x").return, 3);
+ hits++;
+}
+var hits = 0;
+g.eval("(function() { debugger; })()");
+assertEq(hits, 1);
+g.eval("(function() { var x = 4; debugger; })()");
+assertEq(hits, 2);
diff --git a/js/src/jit-test/tests/debug/Frame-eval-14.js b/js/src/jit-test/tests/debug/Frame-eval-14.js
new file mode 100644
index 0000000000..7a0cfc2aa3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-14.js
@@ -0,0 +1,26 @@
+// Test the corner case of accessing an unaliased variable of a block
+// while the block is not live.
+
+var g = newGlobal({newCompartment: true});
+g.eval("function h() { debugger }");
+g.eval("function f() { { let x = 1, y; (function() { y = 0 })(); h() } }");
+g.eval("var surprise = null");
+
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+dbg.onDebuggerStatement = function(hFrame) {
+ var fFrame = hFrame.older;
+ assertEq(fFrame.environment.getVariable('x'), 1);
+ assertEq(fFrame.environment.getVariable('y'), 0);
+ fFrame.eval("surprise = function() { return ++x }");
+ assertEq(gw.executeInGlobal("surprise()").return, 2);
+}
+g.f();
+assertEq(g.surprise !== null, true);
+
+// Either succeed or throw an error about 'x' not being live
+try {
+ assertEq(g.surprise(), 3);
+} catch (e) {
+ assertEq(e+'', 'Error: x is not live');
+}
diff --git a/js/src/jit-test/tests/debug/Frame-eval-15.js b/js/src/jit-test/tests/debug/Frame-eval-15.js
new file mode 100644
index 0000000000..3a452305cc
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-15.js
@@ -0,0 +1,13 @@
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+g.eval("function h() { debugger }");
+g.eval("function f() { h() }");
+g.blah = 42;
+dbg.onDebuggerStatement = function(frame) {
+ frame.older.eval("var blah = 43");
+ frame.older.eval("blah = 44");
+ assertEq(frame.older.environment.getVariable("blah"), 44);
+}
+g.f();
+assertEq(g.blah, 42);
diff --git a/js/src/jit-test/tests/debug/Frame-eval-16.js b/js/src/jit-test/tests/debug/Frame-eval-16.js
new file mode 100644
index 0000000000..f47ccfca76
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-16.js
@@ -0,0 +1,26 @@
+// eval correctly handles optional custom url option
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var count = 0;
+
+function testUrl (options, expected) {
+ count++;
+ dbg.onDebuggerStatement = function (frame) {
+ dbg.onNewScript = function (script) {
+ dbg.onNewScript = undefined;
+ assertEq(script.url, expected);
+ count--;
+ };
+ frame.eval("", options);
+ };
+ g.eval("debugger;");
+}
+
+
+testUrl(undefined, "debugger eval code");
+testUrl(null, "debugger eval code");
+testUrl({ url: undefined }, "debugger eval code");
+testUrl({ url: null }, "null");
+testUrl({ url: 5 }, "5");
+testUrl({ url: "test" }, "test");
+assertEq(count, 0);
diff --git a/js/src/jit-test/tests/debug/Frame-eval-17.js b/js/src/jit-test/tests/debug/Frame-eval-17.js
new file mode 100644
index 0000000000..22fb095505
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-17.js
@@ -0,0 +1,24 @@
+// eval correctly handles optional lineNumber option
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var count = 0;
+
+function testLineNumber (options, expected) {
+ count++;
+ dbg.onDebuggerStatement = function (frame) {
+ dbg.onNewScript = function (script) {
+ dbg.onNewScript = undefined;
+ assertEq(script.startLine, expected);
+ count--;
+ };
+ frame.eval("", options);
+ };
+ g.eval("debugger;");
+}
+
+
+testLineNumber(undefined, 1);
+testLineNumber({}, 1);
+testLineNumber({ lineNumber: undefined }, 1);
+testLineNumber({ lineNumber: 5 }, 5);
+assertEq(count, 0);
diff --git a/js/src/jit-test/tests/debug/Frame-eval-18.js b/js/src/jit-test/tests/debug/Frame-eval-18.js
new file mode 100644
index 0000000000..3cde24eb28
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-18.js
@@ -0,0 +1,12 @@
+// yield is not allowed in eval in a star generator.
+
+load(libdir + 'asserts.js');
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+dbg.onDebuggerStatement = function (frame) {
+ assertThrowsInstanceOf(function() { frame.eval('yield 10;') }, SyntaxError);
+};
+
+g.eval("(function*g(){ debugger; })()");
diff --git a/js/src/jit-test/tests/debug/Frame-eval-19.js b/js/src/jit-test/tests/debug/Frame-eval-19.js
new file mode 100644
index 0000000000..950113f8c0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-19.js
@@ -0,0 +1,34 @@
+// Eval-in-frame of optimized frames to break out of an infinite loop.
+
+load(libdir + "jitopts.js");
+
+if (!jitTogglesMatch(Opts_IonEagerNoOffthreadCompilation))
+ quit(0);
+
+withJitOptions(Opts_IonEagerNoOffthreadCompilation, function () {
+ var g = newGlobal({newCompartment: true});
+ var dbg = new Debugger;
+
+ g.eval("" + function f(d) { g(d); });
+ g.eval("" + function g(d) { h(d); });
+ g.eval("" + function h(d) {
+ var i = 0;
+ while (d)
+ interruptIf(d && i++ == 4000);
+ });
+
+ setInterruptCallback(function () {
+ dbg.addDebuggee(g);
+ var frame = dbg.getNewestFrame();
+ if (frame.callee.name != "h" || frame.implementation != "ion")
+ return true;
+ frame.eval("d = false;");
+ return true;
+ });
+
+ g.eval("(" + function () {
+ for (i = 0; i < 5; i++)
+ f(false);
+ f(true);
+ } + ")();");
+});
diff --git a/js/src/jit-test/tests/debug/Frame-eval-20.js b/js/src/jit-test/tests/debug/Frame-eval-20.js
new file mode 100644
index 0000000000..dde786f8ef
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-20.js
@@ -0,0 +1,48 @@
+// |jit-test| --ion-osr=off
+
+// Eval-in-frame with different type on non-youngest Ion frame.
+
+load(libdir + "jitopts.js");
+
+if (!jitTogglesMatch(Opts_Ion2NoOffthreadCompilation))
+ quit(0);
+
+withJitOptions(Opts_Ion2NoOffthreadCompilation, function () {
+ function test(shadow) {
+ var g = newGlobal({newCompartment: true});
+ var dbg = new Debugger;
+
+ // Note that we depend on CCW scripted functions being opaque to Ion
+ // optimization for this test.
+ g.h = function h(d) {
+ if (d) {
+ dbg.addDebuggee(g);
+ var f = dbg.getNewestFrame().older;
+ assertEq(f.implementation, "ion");
+ assertEq(f.environment.getVariable("foo"), 42);
+
+ // EIF of a different type too.
+ f.eval((shadow ? "var " : "") + "foo = 'string of 42'");
+ g.expected = shadow ? 42 : "string of 42";
+ }
+ }
+
+ g.eval("" + function f(d) {
+ var foo = 42;
+ g(d);
+ return foo;
+ });
+ g.eval("" + function g(d) {
+ h(d);
+ });
+
+ g.eval("(" + function () {
+ for (i = 0; i < 5; i++)
+ f(false);
+ assertEq(f(true), "string of 42");
+ } + ")();");
+ }
+
+ test(false);
+ test(true);
+});
diff --git a/js/src/jit-test/tests/debug/Frame-eval-21.js b/js/src/jit-test/tests/debug/Frame-eval-21.js
new file mode 100644
index 0000000000..a1fdc8b810
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-21.js
@@ -0,0 +1,33 @@
+// Eval-in-frame with different type on baseline frame with let-scoping
+
+load(libdir + "jitopts.js");
+
+if (!jitTogglesMatch(Opts_BaselineEager))
+ quit(0);
+
+withJitOptions(Opts_BaselineEager, function () {
+ var g = newGlobal({newCompartment: true});
+ var dbg = new Debugger;
+
+ g.h = function h(d) {
+ if (d) {
+ dbg.addDebuggee(g);
+ var f = dbg.getNewestFrame().older;
+ assertEq(f.implementation, "baseline");
+ assertEq(f.environment.getVariable("foo"), 42);
+ f.eval("foo = 'string of 42'");
+ }
+ }
+
+ g.eval("" + function f(d) {
+ if (d) {
+ let foo = 42;
+ g(d);
+ return foo;
+ }
+ });
+
+ g.eval("" + function g(d) { h(d); });
+
+ g.eval("(" + function () { assertEq(f(true), "string of 42"); } + ")();");
+});
diff --git a/js/src/jit-test/tests/debug/Frame-eval-22.js b/js/src/jit-test/tests/debug/Frame-eval-22.js
new file mode 100644
index 0000000000..2a9f081feb
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-22.js
@@ -0,0 +1,32 @@
+// Debugger.Frame preserves Ion frame identity
+
+load(libdir + "jitopts.js");
+
+if (!jitTogglesMatch(Opts_Ion2NoOffthreadCompilation))
+ quit();
+
+withJitOptions(Opts_Ion2NoOffthreadCompilation, function () {
+ var g = newGlobal({newCompartment: true});
+ var dbg1 = new Debugger;
+ var dbg2 = new Debugger;
+
+ g.toggle = function toggle(x, d) {
+ if (d) {
+ dbg1.addDebuggee(g);
+ dbg2.addDebuggee(g);
+ var frame1 = dbg1.getNewestFrame();
+ assertEq(frame1.environment.getVariable("x"), x);
+ assertEq(frame1.implementation, "ion");
+ frame1.environment.setVariable("x", "not 42");
+ assertEq(dbg2.getNewestFrame().environment.getVariable("x"), "not 42");
+ }
+ };
+
+ g.eval("" + function f(x, d) { toggle(x, d); });
+
+ g.eval("(" + function test() {
+ for (var i = 0; i < 5; i++)
+ f(42, false);
+ f(42, true);
+ } + ")();");
+});
diff --git a/js/src/jit-test/tests/debug/Frame-eval-23.js b/js/src/jit-test/tests/debug/Frame-eval-23.js
new file mode 100644
index 0000000000..ae30791809
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-23.js
@@ -0,0 +1,37 @@
+// Debugger.Frame preserves Ion frame mutations after removing debuggee.
+
+load(libdir + "jitopts.js");
+
+if (!jitTogglesMatch(Opts_Ion2NoOffthreadCompilation))
+ quit();
+
+withJitOptions(Opts_Ion2NoOffthreadCompilation, function () {
+ var g = newGlobal({newCompartment: true});
+ var dbg = new Debugger;
+
+ g.toggle = function toggle(x, d) {
+ if (d) {
+ dbg.addDebuggee(g);
+ var frame = dbg.getNewestFrame().older;
+ assertEq(frame.callee.name, "f");
+ assertEq(frame.environment.getVariable("x"), x);
+ assertEq(frame.implementation, "ion");
+ frame.environment.setVariable("x", "not 42");
+ dbg.removeDebuggee(g);
+ }
+ };
+
+ g.eval("" + function f(x, d) {
+ g(x, d);
+ if (d)
+ assertEq(x, "not 42");
+ });
+
+ g.eval("" + function g(x, d) { toggle(x, d); });
+
+ g.eval("(" + function test() {
+ for (var i = 0; i < 5; i++)
+ f(42, false);
+ f(42, true);
+ } + ")();");
+});
diff --git a/js/src/jit-test/tests/debug/Frame-eval-24.js b/js/src/jit-test/tests/debug/Frame-eval-24.js
new file mode 100644
index 0000000000..e3a9096d10
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-24.js
@@ -0,0 +1,25 @@
+// Make sure the getVariable/setVariable/eval functions work correctly with
+// unaliased locals.
+var g = newGlobal({newCompartment: true});
+g.eval(`
+function g() { debugger; };
+function f(arg) {
+ var y = arg - 3;
+ var a1 = 1;
+ var a2 = 1;
+ var b = arg + 9;
+ var z = function() { return a1 + a2; };
+ g();
+ return y * b; // To prevent the JIT from optimizing out y and b.
+};`);
+
+var dbg = new Debugger(g);
+
+dbg.onDebuggerStatement = function handleDebugger(frame) {
+ assertEq(frame.older.eval("y + b").return, 26);
+ assertEq(frame.older.environment.getVariable("y"), 7);
+ frame.older.environment.setVariable("b", 4);
+ assertEq(frame.older.eval("y + b").return, 11);
+};
+
+g.f(10);
diff --git a/js/src/jit-test/tests/debug/Frame-eval-25.js b/js/src/jit-test/tests/debug/Frame-eval-25.js
new file mode 100644
index 0000000000..cc91b28587
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-25.js
@@ -0,0 +1,25 @@
+// Make sure we can recover missing arguments even when it gets assigned to
+// another slot.
+
+load(libdir + "asserts.js");
+load(libdir + "evalInFrame.js");
+
+function h() {
+ evalInFrame(1, "a.push(0)");
+}
+
+function f() {
+ var a = arguments;
+ h();
+}
+
+assertThrowsInstanceOf(f, TypeError);
+
+function g() {
+ {
+ let a = arguments;
+ h();
+ }
+}
+
+assertThrowsInstanceOf(g, TypeError);
diff --git a/js/src/jit-test/tests/debug/Frame-eval-26.js b/js/src/jit-test/tests/debug/Frame-eval-26.js
new file mode 100644
index 0000000000..5a15c305e8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-26.js
@@ -0,0 +1,16 @@
+// Bug 1026477: Defining functions with D.F.p.eval works, even if there's
+// already a non-aliased var binding for the identifier.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+ frame.older.eval('function f() { }');
+};
+
+// When the compiler sees the 'debugger' statement, it marks all variables as
+// aliased, but we want to test the case where f is in a stack frame slot, so we
+// put the 'debugger' statement in a separate function, and use frame.older to
+// get back to the anonymous function's frame.
+g.eval('function q() { debugger; }');
+assertEq(typeof g.eval('(function () { var f = 42; q(); return f; })();'),
+ "function");
diff --git a/js/src/jit-test/tests/debug/Frame-eval-27.js b/js/src/jit-test/tests/debug/Frame-eval-27.js
new file mode 100644
index 0000000000..38553c1967
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-27.js
@@ -0,0 +1,13 @@
+// Bug 1026477: Defining functions with D.F.p.eval works, even if there's
+// already a var binding for the identifier.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+ frame.eval('function f() { }');
+};
+
+// When the compiler sees the 'debugger' statement, it marks all variables as
+// aliased, so f will live in a Call object.
+assertEq(typeof g.eval('(function () { var f = 42; debugger; return f;})();'),
+ "function");
diff --git a/js/src/jit-test/tests/debug/Frame-eval-28.js b/js/src/jit-test/tests/debug/Frame-eval-28.js
new file mode 100644
index 0000000000..ec11acb53f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-28.js
@@ -0,0 +1,12 @@
+// Test that strict Debugger.Frame.eval has a correct static scope.
+options('strict_mode');
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var hits = 0;
+dbg.onEnterFrame = function(f) {
+ hits++;
+ if (hits > 2)
+ return;
+ f.eval("42");
+};
+g.eval("42");
diff --git a/js/src/jit-test/tests/debug/Frame-eval-29.js b/js/src/jit-test/tests/debug/Frame-eval-29.js
new file mode 100644
index 0000000000..73a3e735e0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-29.js
@@ -0,0 +1,59 @@
+// Test reading and setting values on "hollow" debug scopes. In testGet and
+// testSet below, f and g *must* be called from a non-heavyweight lambda to
+// trigger the creation of the "hollow" debug scopes for the missing scopes.
+//
+// The reason is that a direct call to f or g that accesses a in testGet or
+// testSet's frame is actually recoverable. The Debugger can synthesize a scope
+// based on the frame. By contorting through a lambda, it becomes unsound to
+// synthesize a scope based on the lambda function's frame. Since f and g are
+// accessing a, which is itself free inside the lambda, the Debugger has no way
+// to tell if the on-stack testGet or testSet frame is the frame that *would
+// have* allocated a scope for the lambda, *had the lambda been heavyweight*.
+//
+// More concretely, if the inner lambda were returned from testGet and testSet,
+// then called from a different invocation of testGet or testSet, it becomes
+// obvious that it is incorrect to synthesize a scope based on the frame of
+// that different invocation.
+
+load(libdir + "evalInFrame.js");
+
+function f() {
+ // Eval one frame up. Nothing aliases a.
+ evalInFrame(1, "print(a)");
+}
+
+function g() {
+ evalInFrame(1, "a = 43");
+}
+
+function testGet() {
+ {
+ let a = 42;
+ (function () { f(); })();
+ }
+}
+
+function testSet() {
+ {
+ let a = 42;
+ (function () { g(); })();
+ }
+}
+
+var log = "";
+
+try {
+ testGet();
+} catch (e) {
+ // Throws due to a having been optimized out.
+ log += "g";
+}
+
+try {
+ testSet();
+} catch (e) {
+ // Throws due to a having been optimized out.
+ log += "s";
+}
+
+assertEq(log, "gs");
diff --git a/js/src/jit-test/tests/debug/Frame-eval-30.js b/js/src/jit-test/tests/debug/Frame-eval-30.js
new file mode 100644
index 0000000000..f99912c452
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-30.js
@@ -0,0 +1,19 @@
+// Test that Debugger.Frame.eval correctly throws on redeclaration.
+
+load(libdir + "evalInFrame.js");
+
+let x;
+
+function f() {
+ evalInFrame(1, "var x;");
+}
+
+var log = "";
+
+try {
+ f();
+} catch (e) {
+ log += "e";
+}
+
+assertEq(log, "e");
diff --git a/js/src/jit-test/tests/debug/Frame-eval-31.js b/js/src/jit-test/tests/debug/Frame-eval-31.js
new file mode 100644
index 0000000000..bef94a046a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-31.js
@@ -0,0 +1,9 @@
+// evalInFrame with non-syntactic scopes.
+
+load(libdir + "asserts.js");
+load(libdir + "evalInFrame.js");
+
+evalReturningScope(`
+ var x = 42;
+ assertEq(evalInFrame(0, "x"), 42);
+`);
diff --git a/js/src/jit-test/tests/debug/Frame-eval-32.js b/js/src/jit-test/tests/debug/Frame-eval-32.js
new file mode 100644
index 0000000000..9a5309d6aa
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-32.js
@@ -0,0 +1,8 @@
+// |jit-test| error: ReferenceError
+
+// Test the TDZ works for glbao lexicals through Debugger environments in
+// compound assignments.
+load(libdir + "evalInFrame.js");
+
+evalInFrame(0, "x |= 0");
+let x;
diff --git a/js/src/jit-test/tests/debug/Frame-eval-33.js b/js/src/jit-test/tests/debug/Frame-eval-33.js
new file mode 100644
index 0000000000..d5d3f94804
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-33.js
@@ -0,0 +1,39 @@
+load(libdir + "evalInFrame.js");
+
+// Test that computing the implicit 'this' in calls for D.F.eval is as if it
+// were a pasted-in eval.
+
+var G = this;
+
+function globalFun(check, expectedThis) {
+ if (check)
+ assertEq(this, expectedThis);
+ return this;
+}
+var expectedGlobalFunThis = globalFun(false);
+evalInFrame(0, "globalFun(true, expectedGlobalFunThis)");
+
+(function testInnerFun() {
+ function innerFun(check, expectedThis) {
+ if (check)
+ assertEq(this, expectedThis);
+ return this;
+ }
+ var expectedInnerFunThis = innerFun(false);
+ evalInFrame(0, "innerFun(true, expectedInnerFunThis)");
+ return [innerFun, expectedInnerFunThis]; // To prevent the JIT from optimizing out vars.
+})();
+
+(function testWith() {
+ var o = {
+ withFun: function withFun(check, expectedThis) {
+ if (check)
+ assertEq(this, expectedThis);
+ return this;
+ }
+ };
+ with (o) {
+ var expectedWithFunThis = withFun(false);
+ evalInFrame(0, "withFun(true, expectedWithFunThis)");
+ }
+})();
diff --git a/js/src/jit-test/tests/debug/Frame-eval-stack.js b/js/src/jit-test/tests/debug/Frame-eval-stack.js
new file mode 100644
index 0000000000..71f7c74789
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-stack.js
@@ -0,0 +1,19 @@
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+g.eval("function h() { debugger; }");
+g.eval("function g() { h() }");
+g.eval("function f() { var blah = 333; g() }");
+
+dbg.onDebuggerStatement = function(frame) {
+ frame = frame.older;
+ g.trace = frame.older.eval("(new Error()).stack;").return;
+}
+g.f();
+
+assertEq(typeof g.trace, "string");
+
+var frames = g.trace.split("\n");
+assertEq(frames[0].includes("eval code"), true);
+assertEq(frames[1].startsWith("f@"), true);
+assertEq(frames[2].startsWith("@"), true);
diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-01.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-01.js
new file mode 100644
index 0000000000..792d4b1867
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-01.js
@@ -0,0 +1,35 @@
+// evalWithBindings basics
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ assertEq(frame.evalWithBindings("x", {x: 2}).return, 2);
+ assertEq(frame.evalWithBindings("x + y", {x: 2}).return, 5);
+ hits++;
+};
+
+// in global code
+g.y = 3;
+g.eval("debugger;");
+
+// in function code
+g.y = "fail";
+g.eval("function f(y) { debugger; }");
+g.f(3);
+
+// in direct eval code
+g.eval("function f() { var y = 3; eval('debugger;'); }");
+g.f();
+
+// in strict eval code with var
+g.eval("function f() { 'use strict'; eval('var y = 3; debugger;'); }");
+g.f();
+
+// in a with block
+g.eval("with ({y: 3}) { debugger; }");
+
+// shadowing
+g.eval("{ let x = 50, y = 3; debugger; }");
+
+assertEq(hits, 6);
diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-02.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-02.js
new file mode 100644
index 0000000000..debb901b4d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-02.js
@@ -0,0 +1,21 @@
+// evalWithBindings to call a method of a debuggee value
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var global = dbg.addDebuggee(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ var obj = frame.arguments[0];
+ var expected = frame.arguments[1];
+ assertEq(frame.evalWithBindings("obj.toString()", {obj: obj}).return, expected);
+ hits++;
+};
+
+g.eval("function f(obj, expected) { debugger; }");
+
+g.eval("f(new Number(-0), '0');");
+g.eval("f(new String('ok'), 'ok');");
+g.eval("f(Symbol('still ok'), 'Symbol(still ok)');");
+g.eval("f(Object(Symbol('still ok')), 'Symbol(still ok)');");
+g.eval("f({toString: function () { return f; }}, f);");
+assertEq(hits, 5);
diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-03.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-03.js
new file mode 100644
index 0000000000..91ebbf5a13
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-03.js
@@ -0,0 +1,16 @@
+// arguments works in evalWithBindings (it does not interpose a function scope)
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var global = dbg.addDebuggee(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ var argc = frame.arguments.length;
+ assertEq(argc, 8);
+ assertEq(frame.evalWithBindings("arguments[prop]", {prop: "length"}).return, argc);
+ for (var i = 0; i < argc; i++)
+ assertEq(frame.evalWithBindings("arguments[i]", {i: i}).return, frame.arguments[i]);
+ hits++;
+};
+g.eval("function f() { debugger; }");
+g.eval("f(undefined, -0, NaN, '\uffff', Symbol('alpha'), Array.prototype, Math, f);");
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-04.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-04.js
new file mode 100644
index 0000000000..0de428c563
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-04.js
@@ -0,0 +1,17 @@
+// evalWithBindings works on non-top frames.
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var f1;
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ assertEq(frame.older.evalWithBindings("q + r", {r: 3}).return, 5);
+
+ // frame.older.older is in the same function as frame, but a different activation of it
+ assertEq(frame.older.older.evalWithBindings("q + r", {r: 3}).return, 6);
+ hits++;
+};
+
+g.eval("function f1(q) { if (q == 1) debugger; else f2(2); }");
+g.eval("function f2(arg) { var q = arg; f1(1); }");
+g.f1(3);
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-05.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-05.js
new file mode 100644
index 0000000000..7f9466fc33
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-05.js
@@ -0,0 +1,12 @@
+// evalWithBindings code can assign to the bindings.
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ assertEq(frame.evalWithBindings("for (i = 0; i < 5; i++) {} i;", {i: 10}).return, 5);
+ hits++;
+};
+
+g.eval("debugger;");
+assertEq("i" in g, false);
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-06.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-06.js
new file mode 100644
index 0000000000..42f1a42b5e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-06.js
@@ -0,0 +1,9 @@
+// In evalWithBindings code, assignment to any name not in the bindings works just as in eval.
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+ assertEq(frame.evalWithBindings("y = z; x = w;", {z: 2, w: 3}).return, 3);
+};
+g.eval("function f(x) { debugger; assertEq(x, 3); }");
+g.eval("var y = 0; f(0);");
+assertEq(g.y, 2);
diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-07.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-07.js
new file mode 100644
index 0000000000..a98b7650cb
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-07.js
@@ -0,0 +1,16 @@
+// var statements in strict evalWithBindings code behave like strict eval.
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ assertEq(frame.evalWithBindings("var i = a*a + b*b; i === 25;", {a: 3, b: 4}).return, true);
+ hits++;
+};
+g.eval("'use strict'; debugger;");
+assertEq(hits, 1);
+assertEq("i" in g, false);
+
+g.eval("function die() { throw fit; }");
+g.eval("Object.defineProperty(this, 'i', {get: die, set: die});");
+g.eval("'use strict'; debugger;");
+assertEq(hits, 2);
diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-08.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-08.js
new file mode 100644
index 0000000000..3785d5a2a1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-08.js
@@ -0,0 +1,13 @@
+// evalWithBindings ignores non-enumerable and non-own properties.
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ assertEq(frame.evalWithBindings("toString + constructor + length", []).return, 112233);
+ var obj = Object.create({constructor: "FAIL"}, {length: {value: "fail"}});
+ assertEq(frame.evalWithBindings("toString + constructor + length", obj).return, 112233);
+ hits++;
+};
+g.eval("function f() { var toString = 111111, constructor = 1111, length = 11; debugger; }");
+g.f();
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-09.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-09.js
new file mode 100644
index 0000000000..c697903193
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-09.js
@@ -0,0 +1,27 @@
+// evalWithBindings code is debuggee code, so it can trip the debugger. It nests!
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var f1;
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ f1 = frame;
+
+ // This trips the onExceptionUnwind hook.
+ var x = frame.evalWithBindings("wrongSpeling", {rightSpelling: 2}).throw;
+
+ assertEq(frame.evalWithBindings("exc.name", {exc: x}).return, "ReferenceError");
+ hits++;
+};
+dbg.onExceptionUnwind = function (frame, exc) {
+ assertEq(frame !== f1, true);
+
+ // f1's environment does not contain the binding for the first evalWithBindings call.
+ assertEq(f1.eval("rightSpelling").return, "dependent");
+ assertEq(f1.evalWithBindings("n + rightSpelling", {n: "in"}).return, "independent");
+
+ // frame's environment does contain the binding.
+ assertEq(frame.eval("rightSpelling").return, 2);
+ assertEq(frame.evalWithBindings("rightSpelling + three", {three: 3}).return, 5);
+ hits++;
+};
+g.eval("(function () { var rightSpelling = 'dependent'; debugger; })();");
diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-10.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-10.js
new file mode 100644
index 0000000000..005b5bf48b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-10.js
@@ -0,0 +1,16 @@
+// Direct eval code under evalWithBindings sees both the bindings and the enclosing scope.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ var code =
+ "assertEq(a, 1234);\n" +
+ "assertEq(b, null);\n" +
+ "assertEq(c, 'ok');\n";
+ assertEq(frame.evalWithBindings("eval(s)", {s: code, a: 1234}).return, undefined);
+ hits++;
+};
+g.eval("function f(b) { var c = 'ok'; debugger; }");
+g.f(null);
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-11.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-11.js
new file mode 100644
index 0000000000..4d17304053
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-11.js
@@ -0,0 +1,18 @@
+// var statements in non-strict evalWithBindings code behave like non-strict direct eval.
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var log;
+dbg.onDebuggerStatement = function (frame) {
+ log += 'd';
+ assertEq(frame.evalWithBindings("var i = v; 42;", { v: 'inner' }).return, 42);
+};
+
+g.i = 'outer';
+log = '';
+assertEq(g.eval('debugger; i;'), 'inner');
+assertEq(log, 'd');
+
+g.j = 'outer';
+log = '';
+assertEq(g.eval('debugger; j;'), 'outer');
+assertEq(log, 'd');
diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-12.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-12.js
new file mode 100644
index 0000000000..23e42ec0fe
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-12.js
@@ -0,0 +1,26 @@
+// evalWithBindings correctly handles optional custom url option
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var count = 0;
+
+function testUrl (options, expected) {
+ count++;
+ dbg.onDebuggerStatement = function (frame) {
+ dbg.onNewScript = function (script) {
+ dbg.onNewScript = undefined;
+ assertEq(script.url, expected);
+ count--;
+ };
+ frame.evalWithBindings("", {}, options);
+ };
+ g.eval("debugger;");
+}
+
+
+testUrl(undefined, "debugger eval code");
+testUrl(null, "debugger eval code");
+testUrl({ url: undefined }, "debugger eval code");
+testUrl({ url: null }, "null");
+testUrl({ url: 5 }, "5");
+testUrl({ url: "test" }, "test");
+assertEq(count, 0);
diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-13.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-13.js
new file mode 100644
index 0000000000..37cf30026f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-13.js
@@ -0,0 +1,24 @@
+// evalWithBindings correctly handles optional lineNumber option
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var count = 0;
+
+function testLineNumber (options, expected) {
+ count++;
+ dbg.onDebuggerStatement = function (frame) {
+ dbg.onNewScript = function (script) {
+ dbg.onNewScript = undefined;
+ assertEq(script.startLine, expected);
+ count--;
+ };
+ frame.evalWithBindings("", {}, options);
+ };
+ g.eval("debugger;");
+}
+
+
+testLineNumber(undefined, 1);
+testLineNumber({}, 1);
+testLineNumber({ lineNumber: undefined }, 1);
+testLineNumber({ lineNumber: 5 }, 5);
+assertEq(count, 0);
diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-14.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-14.js
new file mode 100644
index 0000000000..2875cd63b0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-14.js
@@ -0,0 +1,20 @@
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+// We're going to need to eval with a thrown exception from inside
+// onExceptionUnwind, so guard against infinite recursion.
+var exceptionCount = 0;
+dbg.onDebuggerStatement = function (frame) {
+ var x = frame.evalWithBindings("throw 'haha'", { rightSpelling: 4 }).throw;
+ assertEq(x, "haha");
+};
+dbg.onExceptionUnwind = function (frame, exc) {
+ ++exceptionCount;
+ if (exceptionCount == 1) {
+ var y = frame.evalWithBindings("throw 'haha2'", { rightSpelling: 2 }).throw;
+ assertEq(y, "haha2");
+ } else {
+ assertEq(frame.evalWithBindings("rightSpelling + three", { three : 3 }).return, 5);
+ }
+};
+g.eval("(function () { var rightSpelling = 7; debugger; })();");
+assertEq(exceptionCount, 2);
diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-15.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-15.js
new file mode 100644
index 0000000000..98aba48d14
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-15.js
@@ -0,0 +1,15 @@
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+dbg.onDebuggerStatement = function (frame) {
+ // The bindings object is unused but adds another environment on the
+ // environment chain. Make sure 'this' computes the right value in light of
+ // this.
+ assertEq(frame.evalWithBindings(`this === foo;`, { bar: 42 }).return, true);
+ assertEq(frame.evalWithBindings(`eval('this') === foo;`, { bar: 42 }).return, true);
+};
+
+g.eval(`
+var foo = { bar: function() { debugger; } };
+foo.bar();
+`);
diff --git a/js/src/jit-test/tests/debug/Frame-identity-01.js b/js/src/jit-test/tests/debug/Frame-identity-01.js
new file mode 100644
index 0000000000..de16e680be
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-identity-01.js
@@ -0,0 +1,19 @@
+// Check that {return:} resumption kills the current stack frame.
+
+var g = newGlobal({newCompartment: true});
+g.debuggeeGlobal = this;
+g.eval("(" + function () {
+ var dbg = new Debugger(debuggeeGlobal);
+ var prev = null;
+ dbg.onDebuggerStatement = function (frame) {
+ assertEq(frame === prev, false);
+ if (prev)
+ assertEq(prev.onStack, false);
+ prev = frame;
+ return {return: frame.arguments[0]};
+ };
+ } + ")();");
+
+function f(i) { debugger; }
+for (var i = 0; i < 10; i++)
+ assertEq(f(i), i);
diff --git a/js/src/jit-test/tests/debug/Frame-identity-02.js b/js/src/jit-test/tests/debug/Frame-identity-02.js
new file mode 100644
index 0000000000..d3f1b94b9c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-identity-02.js
@@ -0,0 +1,21 @@
+// Check that {throw:} resumption kills the current stack frame.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+g.debuggeeGlobal = this;
+g.eval("(" + function () {
+ var dbg = new Debugger(debuggeeGlobal);
+ var prev = null;
+ dbg.onDebuggerStatement = function (frame) {
+ assertEq(frame === prev, false);
+ if (prev)
+ assertEq(prev.onStack, false);
+ prev = frame;
+ return {throw: debuggeeGlobal.i};
+ };
+ } + ")();");
+
+function f() { debugger; }
+for (var i = 0; i < 10; i++)
+ assertThrowsValue(f, i);
diff --git a/js/src/jit-test/tests/debug/Frame-identity-03.js b/js/src/jit-test/tests/debug/Frame-identity-03.js
new file mode 100644
index 0000000000..fad88e01c4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-identity-03.js
@@ -0,0 +1,49 @@
+// Test that we create new Debugger.Frames and reuse old ones correctly with recursion.
+
+var g = newGlobal({newCompartment: true});
+g.debuggeeGlobal = this;
+g.eval("(" + function () {
+ function id(f) {
+ return ("id" in f) ? f.id : (f.id = nextid++);
+ }
+
+ var dbg = new Debugger(debuggeeGlobal);
+ dbg.onDebuggerStatement = function (frame) {
+ var a = [];
+ for (; frame; frame = frame.older)
+ a.push(frame);
+ var s = '';
+ while (a.length)
+ s += id(a.pop());
+ results.push(s);
+ };
+ } + ")();");
+
+function cons(a, b) {
+ debugger;
+ return [a, b];
+}
+
+function tree(n) {
+ if (n < 2)
+ return n;
+ return cons(tree(n - 1), tree(n - 2));
+}
+
+g.eval("results = []; nextid = 0;");
+debugger;
+assertEq(g.results.join(","), "0");
+assertEq(g.nextid, 1);
+
+g.eval("results = [];");
+tree(2);
+assertEq(g.results.join(","), "012"); // 0=global, 1=tree, 2=cons
+
+g.eval("results = []; nextid = 1;");
+tree(3);
+assertEq(g.results.join(","), "0123,014"); //0=global, 1=tree(3), 2=tree(2), 3=cons, 4=cons
+
+g.eval("results = []; nextid = 1;");
+tree(4);
+// 0=global, 1=tree(4), 2=tree(3), 3=tree(2), 4=cons, tree(1), 5=cons, 6=tree(2), 7=cons, 8=cons
+assertEq(g.results.join(","), "01234,0125,0167,018");
diff --git a/js/src/jit-test/tests/debug/Frame-identity-04.js b/js/src/jit-test/tests/debug/Frame-identity-04.js
new file mode 100644
index 0000000000..79d0a9f24c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-identity-04.js
@@ -0,0 +1,20 @@
+// Test that on-stack Debugger.Frames are not GC'd even if they are only reachable
+// from the js::Debugger::frames table.
+
+var g = newGlobal({newCompartment: true});
+g.eval("function f(n) { if (n) f(n - 1); debugger; }");
+var dbg = new Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ if (hits === 0) {
+ for (; frame; frame = frame.older)
+ frame.seen = true;
+ } else {
+ for (; frame; frame = frame.older)
+ assertEq(frame.seen, true);
+ }
+ gc();
+ hits++;
+};
+g.f(20);
+assertEq(hits, 21);
diff --git a/js/src/jit-test/tests/debug/Frame-identity-05.js b/js/src/jit-test/tests/debug/Frame-identity-05.js
new file mode 100644
index 0000000000..ee92cf9c41
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-identity-05.js
@@ -0,0 +1,20 @@
+// Suspended generators keep their associated Debugger.Frames gc-alive.
+
+var g = newGlobal({newCompartment: true});
+g.eval("function* f() { debugger; yield 1; debugger; }");
+var dbg = new Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ if (hits === 0)
+ frame.seen = true;
+ else
+ assertEq(frame.seen, true);
+ gc();
+ hits++;
+};
+var it = g.f();
+gc();
+assertEq(it.next().value, 1);
+gc();
+assertEq(it.next().done, true);
+assertEq(hits, 2);
diff --git a/js/src/jit-test/tests/debug/Frame-identity-06.js b/js/src/jit-test/tests/debug/Frame-identity-06.js
new file mode 100644
index 0000000000..04eb16b386
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-identity-06.js
@@ -0,0 +1,45 @@
+// Debugger.Frames for async functions are not GC'd while they're suspended.
+// The awaited promise keeps the generator alive, via its reaction lists.
+
+var g = newGlobal({newCompartment: true});
+g.eval(`
+ // Create a few promises.
+ var promises = [], resolvers = [];
+ for (let i = 0; i < 3; i++)
+ promises.push(new Promise(r => { resolvers.push(r); }));
+
+ async function f() {
+ debugger;
+ for (let p of promises) {
+ await p;
+ debugger;
+ }
+ }
+`);
+var dbg = new Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ if (hits === 0)
+ frame.seen = true;
+ else
+ assertEq(frame.seen, true);
+ hits++;
+};
+
+let done = false;
+g.f().then(_ => { done = true; });
+gc();
+drainJobQueue();
+gc();
+
+// Resolve the promises one by one.
+for (let [i, resolve] of g.resolvers.entries()) {
+ assertEq(hits, 1 + i);
+ assertEq(done, false);
+ resolve("x");
+ gc();
+ drainJobQueue();
+ gc();
+}
+assertEq(hits, 1 + g.resolvers.length);
+assertEq(done, true);
diff --git a/js/src/jit-test/tests/debug/Frame-identity-07.js b/js/src/jit-test/tests/debug/Frame-identity-07.js
new file mode 100644
index 0000000000..fa7a835992
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-identity-07.js
@@ -0,0 +1,52 @@
+// Distinct generator calls result in distinct Debugger.Frames.
+
+let g = newGlobal({newCompartment: true});
+g.eval(`
+ function* count(n) {
+ if (n > 0) {
+ for (let x of count(n - 1))
+ yield x;
+ yield n;
+ }
+ }
+`);
+
+let log = "";
+let dbg = Debugger(g);
+let nextId = 0;
+function mark(frame) {
+ if (frame.id === undefined)
+ frame.id = nextId++;
+}
+dbg.onEnterFrame = frame => {
+ mark(frame);
+ log += frame.id + "[";
+ frame.onPop = completion => {
+ mark(frame);
+ log += "]" + frame.id;
+ };
+};
+
+
+let j = 0;
+for (let k of g.count(5)) {
+ assertEq(k, ++j);
+ log += " ";
+}
+
+assertEq(log,
+ // Calling a generator function just returns a generator object
+ // without running the body at all; hence "0[]0". However, this call
+ // does evaluate default argument values, if any, so we do report an
+ // onEnterFrame / onPop for it.
+ "0[]0" +
+ // Demanding the first value from the top generator forces
+ // SpiderMonkey to create all five generator objects (the empty "n[]n"
+ // pairs) and then demand a value from them (the longer "n[...]n"
+ // substrings).
+ "0[1[]11[2[]22[3[]33[4[]44[5[]55[]5]4]3]2]1]0 " +
+ "0[1[2[3[4[]4]3]2]1]0 " +
+ "0[1[2[3[]3]2]1]0 " +
+ "0[1[2[]2]1]0 " +
+ "0[1[]1]0 " +
+ "0[]0");
diff --git a/js/src/jit-test/tests/debug/Frame-implementation-01.js b/js/src/jit-test/tests/debug/Frame-implementation-01.js
new file mode 100644
index 0000000000..5eaeea97fb
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-implementation-01.js
@@ -0,0 +1,45 @@
+// Debugger.Frames of all implementations.
+
+load(libdir + "jitopts.js");
+
+function testFrameImpl(jitopts, assertFrameImpl) {
+ if (!jitTogglesMatch(jitopts))
+ return;
+
+ withJitOptions(jitopts, function () {
+ var g = newGlobal({newCompartment: true});
+ var dbg = new Debugger;
+
+ g.toggle = function toggle(d) {
+ if (d) {
+ dbg.addDebuggee(g);
+ var frame = dbg.getNewestFrame();
+ // We only care about the f and g frames.
+ for (var i = 0; i < 2; i++) {
+ assertFrameImpl(frame);
+ frame = frame.older;
+ }
+ }
+ };
+
+ g.eval("" + function f(d) { g(d); });
+ g.eval("" + function g(d) { toggle(d); });
+
+ g.eval("(" + function test() {
+ for (var i = 0; i < 5; i++)
+ f(false);
+ f(true);
+ } + ")();");
+ });
+}
+
+[[Opts_BaselineEager,
+ function (f) { assertEq(f.implementation, "baseline"); }],
+ // Note that the Ion case *depends* on CCW scripted functions being opaque to
+ // Ion optimization and not deoptimizing the frames below the call to toggle.
+ [Opts_Ion2NoOffthreadCompilation,
+ function (f) { assertEq(f.implementation, "ion"); }],
+ [Opts_NoJits,
+ function (f) { assertEq(f.implementation, "interpreter"); }]].forEach(function ([opts, fn]) {
+ testFrameImpl(opts, fn);
+ });
diff --git a/js/src/jit-test/tests/debug/Frame-implementation-02.js b/js/src/jit-test/tests/debug/Frame-implementation-02.js
new file mode 100644
index 0000000000..ebcd65835f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-implementation-02.js
@@ -0,0 +1,51 @@
+// Test that Ion frames are invalidated by turning on Debugger. Invalidation
+// is unobservable, but we know that Ion scripts cannot handle Debugger
+// handlers, so we test for the handlers being called.
+
+load(libdir + "jitopts.js");
+
+if (!jitTogglesMatch(Opts_Ion2NoOffthreadCompilation))
+ quit();
+
+withJitOptions(Opts_Ion2NoOffthreadCompilation, function () {
+ var g = newGlobal({newCompartment: true});
+ var dbg = new Debugger;
+ var onPopExecuted = false;
+ var breakpointHit = false;
+
+ g.toggle = function toggle(d) {
+ if (d) {
+ dbg.addDebuggee(g);
+
+ var frame1 = dbg.getNewestFrame();
+ assertEq(frame1.implementation, "ion");
+ frame1.onPop = function () {
+ onPopExecuted = true;
+ };
+
+ var frame2 = frame1.older;
+ assertEq(frame2.implementation, "ion");
+ // Offset of |print(42 + 42)|
+ var offset = frame2.script.getLineOffsets(3)[0];
+ frame2.script.setBreakpoint(offset, { hit: function (fr) {
+ assertEq(fr.implementation != "ion", true);
+ breakpointHit = true;
+ }});
+ }
+ };
+
+ g.eval("" + function f(d) {
+ g(d);
+ print(42 + 42);
+ });
+ g.eval("" + function g(d) { toggle(d); });
+
+ g.eval("(" + function test() {
+ for (var i = 0; i < 5; i++)
+ f(false);
+ f(true);
+ } + ")();");
+
+ assertEq(onPopExecuted, true);
+ assertEq(breakpointHit, true);
+});
diff --git a/js/src/jit-test/tests/debug/Frame-newTargetEval-01.js b/js/src/jit-test/tests/debug/Frame-newTargetEval-01.js
new file mode 100644
index 0000000000..69978dcb8b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-newTargetEval-01.js
@@ -0,0 +1,40 @@
+// Test that new.target is acceptably usable in RematerializedFrames.
+
+gczeal(0);
+
+load(libdir + "jitopts.js");
+
+if (!jitTogglesMatch(Opts_Ion2NoOffthreadCompilation))
+ quit();
+
+withJitOptions(Opts_Ion2NoOffthreadCompilation, function () {
+ var g = newGlobal({newCompartment: true});
+ var dbg = new Debugger;
+
+ g.toggle = function toggle(d, expected) {
+ if (d) {
+ dbg.addDebuggee(g);
+
+ var frame = dbg.getNewestFrame();
+ assertEq(frame.implementation, "ion");
+ assertEq(frame.constructing, true);
+
+ // CONGRATS IF THIS FAILS! You, proud saviour, have made new.target parse
+ // in debug frame evals (presumably by hooking up static scope walks).
+ // Uncomment the assert below for efaust's undying gratitude.
+ // Note that we use .name here because of CCW nonsense.
+ assertEq(frame.eval('new.target').throw.unsafeDereference().name, "SyntaxError");
+ // assertEq(frame.eval('new.target').value.unsafeDereference(), expected);
+ }
+ };
+
+ g.eval("" + function f(d) { new g(d, g, 15); });
+
+ g.eval("" + function g(d, expected) { toggle(d, expected); });
+
+ g.eval("(" + function test() {
+ for (var i = 0; i < 5; i++)
+ f(false);
+ f(true);
+ } + ")();");
+});
diff --git a/js/src/jit-test/tests/debug/Frame-newTargetEval-02.js b/js/src/jit-test/tests/debug/Frame-newTargetEval-02.js
new file mode 100644
index 0000000000..89a20d4e39
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-newTargetEval-02.js
@@ -0,0 +1,43 @@
+// Test that new.target is acceptably usable in RematerializedFrames.
+
+gczeal(0);
+
+load(libdir + "jitopts.js");
+
+if (!jitTogglesMatch(Opts_Ion2NoOffthreadCompilation))
+ quit();
+
+withJitOptions(Opts_Ion2NoOffthreadCompilation, function () {
+ var g = newGlobal({newCompartment: true});
+ var dbg = new Debugger;
+
+ g.toggle = function toggle(d, expected) {
+ if (d) {
+ dbg.addDebuggee(g);
+
+ var frame = dbg.getNewestFrame();
+ assertEq(frame.implementation, "ion");
+
+ // the arrow function will not be constructing, even though it has a
+ // new.target value.
+ assertEq(frame.constructing, false);
+
+ // CONGRATS IF THIS FAILS! You, proud saviour, have made new.target parse
+ // in debug frame evals (presumably by hooking up static scope walks).
+ // Uncomment the assert below for efaust's undying gratitude.
+ // Note that we use .name here because of CCW nonsense.
+ assertEq(frame.eval('new.target').throw.unsafeDereference().name, "SyntaxError");
+ // assertEq(frame.eval('new.target').return.unsafeDereference(), expected);
+ }
+ };
+
+ g.eval("" + function f(d) { new g(d, g, 15); });
+
+ g.eval("" + function g(d, expected) { (() => toggle(d, expected))(); });
+
+ g.eval("(" + function test() {
+ for (var i = 0; i < 5; i++)
+ f(false);
+ f(true);
+ } + ")();");
+});
diff --git a/js/src/jit-test/tests/debug/Frame-newTargetOverflow-01.js b/js/src/jit-test/tests/debug/Frame-newTargetOverflow-01.js
new file mode 100644
index 0000000000..2e2b48f10c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-newTargetOverflow-01.js
@@ -0,0 +1,41 @@
+// Test that Ion frames are invalidated by turning on Debugger. Invalidation
+// is unobservable, but we know that Ion scripts cannot handle Debugger
+// handlers, so we test for the handlers being called.
+
+load(libdir + "jitopts.js");
+
+if (!jitTogglesMatch(Opts_Ion2NoOffthreadCompilation))
+ quit();
+
+// GCs can invalidate JIT code and cause this test to fail.
+if ('gczeal' in this)
+ gczeal(0);
+
+withJitOptions(Opts_Ion2NoOffthreadCompilation, function () {
+ var g = newGlobal({newCompartment: true});
+ var dbg = new Debugger;
+
+ g.toggle = function toggle(d) {
+ if (d) {
+ dbg.addDebuggee(g);
+
+ var frame = dbg.getNewestFrame();
+ assertEq(frame.implementation, "ion");
+ assertEq(frame.constructing, true);
+
+ // overflow args are read from the parent's frame
+ // ensure we have the right offset to read from those.
+ assertEq(frame.arguments[1], 15);
+ }
+ };
+
+ g.eval("" + function f(d) { new g(d, 15); });
+
+ g.eval("" + function g(d) { toggle(d); });
+
+ g.eval("(" + function test() {
+ for (var i = 0; i < 5; i++)
+ f(false);
+ f(true);
+ } + ")();");
+});
diff --git a/js/src/jit-test/tests/debug/Frame-offset-01.js b/js/src/jit-test/tests/debug/Frame-offset-01.js
new file mode 100644
index 0000000000..69f7f3ae5a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-offset-01.js
@@ -0,0 +1,11 @@
+// frame.offset throws if !frame.onStack.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var f;
+dbg.onDebuggerStatement = function (frame) { f = frame; };
+g.eval("debugger;");
+assertEq(f.onStack, false);
+assertThrowsInstanceOf(function () { f.offset; }, Error);
diff --git a/js/src/jit-test/tests/debug/Frame-offset-02.js b/js/src/jit-test/tests/debug/Frame-offset-02.js
new file mode 100644
index 0000000000..d11945cc5c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-offset-02.js
@@ -0,0 +1,16 @@
+// frame.offset gives different values at different points in a script.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var s = undefined, a = []
+dbg.onDebuggerStatement = function (frame) {
+ if (s === undefined)
+ s = frame.script;
+ else
+ assertEq(s, frame.script);
+ assertEq(frame.offset !== undefined, true);
+ assertEq(a.indexOf(frame.offset), -1);
+ a.push(frame.offset);
+};
+g.eval("debugger; debugger; debugger;");
+assertEq(a.length, 3);
diff --git a/js/src/jit-test/tests/debug/Frame-offset-03.js b/js/src/jit-test/tests/debug/Frame-offset-03.js
new file mode 100644
index 0000000000..76384541f0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-offset-03.js
@@ -0,0 +1,71 @@
+// frame.offset works in generators.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({ newCompartment: true });
+var dbg = new Debugger(g);
+g.eval(`
+function* f() {
+ yield 11;
+ yield 21;
+ yield 31;
+}
+`);
+
+const ranges = [];
+let script;
+let frame;
+dbg.onEnterFrame = function(f) {
+ if (frame) {
+ assertEq(f, frame);
+ assertEq(f.script, script);
+ } else {
+ frame = f;
+ script = f.script;
+ assertEq(frame instanceof Debugger.Frame, true);
+ assertEq(script instanceof Debugger.Script, true);
+ }
+
+ const range = [script.getOffsetMetadata(frame.offset).lineNumber, null];
+ ranges.push(range);
+ f.onPop = () => {
+ range[1] = script.getOffsetMetadata(frame.offset).lineNumber;
+ };
+};
+
+const it = g.f();
+
+assertEq(ranges.length, 1);
+assertEq(ranges[0][0], 2);
+assertEq(ranges[0][1], 2);
+assertEq(script.getOffsetMetadata(frame.offset).lineNumber, 2);
+
+it.next();
+
+assertEq(ranges.length, 2);
+assertEq(ranges[1][0], 2);
+assertEq(ranges[1][1], 3);
+assertEq(script.getOffsetMetadata(frame.offset).lineNumber, 3);
+
+it.next();
+
+assertEq(ranges.length, 3);
+assertEq(ranges[2][0], 3);
+assertEq(ranges[2][1], 4);
+assertEq(script.getOffsetMetadata(frame.offset).lineNumber, 4);
+
+it.next();
+
+assertEq(ranges.length, 4);
+assertEq(ranges[3][0], 4);
+assertEq(ranges[3][1], 5);
+assertEq(script.getOffsetMetadata(frame.offset).lineNumber, 5);
+
+it.next();
+
+assertEq(ranges.length, 5);
+assertEq(ranges[4][0], 5);
+assertEq(ranges[4][1], 5);
+
+// The frame has finished evaluating, so the callee is no longer accessible.
+assertThrowsInstanceOf(() => frame.offset, Error);
diff --git a/js/src/jit-test/tests/debug/Frame-offset-04.js b/js/src/jit-test/tests/debug/Frame-offset-04.js
new file mode 100644
index 0000000000..563dad4d84
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-offset-04.js
@@ -0,0 +1,50 @@
+// frame.offset works in async fns.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({ newCompartment: true });
+var dbg = new Debugger(g);
+g.eval(`
+async function f() {
+ await Promise.resolve();
+}
+`);
+
+const ranges = [];
+let script;
+let frame;
+dbg.onEnterFrame = function(f) {
+ if (frame) {
+ assertEq(f, frame);
+ assertEq(f.script, script);
+ } else {
+ frame = f;
+ script = f.script;
+ assertEq(frame instanceof Debugger.Frame, true);
+ assertEq(script instanceof Debugger.Script, true);
+ }
+
+ const range = [script.getOffsetMetadata(frame.offset).lineNumber, null];
+ ranges.push(range);
+ f.onPop = () => {
+ range[1] = script.getOffsetMetadata(frame.offset).lineNumber;
+ };
+};
+
+(async () => {
+ const promise = g.f();
+
+ assertEq(ranges.length, 1);
+ assertEq(ranges[0][0], 2);
+ assertEq(ranges[0][1], 3);
+ assertEq(script.getOffsetMetadata(frame.offset).lineNumber, 3);
+
+ await promise;
+
+ assertEq(ranges.length, 2);
+ assertEq(ranges[1][0], 3);
+ assertEq(ranges[1][1], 3);
+
+ // The frame has finished evaluating, so the callee is no longer accessible.
+ assertThrowsInstanceOf(() => frame.offset, Error);
+})();
diff --git a/js/src/jit-test/tests/debug/Frame-offset-05.js b/js/src/jit-test/tests/debug/Frame-offset-05.js
new file mode 100644
index 0000000000..e5b2242409
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-offset-05.js
@@ -0,0 +1,94 @@
+// frame.offset works in async generator fns.
+load(libdir + "asserts.js");
+
+var g = newGlobal({ newCompartment: true });
+var dbg = new Debugger(g);
+g.eval(`
+async function* f() {
+ yield 11;
+ yield 21;
+ yield 31;
+}
+`);
+
+const ranges = [];
+let script;
+let frame;
+dbg.onEnterFrame = function(f) {
+ if (frame) {
+ assertEq(f, frame);
+ assertEq(f.script, script);
+ } else {
+ frame = f;
+ script = f.script;
+ assertEq(frame instanceof Debugger.Frame, true);
+ assertEq(script instanceof Debugger.Script, true);
+ }
+
+ const range = [script.getOffsetMetadata(frame.offset).lineNumber, null];
+ ranges.push(range);
+ f.onPop = () => {
+ range[1] = script.getOffsetMetadata(frame.offset).lineNumber;
+ };
+};
+
+(async () => {
+ let promise;
+ const it = g.f();
+
+ assertEq(ranges.length, 1);
+ assertEq(ranges[0][0], 2);
+ assertEq(ranges[0][1], 2);
+ assertEq(script.getOffsetMetadata(frame.offset).lineNumber, 2);
+
+ promise = it.next();
+
+ assertEq(ranges.length, 2);
+ assertEq(ranges[1][0], 2);
+ assertEq(ranges[1][1], 3);
+ assertEq(script.getOffsetMetadata(frame.offset).lineNumber, 3);
+
+ await promise;
+
+ assertEq(ranges.length, 3);
+ assertEq(ranges[2][0], 3);
+ assertEq(ranges[2][1], 3);
+ assertEq(script.getOffsetMetadata(frame.offset).lineNumber, 3);
+
+ promise = it.next();
+
+ assertEq(ranges.length, 4);
+ assertEq(ranges[3][0], 3);
+ assertEq(ranges[3][1], 4);
+ assertEq(script.getOffsetMetadata(frame.offset).lineNumber, 4);
+
+ await promise;
+
+ assertEq(ranges.length, 5);
+ assertEq(ranges[4][0], 4);
+ assertEq(ranges[4][1], 4);
+ assertEq(script.getOffsetMetadata(frame.offset).lineNumber, 4);
+
+ promise = it.next();
+
+ assertEq(ranges.length, 6);
+ assertEq(ranges[5][0], 4);
+ assertEq(ranges[5][1], 5);
+ assertEq(script.getOffsetMetadata(frame.offset).lineNumber, 5);
+
+ await promise;
+
+ assertEq(ranges.length, 7);
+ assertEq(ranges[6][0], 5);
+ assertEq(ranges[6][1], 5);
+ assertEq(script.getOffsetMetadata(frame.offset).lineNumber, 5);
+
+ promise = it.next();
+
+ assertEq(ranges.length, 8);
+ assertEq(ranges[7][0], 5);
+ assertEq(ranges[7][1], 5);
+
+ // The frame has finished evaluating, so the callee is no longer accessible.
+ assertThrowsInstanceOf(() => frame.offset, Error);
+})();
diff --git a/js/src/jit-test/tests/debug/Frame-older-01.js b/js/src/jit-test/tests/debug/Frame-older-01.js
new file mode 100644
index 0000000000..750c6614b8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-older-01.js
@@ -0,0 +1,19 @@
+// Basic call chain.
+
+var g = newGlobal({newCompartment: true});
+var result = null;
+var dbg = new Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+ var a = [];
+ assertEq(frame === frame.older, false);
+ for (; frame; frame = frame.older)
+ a.push(frame.type === 'call' ? frame.callee.name : frame.type);
+ a.reverse();
+ result = a.join(", ");
+};
+
+g.eval("function first() { return second(); }");
+g.eval("function second() { return eval('third()'); }");
+g.eval("function third() { debugger; }");
+g.evaluate("first();");
+assertEq(result, "global, first, second, eval, third");
diff --git a/js/src/jit-test/tests/debug/Frame-older-02.js b/js/src/jit-test/tests/debug/Frame-older-02.js
new file mode 100644
index 0000000000..c5f772e46c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-older-02.js
@@ -0,0 +1,34 @@
+// An explicit async stack should interrupt a Debugger.Frame chain.
+
+var g = newGlobal({ newCompartment: true });
+var dbg = new Debugger(g);
+let done = false;
+dbg.onDebuggerStatement = function (frame) {
+ // The frame has no "older" frame because the explicit async stack
+ // attached to the async function takes priority over the real
+ // parent frame that is tracked in the frame iterator.
+ assertEq(!!frame.older, false);
+
+ done = true;
+};
+
+g.eval(`
+let draining = false;
+async function run() {
+ await Promise.resolve();
+
+ // Make sure that the test is running within "drainJobQueue()".
+ assertEq(draining, true);
+ debugger;
+}
+
+(function main() {
+ run();
+
+ // Force resumption of the "run" function.
+ draining = true;
+ drainJobQueue();
+ draining = false;
+})();
+`);
+assertEq(done, true);
diff --git a/js/src/jit-test/tests/debug/Frame-older-generators-01.js b/js/src/jit-test/tests/debug/Frame-older-generators-01.js
new file mode 100644
index 0000000000..f79f485faf
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-older-generators-01.js
@@ -0,0 +1,53 @@
+// Generator/async frames can be created by following .older.
+//
+// The goal here is to get some test coverage creating generator Frame objects
+// at some time other than when firing onEnterFrame. Here they're created after
+// the initial yield.
+
+let g = newGlobal({newCompartment: true});
+g.eval(`
+ function f() {
+ debugger;
+ }
+ function* gen() {
+ f();
+ yield 1;
+ f();
+ }
+ function* genDefaults(x=f()) {
+ f();
+ }
+ async function af() {
+ f();
+ await 1;
+ f();
+ }
+ async function afDefaults(x=f()) {
+ await 1;
+ f();
+ }
+`);
+
+function test(expected, code) {
+ let dbg = Debugger(g);
+ let hits = 0;
+ let genFrame = null;
+ dbg.onDebuggerStatement = frame => {
+ hits++;
+ assertEq(frame.callee.name, "f");
+ if (genFrame === null) {
+ genFrame = frame.older;
+ } else {
+ assertEq(frame.older, genFrame);
+ }
+ assertEq(genFrame.callee.name, expected);
+ };
+ g.eval(code);
+ assertEq(hits, 2);
+ dbg.removeDebuggee(g);
+}
+
+test("gen", "for (var x of gen()) {}");
+test("genDefaults", "for (var x of genDefaults()) {}");
+test("af", "af(); drainJobQueue();");
+test("afDefaults", "afDefaults(); drainJobQueue();")
diff --git a/js/src/jit-test/tests/debug/Frame-older-generators-02.js b/js/src/jit-test/tests/debug/Frame-older-generators-02.js
new file mode 100644
index 0000000000..f677bf64a8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-older-generators-02.js
@@ -0,0 +1,50 @@
+// Like Frame-older-generators-01.js, but attach the debugger on the fly.
+//
+// (That is, check that it works even if the debugger never received
+// onNewGenerator for the generator, because it wasn't attached at the time.)
+
+let g = newGlobal({newCompartment: true});
+g.eval(`
+ function f() {
+ attach();
+ debugger;
+ }
+ function* gen() {
+ f();
+ yield 1;
+ f();
+ }
+ async function af() {
+ f();
+ await 1;
+ f();
+ }
+`);
+
+function test(expected, code) {
+ let dbg;
+ let hits = 0;
+ let genFrame = null;
+
+ g.attach = () => {
+ if (dbg === undefined) {
+ dbg = Debugger(g);
+ dbg.onDebuggerStatement = frame => {
+ hits++;
+ assertEq(frame.callee.name, "f");
+ if (genFrame === null) {
+ genFrame = frame.older;
+ } else {
+ assertEq(frame.older, genFrame);
+ }
+ assertEq(genFrame.callee.name, expected);
+ };
+ }
+ };
+ g.eval(code);
+ assertEq(hits, 2);
+ dbg.removeDebuggee(g);
+}
+
+test("gen", "for (var x of gen()) {}");
+test("af", "af(); drainJobQueue();");
diff --git a/js/src/jit-test/tests/debug/Frame-older-generators-03.js b/js/src/jit-test/tests/debug/Frame-older-generators-03.js
new file mode 100644
index 0000000000..128898b206
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-older-generators-03.js
@@ -0,0 +1,24 @@
+// Test that Debugger.Frame.prototype.older works on suspended generators.
+
+load(libdir + "asserts.js");
+
+const g = newGlobal({ newCompartment: true });
+const dbg = new Debugger(g);
+
+g.eval(`
+function* f() {}
+`);
+
+let frame;
+dbg.onEnterFrame = f => {
+ frame = f;
+ dbg.onEnterFrame = undefined;
+};
+
+const it = g.f();
+
+assertEq(frame.older, null);
+
+it.next();
+
+assertThrowsInstanceOf(() => frame.older, Error);
diff --git a/js/src/jit-test/tests/debug/Frame-older-generators-04.js b/js/src/jit-test/tests/debug/Frame-older-generators-04.js
new file mode 100644
index 0000000000..405e08bed5
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-older-generators-04.js
@@ -0,0 +1,28 @@
+// Test that Debugger.Frame.prototype.older works on suspended async functions.
+
+load(libdir + "asserts.js");
+
+const g = newGlobal({ newCompartment: true });
+const dbg = new Debugger(g);
+
+g.eval(`
+async function f() {
+ await Promise.resolve();
+}
+`);
+
+let frame;
+dbg.onEnterFrame = f => {
+ frame = f;
+ dbg.onEnterFrame = undefined;
+};
+
+(async () => {
+ const promise = g.f();
+
+ assertEq(frame.older, null);
+
+ await promise;
+
+ assertThrowsInstanceOf(() => frame.older, Error);
+})();
diff --git a/js/src/jit-test/tests/debug/Frame-older-generators-05.js b/js/src/jit-test/tests/debug/Frame-older-generators-05.js
new file mode 100644
index 0000000000..61267a8443
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-older-generators-05.js
@@ -0,0 +1,32 @@
+// Test that Debugger.Frame.prototype.older works on suspended async generators.
+
+load(libdir + "asserts.js");
+
+const g = newGlobal({ newCompartment: true });
+const dbg = new Debugger(g);
+
+g.eval(`
+async function* f() {
+ await Promise.resolve();
+}
+`);
+
+let frame;
+dbg.onEnterFrame = f => {
+ frame = f;
+ dbg.onEnterFrame = undefined;
+};
+
+(async () => {
+ const it = g.f();
+
+ assertEq(frame.older, null);
+
+ const promise = it.next();
+
+ assertEq(frame.older, null);
+
+ await promise;
+
+ assertThrowsInstanceOf(() => frame.older, Error);
+})();
diff --git a/js/src/jit-test/tests/debug/Frame-olderSavedFrame-01.js b/js/src/jit-test/tests/debug/Frame-olderSavedFrame-01.js
new file mode 100644
index 0000000000..018e6130ea
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-olderSavedFrame-01.js
@@ -0,0 +1,40 @@
+// An explicit async stack should be available via olderSavedFrame.
+// This test makes sure that "main" shows up as an async saved frame
+// instead of "older", even though "drainJobQueue()" is running inside of
+// "main()" directly.
+
+var g = newGlobal({ newCompartment: true });
+var dbg = new Debugger(g);
+let done = false;
+dbg.onDebuggerStatement = function (frame) {
+ // This frame has an "olderSavedFrame" because "run()" is an async function
+ // and those have explicit async stacks attached to them.
+ const parent = frame.olderSavedFrame;
+ assertEq(typeof parent.source, "string");
+ assertEq(parent.line, 12);
+ assertEq(parent.column, 3);
+ assertEq(parent.asyncCause, "async");
+ assertEq(parent.functionDisplayName, "main");
+ done = true;
+};
+
+g.eval(`
+let draining = false;
+async function run() {
+ await Promise.resolve();
+
+ // Make sure that the test is running within "drainJobQueue()".
+ assertEq(draining, true);
+ debugger;
+}
+
+(function main() {
+ run();
+
+ // Force resumption of the "run" function.
+ draining = true;
+ drainJobQueue();
+ draining = false;
+})();
+`);
+assertEq(done, true);
diff --git a/js/src/jit-test/tests/debug/Frame-olderSavedFrame-02.js b/js/src/jit-test/tests/debug/Frame-olderSavedFrame-02.js
new file mode 100644
index 0000000000..ae774b8784
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-olderSavedFrame-02.js
@@ -0,0 +1,32 @@
+// An explicit async stack should be available via olderSavedFrame.
+// This test makes sure that "main" shows up as an async saved frame properly
+// when the promise is resumed normally outside "main()".
+
+var g = newGlobal({ newCompartment: true });
+var dbg = new Debugger(g);
+let done = false;
+dbg.onDebuggerStatement = function (frame) {
+ // This frame has an "olderSavedFrame" because "run()" is an async function
+ // and those have explicit async stacks attached to them.
+ const parent = frame.olderSavedFrame;
+ assertEq(typeof parent.source, "string");
+ assertEq(parent.line, 9);
+ assertEq(parent.column, 3);
+ assertEq(parent.asyncCause, "async");
+ assertEq(parent.functionDisplayName, "main");
+ done = true;
+};
+
+g.eval(`
+let draining = false;
+async function run() {
+ await Promise.resolve();
+ debugger;
+}
+
+(function main() {
+ run();
+})();
+drainJobQueue();
+`);
+assertEq(done, true);
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-01.js b/js/src/jit-test/tests/debug/Frame-onPop-01.js
new file mode 100644
index 0000000000..2b9f227e3d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-01.js
@@ -0,0 +1,29 @@
+// When multiple frames have onPop handlers, they are called in the correct order.
+var g = newGlobal({newCompartment: true});
+g.eval("function f() { debugger; }");
+g.eval("function g() { f(); }");
+g.eval("function h() { g(); }");
+g.eval("function i() { h(); }");
+
+var dbg = new Debugger(g);
+var log;
+function logger(frame, mark) {
+ return function (completion) {
+ assertEq(this, frame);
+ assertEq('return' in completion, true);
+ log += mark;
+ };
+}
+dbg.onEnterFrame = function handleEnter(f) {
+ log += "(" + f.callee.name;
+ // Note that this establishes a distinct function object as each
+ // frame's onPop handler. Thus, a pass proves that each frame is
+ // remembering its handler separately.
+ f.onPop = logger(f, f.callee.name + ")");
+};
+dbg.onDebuggerStatement = function handleDebugger(f) {
+ log += 'd';
+};
+log = '';
+g.i();
+assertEq(log, "(i(h(g(fdf)g)h)i)");
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-02.js b/js/src/jit-test/tests/debug/Frame-onPop-02.js
new file mode 100644
index 0000000000..11179f64b9
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-02.js
@@ -0,0 +1,20 @@
+// Clearing a frame's onPop handler works.
+var g = newGlobal({newCompartment: true});
+g.eval("function f() { debugger; }");
+var dbg = new Debugger(g);
+
+var log;
+dbg.onEnterFrame = function handleEnter(f) {
+ log += "(";
+ f.onPop = function handlePop() {
+ assertEq("handlePop was called", "handlePop should never be called");
+ };
+};
+dbg.onDebuggerStatement = function handleDebugger(f) {
+ log += "d";
+ assertEq(typeof f.onPop, "function");
+ f.onPop = undefined;
+};
+log = '';
+g.f();
+assertEq(log, "(d");
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-03.js b/js/src/jit-test/tests/debug/Frame-onPop-03.js
new file mode 100644
index 0000000000..e2a2034921
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-03.js
@@ -0,0 +1,32 @@
+// When an exception is propagated out of multiple frames, their onPop
+// and onExceptionUnwind handlers are called in the correct order.
+var g = newGlobal({newCompartment: true});
+g.eval("function f() { throw 'mud'; }");
+g.eval("function g() { f(); }");
+g.eval("function h() { g(); }");
+g.eval("function i() { h(); }");
+
+var dbg = new Debugger(g);
+var log;
+function makePopHandler(label) {
+ return function handlePop(completion) {
+ log += label;
+ assertEq(completion.throw, "mud");
+ };
+}
+dbg.onEnterFrame = function handleEnter(f) {
+ log += "(" + f.callee.name;
+ f.onPop = makePopHandler(")" + f.callee.name);
+};
+dbg.onExceptionUnwind = function handleExceptionUnwind(f, x) {
+ assertEq(x, 'mud');
+ log += "u" + f.callee.name;
+};
+log = '';
+try {
+ g.i();
+} catch (x) {
+ log += 'c';
+ assertEq(x, "mud");
+}
+assertEq(log, "(i(h(g(fuf)fug)guh)hui)ic");
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-04.js b/js/src/jit-test/tests/debug/Frame-onPop-04.js
new file mode 100644
index 0000000000..3915fce278
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-04.js
@@ -0,0 +1,30 @@
+// When a termination is propagated out of multiple frames, their onPop
+// handlers are called in the correct order, and no onExceptionUnwind
+// handlers are called.
+var g = newGlobal({newCompartment: true});
+g.eval("function f() { terminate(); }");
+g.eval("function g() { f(); }");
+g.eval("function h() { g(); }");
+g.eval("function i() { h(); }");
+
+var dbg = new Debugger(g);
+var log;
+var count = 0;
+function makePopHandler(label, resumption) {
+ return function handlePop(completion) {
+ log += label;
+ assertEq(completion, null);
+ return resumption;
+ };
+}
+dbg.onEnterFrame = function handleEnter(f) {
+ log += "(" + f.callee.name;
+ f.onPop = makePopHandler(f.callee.name + ")",
+ count++ == 0 ? { return: 'king' } : undefined);
+};
+dbg.onExceptionUnwind = function handleExceptionUnwind(f, x) {
+ log += 'u';
+};
+log = '';
+assertEq(g.i(), 'king');
+assertEq(log, "(i(h(g(ff)g)h)i)");
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-05.js b/js/src/jit-test/tests/debug/Frame-onPop-05.js
new file mode 100644
index 0000000000..6e73c0e1ce
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-05.js
@@ -0,0 +1,25 @@
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+g.debuggerGlobal = this;
+var log;
+
+dbg.onEnterFrame = function handleEnter(f) {
+ log += '(';
+ f.onPop = function handlePop(c) {
+ log += ')';
+ assertEq(c.throw, "election");
+ };
+};
+dbg.onExceptionUnwind = function handleExceptionUnwind(f, x) {
+ log += 'u';
+ assertEq(x, "election");
+};
+
+log = '';
+try {
+ g.eval("try { throw 'election'; } finally { debuggerGlobal.log += 'f'; }");
+} catch (x) {
+ log += 'c';
+ assertEq(x, 'election');
+}
+assertEq(log, '(ufu)c');
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-06.js b/js/src/jit-test/tests/debug/Frame-onPop-06.js
new file mode 100644
index 0000000000..05e8474564
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-06.js
@@ -0,0 +1,20 @@
+// dbg.getNewestFrame in an onPop handler returns the frame being popped.
+
+var g = newGlobal({newCompartment: true});
+g.eval("function f() { debugger; }");
+g.eval("function g() { f(); }");
+g.eval("function h() { g(); }");
+g.eval("function i() { h(); }");
+
+var dbg = new Debugger(g);
+var log;
+dbg.onEnterFrame = function handleEnter(f) {
+ log += "(" + f.callee.name;
+ f.onPop = function handlePop(c) {
+ log += ")" + f.callee.name;
+ assertEq(dbg.getNewestFrame(), this);
+ };
+};
+log = '';
+g.i();
+assertEq(log, "(i(h(g(f)f)g)h)i");
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-08.js b/js/src/jit-test/tests/debug/Frame-onPop-08.js
new file mode 100644
index 0000000000..2d7f63576d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-08.js
@@ -0,0 +1,16 @@
+// Setting onPop handlers from a 'debugger' statement handler works.
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var log;
+
+dbg.onDebuggerStatement = function handleDebugger(frame) {
+ assertEq(frame.type, "eval");
+ log += 'd';
+ frame.onPop = function handlePop(c) {
+ log += ')';
+ };
+};
+
+log = '';
+g.eval('debugger;');
+assertEq(log, 'd)');
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-09.js b/js/src/jit-test/tests/debug/Frame-onPop-09.js
new file mode 100644
index 0000000000..2147a76905
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-09.js
@@ -0,0 +1,23 @@
+// Setting onPop handlers from an onExceptionUnwind handler works.
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var log;
+
+dbg.onExceptionUnwind = function handleUnwind(frame) {
+ log += 'u';
+ assertEq(frame.type, "eval");
+ frame.onPop = function handleCallPop(c) {
+ log += ')';
+ assertEq(c.throw, 'up');
+ };
+};
+
+log = "";
+try {
+ g.eval("throw 'up';");
+ log += '-';
+} catch (x) {
+ log += 'c';
+ assertEq(x, 'up');
+}
+assertEq(log, 'u)c');
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-10.js b/js/src/jit-test/tests/debug/Frame-onPop-10.js
new file mode 100644
index 0000000000..091bf076e4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-10.js
@@ -0,0 +1,22 @@
+// Setting onPop handlers from an onStep handler works.
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var log;
+
+dbg.onDebuggerStatement = function handleDebugger(frame) {
+ log += 'd';
+ assertEq(frame.type, "eval");
+ frame.onStep = function handleStep() {
+ log += 's';
+ this.onStep = undefined;
+ this.onPop = function handlePop() {
+ log += ')';
+ };
+ };
+};
+
+log = "";
+g.flag = false;
+g.eval('debugger; flag = true');
+assertEq(log, 'ds)');
+assertEq(g.flag, true);
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-11.js b/js/src/jit-test/tests/debug/Frame-onPop-11.js
new file mode 100644
index 0000000000..36b58cd6dc
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-11.js
@@ -0,0 +1,23 @@
+// Setting onPop handlers from breakpoint handlers works.
+var g = newGlobal({newCompartment: true});
+g.eval("function f(){ return 'to normalcy'; }");
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+var log;
+
+// Set a breakpoint at the start of g.f
+var gf = gw.makeDebuggeeValue(g.f);
+var fStartOffset = gf.script.getLineOffsets(gf.script.startLine)[0];
+gf.script.setBreakpoint(fStartOffset, {
+ hit: function handleHit(frame) {
+ log += 'b';
+ frame.onPop = function handlePop(c) {
+ log += ')';
+ assertEq(c.return, "to normalcy");
+ };
+ }
+});
+
+log = "";
+assertEq(g.f(), "to normalcy");
+assertEq(log, "b)");
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-12.js b/js/src/jit-test/tests/debug/Frame-onPop-12.js
new file mode 100644
index 0000000000..6fef64d635
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-12.js
@@ -0,0 +1,21 @@
+// Setting an onPop handler from an onPop handler doesn't throw, but the
+// new handler doesn't fire.
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var log;
+
+dbg.onDebuggerStatement = function handleDebugger(frame) {
+ log += 'd';
+ assertEq(frame.type, "eval");
+ frame.onPop = function firstHandlePop(c) {
+ log +=')';
+ assertEq(c.return, 'on investment');
+ this.onPop = function secondHandlePop(c) {
+ assertEq("secondHandlePop was called", "secondHandlePop should never be called");
+ };
+ };
+};
+
+log = "";
+g.eval("debugger; 'on investment';");
+assertEq(log, 'd)');
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-13.js b/js/src/jit-test/tests/debug/Frame-onPop-13.js
new file mode 100644
index 0000000000..2fe0f79ddf
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-13.js
@@ -0,0 +1,37 @@
+// One can set onPop handlers on some frames but not others.
+var g = newGlobal({newCompartment: true});
+g.eval("function f(n) { if (n > 0) f(n-1); else debugger; }");
+var dbg = new Debugger(g);
+var log;
+
+Debugger.Frame.prototype.nthOlder = function nthOlder(n) {
+ var f = this;
+ while (n-- > 0)
+ f = f.older;
+ return f;
+};
+
+dbg.onEnterFrame = function handleEnter(f) {
+ log += "(" + f.arguments[0];
+};
+
+function makePopHandler(n) {
+ return function handlePop(c) {
+ log += ")" + this.arguments[0];
+ assertEq(this.arguments[0], n);
+ };
+}
+
+dbg.onDebuggerStatement = function handleDebugger(f) {
+ // Set onPop handers on some frames, and leave others alone. Vary the
+ // spacing.
+ f.nthOlder(2).onPop = makePopHandler(2);
+ f.nthOlder(3).onPop = makePopHandler(3);
+ f.nthOlder(5).onPop = makePopHandler(5);
+ f.nthOlder(8).onPop = makePopHandler(8);
+ f.nthOlder(13).onPop = makePopHandler(13);
+};
+
+log = '';
+g.f(20);
+assertEq(log, "(20(19(18(17(16(15(14(13(12(11(10(9(8(7(6(5(4(3(2(1(0)2)3)5)8)13");
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-14.js b/js/src/jit-test/tests/debug/Frame-onPop-14.js
new file mode 100644
index 0000000000..671f63d4bb
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-14.js
@@ -0,0 +1,25 @@
+// A frame's onPop handler is called only once, even if it is for a function
+// called from a loop.
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var log;
+
+var count;
+dbg.onDebuggerStatement = function handleDebug(frame) {
+ log += 'd';
+ assertEq(frame.type, "call");
+ count++;
+ if (count == 10) {
+ frame.onPop = function handlePop(c) {
+ log += ')' + this.arguments[0];
+ assertEq(c.return, "snifter");
+ };
+ }
+};
+
+g.eval("function f(n) { debugger; return 'snifter'; }");
+log = '';
+count = 0;
+g.eval("for (i = 0; i < 20; i++) f(i);");
+assertEq(count, 20);
+assertEq(log, "dddddddddd)9dddddddddd");
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-15.js b/js/src/jit-test/tests/debug/Frame-onPop-15.js
new file mode 100644
index 0000000000..6b76723cb2
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-15.js
@@ -0,0 +1,31 @@
+// Each resumption of a generator gets the same Frame; its onPop handler
+// fires each time the generator yields.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var log;
+
+var seenFrame = null;
+dbg.onDebuggerStatement = function handleDebugger(frame) {
+ log += 'd';
+ assertEq(frame.type, "call");
+
+ if (seenFrame === null) {
+ seenFrame = frame;
+ } else {
+ assertEq(seenFrame, frame);
+ }
+
+ let i = frame.eval('i').return;
+ if (i % 3 == 0) {
+ frame.onPop = function handlePop(c) {
+ assertEq(this, seenFrame);
+ log += ')' + i;
+ };
+ }
+};
+
+g.eval("function* g() { for (var i = 0; i < 10; i++) { debugger; yield i; } }");
+log ='';
+assertEq(g.eval("var t = 0; for (j of g()) t += j; t;"), 45);
+assertEq(log, "d)0d)0d)0d)3d)3d)3d)6d)6d)6d)9)9");
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-16.js b/js/src/jit-test/tests/debug/Frame-onPop-16.js
new file mode 100644
index 0000000000..dfb765d1e7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-16.js
@@ -0,0 +1,18 @@
+// onPop handlers fire even on frames that make tail calls.
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var log;
+
+g.eval('function f(n) { if (n > 0) f(n-1); else debugger; }');
+
+dbg.onEnterFrame = function handleEnter(frame) {
+ log += '(';
+ frame.onPop = function handlePop(c) {
+ log += ')';
+ assertEq(typeof c == "object" && 'return' in c, true);
+ };
+};
+
+log = '';
+g.f(10);
+assertEq(log, "((((((((((()))))))))))");
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-17.js b/js/src/jit-test/tests/debug/Frame-onPop-17.js
new file mode 100644
index 0000000000..f37bcb3cd5
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-17.js
@@ -0,0 +1,41 @@
+// onPop surfaces.
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+// Assigning a bogus value to Debugger.Frame.prototype.onPop raises a TypeError.
+function test(badValue) {
+ print("store " + JSON.stringify(badValue) + " in Debugger.Frame.prototype.onPop");
+
+ var log;
+ dbg.onDebuggerStatement = function handleDebugger(frame) {
+ log += "d";
+ assertThrowsInstanceOf(function () { frame.onPop = badValue; }, TypeError);
+ };
+
+ log = "";
+ g.eval("debugger");
+ assertEq(log, "d");
+}
+
+test(null);
+test(false);
+test(1);
+test("stringy");
+test(Symbol("symbolic"));
+test({});
+test([]);
+
+// Getting and setting the prototype's onPop is an error.
+assertThrowsInstanceOf(function () { Debugger.Frame.prototype.onPop; }, TypeError);
+assertThrowsInstanceOf(
+ function () { Debugger.Frame.prototype.onPop = function () {}; },
+ TypeError);
+
+// The getters and setters correctly check the type of their 'this' argument.
+var descriptor = Object.getOwnPropertyDescriptor(Debugger.Frame.prototype, 'onPop');
+assertEq(descriptor.configurable, true);
+assertEq(descriptor.enumerable, false);
+assertThrowsInstanceOf(function () { descriptor.get.call({}); }, TypeError);
+assertThrowsInstanceOf(function () { descriptor.set.call({}, function() {}); }, TypeError);
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-18.js b/js/src/jit-test/tests/debug/Frame-onPop-18.js
new file mode 100644
index 0000000000..2fd37c27bb
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-18.js
@@ -0,0 +1,22 @@
+// A garbage collection in the debugger compartment does not disturb onPop
+// handlers.
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var log;
+
+dbg.onEnterFrame = function handleEnter(frame) {
+ log += '(';
+ frame.onPop = function handlePop(completion) {
+ log += ')';
+ };
+};
+
+dbg.onDebuggerStatement = function handleDebugger (frame) {
+ log += 'd';
+ // GC in the debugger's compartment only.
+ gc(dbg);
+};
+
+log = '';
+assertEq(g.eval('debugger; 42;'), 42);
+assertEq(log, '(d)');
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-19.js b/js/src/jit-test/tests/debug/Frame-onPop-19.js
new file mode 100644
index 0000000000..27c8a9b131
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-19.js
@@ -0,0 +1,16 @@
+// A garbage collection in the debuggee compartment does not disturb onPop
+// handlers.
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var log;
+
+dbg.onEnterFrame = function handleEnter(frame) {
+ log += '(';
+ frame.onPop = function handlePop(completion) {
+ log += ')';
+ };
+};
+
+log = '';
+assertEq(g.eval('gc(this); 42;'), 42);
+assertEq(log, '()');
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-20.js b/js/src/jit-test/tests/debug/Frame-onPop-20.js
new file mode 100644
index 0000000000..2702c48048
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-20.js
@@ -0,0 +1,15 @@
+// A global garbage collection does not disturb onPop handlers.
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var log;
+
+dbg.onEnterFrame = function handleEnter(frame) {
+ log += '(';
+ frame.onPop = function handlePop(completion) {
+ log += ')';
+ };
+};
+
+log = '';
+assertEq(g.eval('gc(); 42;'), 42);
+assertEq(log, '()');
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-21.js b/js/src/jit-test/tests/debug/Frame-onPop-21.js
new file mode 100644
index 0000000000..c7d52f974c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-21.js
@@ -0,0 +1,30 @@
+// frame.eval works from an onPop handler.
+var g = newGlobal({newCompartment: true});
+g.eval('function f(a,b) { var x = "entablature", y; debugger; return x+y+a+b; }');
+
+var dbg = new Debugger(g);
+var log;
+
+dbg.onDebuggerStatement = function handleDebugger(frame) {
+ log += 'd';
+ frame.onPop = handlePop;
+};
+
+function handlePop(c) {
+ log += ')';
+
+ // Arguments must be live.
+ assertEq(this.eval('a').return, 'frieze');
+ assertEq(this.eval('b = "architrave"').return, 'architrave');
+ assertEq(this.eval('arguments[1]').return, 'architrave');
+ assertEq(this.eval('b').return, 'architrave');
+
+ // function-scope variables must be live.
+ assertEq(this.eval('x').return, 'entablature');
+ assertEq(this.eval('y = "cornice"').return, 'cornice');
+ assertEq(this.eval('y').return, 'cornice');
+}
+
+log = '';
+g.eval('f("frieze", "stylobate")');
+assertEq(log, 'd)');
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-23.js b/js/src/jit-test/tests/debug/Frame-onPop-23.js
new file mode 100644
index 0000000000..894250ef5b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-23.js
@@ -0,0 +1,34 @@
+// Check that the line number reported at an onPop stop makes sense,
+// even when it happens on an "artificial" instruction.
+
+var g = newGlobal({newCompartment: true});
+
+// This bit of code arranges for the line number of the "artificial"
+// instruction to be something nonsensical -- the middle of a loop
+// which cannot be entered.
+g.eval(`function f() {
+ debugger; // +0
+ if(false) { // +1
+ for(var b=0; b<0; b++) { // +2
+ c = 2; // +3
+ } // +4
+ } // +5
+} // +6
+`);
+
+var dbg = Debugger(g);
+
+let debugLine;
+let foundLine;
+
+dbg.onDebuggerStatement = function(frame) {
+ debugLine = frame.script.getOffsetLocation(frame.offset).lineNumber;
+ frame.onPop = function(c) {
+ foundLine = this.script.getOffsetLocation(this.offset).lineNumber;
+ };
+};
+
+g.eval("f();\n");
+
+// The stop should happen on the closing brace of the function.
+assertEq(foundLine == debugLine + 6, true);
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-after-debugger-return.js b/js/src/jit-test/tests/debug/Frame-onPop-after-debugger-return.js
new file mode 100644
index 0000000000..ddd117bf83
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-after-debugger-return.js
@@ -0,0 +1,11 @@
+// Bug 744730.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+dbg.onDebuggerStatement = function (f) { return {return: 1234}; };
+var hit = false;
+dbg.onEnterFrame = function (f) {
+ f.onPop = function () { hit = true};
+};
+g.eval("debugger;");
+assertEq(hit, true);
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-assign-function.js b/js/src/jit-test/tests/debug/Frame-onPop-assign-function.js
new file mode 100644
index 0000000000..1994248d90
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-assign-function.js
@@ -0,0 +1,48 @@
+// Changing onPop while the function is dead is allowed.
+
+load(libdir + "asserts.js");
+
+const g = newGlobal({ newCompartment: true });
+const dbg = new Debugger(g);
+
+let steps = new Set();
+dbg.onDebuggerStatement = function(frame) {
+ // Setting 'onPop' while alive is allowed.
+ steps.add("debugger 1");
+ assertEq(frame.onStack, true);
+ frame.onPop = function() {
+ steps.add("onpop 1");
+ };
+
+ dbg.onDebuggerStatement = function() {
+ // Clear the 'onPop' while dead.
+ steps.add("debugger 2");
+ assertEq(frame.onStack, false);
+
+ // Clearing 'onPop' while dead is allowed.
+ frame.onPop = undefined;
+
+ // Setting 'onPop' while dead is allowed.
+ frame.onPop = function() {
+ steps.add("onpop 2");
+ };
+ };
+};
+
+g.eval(
+ `
+ function myGen() {
+ debugger;
+ }
+
+ const g = myGen();
+
+ debugger;
+ `
+);
+
+assertDeepEq(Array.from(steps), [
+ "debugger 1",
+ "onpop 1",
+ "debugger 2",
+]);
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-assign-generator.js b/js/src/jit-test/tests/debug/Frame-onPop-assign-generator.js
new file mode 100644
index 0000000000..14a7a2a162
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-assign-generator.js
@@ -0,0 +1,68 @@
+// Changing onPop while the generator is suspended/dead is allowed.
+
+load(libdir + "asserts.js");
+
+const g = newGlobal({ newCompartment: true });
+const dbg = new Debugger(g);
+
+let steps = new Set();
+dbg.onDebuggerStatement = function(frame) {
+ // Setting 'onStep' while alive is allowed.
+ steps.add("debugger 1");
+ assertEq(frame.onStack, true);
+ frame.onPop = function() {
+ steps.add("onpop 1");
+ };
+
+ dbg.onDebuggerStatement = function() {
+ // Clear the 'onPop' while suspended.
+ steps.add("debugger 2");
+ assertEq(frame.onStack, false);
+
+ // Clearing 'onPop' while suspended is allowed.
+ frame.onPop = undefined;
+
+ // Setting 'onPop' while suspended is allowed.
+ frame.onPop = function() {
+ steps.add("onpop 2");
+ };
+
+ dbg.onDebuggerStatement = function() {
+ steps.add("debugger 3");
+ assertEq(frame.onStack, false);
+
+ // Clearing 'onPop' while dead is allowed.
+ frame.onPop = undefined;
+
+ // Setting 'onPop' while dead is allowed.
+ frame.onPop = function() {
+ steps.add("onpop 3");
+ };
+ };
+ };
+};
+
+g.eval(
+ `
+ function* myGen() {
+ debugger;
+ yield;
+ }
+
+ const g = myGen();
+ g.next();
+
+ debugger;
+ g.next();
+
+ debugger;
+ `
+);
+
+assertDeepEq(Array.from(steps), [
+ "debugger 1",
+ "onpop 1",
+ "debugger 2",
+ "onpop 2",
+ "debugger 3",
+]);
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-async-01.js b/js/src/jit-test/tests/debug/Frame-onPop-async-01.js
new file mode 100644
index 0000000000..8d491f3f7f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-async-01.js
@@ -0,0 +1,39 @@
+// When an async function awaits, if Frame.onPop processes microtasks,
+// the async function itself will not run. It'll run later.
+//
+// This is a reentrancy test, like Frame-onPop-generators-03.
+
+let g = newGlobal({newCompartment: true});
+g.log = "";
+g.eval(`
+ async function f() {
+ log += "1";
+ debugger;
+ log += "2";
+ await Promise.resolve(3);
+ log += "3";
+ return "ok";
+ }
+`);
+
+let dbg = Debugger(g);
+dbg.onDebuggerStatement = frame => {
+ frame.onPop = completion => {
+ // What we are really testing is that when onPop is called, we have not
+ // yet thrown this async function activation back into the hopper.
+ g.log += 'A';
+ drainJobQueue();
+ g.log += 'B';
+
+ frame.onPop = completion => {
+ g.log += 'C';
+ };
+ };
+};
+
+let status = "FAIL - g.f() did not resolve";
+g.f().then(value => { status = value; });
+assertEq(g.log, "12AB");
+drainJobQueue();
+assertEq(g.log, "12AB3C");
+assertEq(status, "ok");
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-async-02.js b/js/src/jit-test/tests/debug/Frame-onPop-async-02.js
new file mode 100644
index 0000000000..8e579b588b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-async-02.js
@@ -0,0 +1,36 @@
+// |jit-test| error:all-jobs-completed-successfully
+
+load(libdir + 'match.js');
+load(libdir + 'match-debugger.js');
+const { Pattern } = Match;
+const { OBJECT_WITH_EXACTLY: EXACT } = Pattern;
+
+let g = newGlobal({newCompartment: true});
+let dbg = Debugger(g);
+const log = [];
+g.capture = function () {
+ dbg.getNewestFrame().onPop = completion => {
+ log.push(completion);
+ };
+};
+
+g.eval(`
+ async function f() {
+ capture();
+ await Promise.resolve(3);
+ return "ok";
+ }
+`);
+
+
+const promise = g.f();
+promise.then(value => {
+ assertEq(value, "ok");
+
+ Pattern([
+ EXACT({ return: new DebuggerObjectPattern("Promise"), await:true }),
+ EXACT({ return: new DebuggerObjectPattern("Promise") }),
+ ]).assert(log);
+
+ throw "all-jobs-completed-successfully";
+});
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-async-generators-01.js b/js/src/jit-test/tests/debug/Frame-onPop-async-generators-01.js
new file mode 100644
index 0000000000..0c2cc27f8c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-async-generators-01.js
@@ -0,0 +1,53 @@
+// |jit-test| error:all-jobs-completed-successfully
+
+load(libdir + "asserts.js");
+load(libdir + 'match.js');
+load(libdir + 'match-debugger.js');
+const { Pattern } = Match;
+const { OBJECT_WITH_EXACTLY: EXACT } = Pattern;
+
+let g = newGlobal({newCompartment: true});
+let dbg = Debugger(g);
+const log = [];
+g.capture = function () {
+ dbg.getNewestFrame().onPop = completion => {
+ log.push(completion);
+ };
+};
+
+g.eval(`
+ async function* asyncgen() {
+ capture();
+ await Promise.resolve(1);
+ yield 2;
+ await Promise.resolve(3);
+ yield 4;
+ return "ok";
+ }
+`);
+
+async function collect() {
+ let items = [];
+ for await (let item of g.asyncgen()) {
+ items.push(item);
+ }
+ return items;
+}
+
+collect().then(value => {
+ assertDeepEq(value, [2, 4]);
+
+ Pattern([
+ EXACT({ return: new DebuggerObjectPattern("Promise"), await: true }),
+ EXACT({ return: 2, await: true }),
+ EXACT({ return: 2, yield: true }),
+ EXACT({ return: new DebuggerObjectPattern("Promise"), await: true }),
+ EXACT({ return: 4, await: true }),
+ EXACT({ return: 4, yield: true }),
+ EXACT({ return: "ok", await: true }),
+ EXACT({ return: "ok" }),
+ ]).assert(log);
+
+ throw "all-jobs-completed-successfully";
+});
+
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-dead-frame.js b/js/src/jit-test/tests/debug/Frame-onPop-dead-frame.js
new file mode 100644
index 0000000000..52c6f2ab79
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-dead-frame.js
@@ -0,0 +1,28 @@
+// Don't crash trying to fire a dead frame's onPop handler.
+
+var g = newGlobal({newCompartment: true});
+g.eval('function f() { debugger; }');
+
+var log = '';
+
+// Create two Debuggers debugging the same global `g`. Both will put onPop
+// handlers on the same frame.
+var dbg1 = Debugger(g);
+dbg1.onDebuggerStatement = frame1 => {
+ frame1.onPop = completion => {
+ log += 'A';
+ dbg2.removeDebuggee(g); // kills frame2, so frame2.onPop should not fire
+ log += 'B';
+ };
+};
+
+var dbg2 = Debugger(g);
+dbg2.onDebuggerStatement = frame2 => {
+ frame2.onPop = completion => {
+ log += 'C';
+ };
+};
+
+g.f();
+
+assertEq(log, 'AB');
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-error-error.js b/js/src/jit-test/tests/debug/Frame-onPop-error-error.js
new file mode 100644
index 0000000000..fc6c18b6d4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-error-error.js
@@ -0,0 +1,60 @@
+// |jit-test| error: TestComplete
+// onPop can request a termination when stopped for a termination
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+// We use Debugger.Frame.prototype.eval and ignore the outer 'eval' frame so we
+// can catch the termination.
+
+function test(type, provocation) {
+ // Help people figure out which 'test' call failed.
+ print("type: " + JSON.stringify(type));
+ print("provocation: " + JSON.stringify(provocation));
+
+ var log;
+ dbg.onEnterFrame = function handleFirstFrame(f) {
+ log += 'f';
+ dbg.onDebuggerStatement = function handleDebugger(f) {
+ log += 'd';
+ return null;
+ };
+
+ dbg.onEnterFrame = function handleSecondFrame(f) {
+ log += 'e';
+ assertEq(f.type, 'eval');
+
+ dbg.onEnterFrame = function handleThirdFrame(f) {
+ log += '(';
+ assertEq(f.type, type);
+
+ dbg.onEnterFrame = function handleExtraFrames(f) {
+ // This should never be called.
+ assertEq(false, true);
+ };
+
+ f.onPop = function handlePop(c) {
+ log += ')';
+ assertEq(c, null);
+ return null;
+ };
+ };
+ };
+
+ assertEq(f.eval(provocation), null);
+ };
+
+ log = '';
+ // This causes handleFirstFrame to be called.
+ assertEq(typeof g.eval('eval'), 'function');
+ assertEq(log, 'fe(d)');
+
+ print();
+}
+
+g.eval('function f() { debugger; return \'termination fail\'; }');
+test('call', 'f();');
+test('call', 'new f;');
+test('eval', 'eval(\'debugger; \\\'termination fail\\\';\');');
+test('global', 'evaluate(\'debugger; \\\'termination fail\\\';\');');
+throw 'TestComplete';
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-error-return.js b/js/src/jit-test/tests/debug/Frame-onPop-error-return.js
new file mode 100644
index 0000000000..d8abdebd70
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-error-return.js
@@ -0,0 +1,47 @@
+// |jit-test| error: TestComplete
+// onPop can change a termination into a normal return.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+function test(type, provocation) {
+ var log;
+ var wasConstructing;
+
+ // Help people figure out which 'test' call failed.
+ print("type: " + JSON.stringify(type));
+ print("provocation: " + JSON.stringify(provocation));
+
+ dbg.onDebuggerStatement = function handleDebuggerStatement(f) {
+ log += 'd';
+ return null;
+ };
+
+ dbg.onEnterFrame = function handleEnterFrame(f) {
+ log += '(';
+ assertEq(f.type, type);
+ wasConstructing = f.constructing;
+ f.onPop = function handlePop(c) {
+ log += ')';
+ assertEq(c, null);
+ return { return: 'favor' };
+ };
+ };
+
+ log = '';
+ var result = provocation();
+ if (wasConstructing)
+ assertEq(typeof result, "object");
+ else
+ assertEq(result, 'favor');
+ assertEq(log, "(d)");
+
+ print();
+}
+
+g.eval("function f() { debugger; return 'termination fail'; }");
+test("call", g.f);
+test("call", function () { return new g.f; });
+test("eval", function () { return g.eval("debugger; \'termination fail\';"); });
+test("global", function () { return g.evaluate("debugger; \'termination fail\';"); });
+throw 'TestComplete';
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-error-scope-unwind-01.js b/js/src/jit-test/tests/debug/Frame-onPop-error-scope-unwind-01.js
new file mode 100644
index 0000000000..c4c6ee0f73
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-error-scope-unwind-01.js
@@ -0,0 +1,33 @@
+// Tests that exception handling works with block scopes.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var correct;
+dbg.onEnterFrame = function (f) {
+ if (f.callee && f.callee.name == "f") {
+ f.onPop = function() {
+ // The scope at the point of onPop is at the point of popping (the
+ // noSuchFn call).
+ correct = (f.environment.getVariable("e") === 42 &&
+ f.environment.getVariable("outer") === undefined);
+ };
+ }
+};
+g.eval("" + function f() {
+ var outer = 43;
+ try {
+ eval("");
+ throw 42;
+ } catch (e) {
+ noSuchFn(e);
+ }
+});
+
+
+try {
+ g.eval("f();");
+} catch (e) {
+ // The above is expected to throw.
+}
+
+assertEq(correct, true);
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-error-scope-unwind-02.js b/js/src/jit-test/tests/debug/Frame-onPop-error-scope-unwind-02.js
new file mode 100644
index 0000000000..935c4173a0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-error-scope-unwind-02.js
@@ -0,0 +1,36 @@
+// Tests that exception handling works with block scopes.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var correct;
+dbg.onEnterFrame = function (f) {
+ if (f.callee && f.callee.name == "f") {
+ f.onPop = function() {
+ // The scope at the point of onPop is at the point of popping (the
+ // noSuchFn call).
+ correct = (f.environment.getVariable("e") === 42 &&
+ f.environment.getVariable("outer") === undefined);
+ };
+ }
+};
+g.eval("" + function f() {
+ var outer = 43;
+ // Surround with a loop to insert a loop trynote.
+ for (;;) {
+ try {
+ eval("");
+ throw 42;
+ } catch (e) {
+ noSuchFn(e);
+ }
+ }
+});
+
+
+try {
+ g.eval("f();");
+} catch (e) {
+ // The above is expected to throw.
+}
+
+assertEq(correct, true);
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-error-throw.js b/js/src/jit-test/tests/debug/Frame-onPop-error-throw.js
new file mode 100644
index 0000000000..0f96865ead
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-error-throw.js
@@ -0,0 +1,42 @@
+// |jit-test| error: TestComplete
+// onPop can change a termination into a throw.
+
+load(libdir + "asserts.js");
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+function test(type, provocation) {
+ var log;
+
+ // Help people figure out which 'test' call failed.
+ print("type: " + JSON.stringify(type));
+ print("provocation: " + JSON.stringify(provocation));
+
+ dbg.onDebuggerStatement = function handleDebuggerStatement(f) {
+ log += 'd';
+ return null;
+ };
+
+ dbg.onEnterFrame = function handleEnterFrame(f) {
+ log += '(';
+ assertEq(f.type, type);
+ f.onPop = function handlePop(c) {
+ log += ')';
+ assertEq(c, null);
+ return { throw: 'snow' };
+ };
+ };
+
+ log = '';
+ assertThrowsValue(provocation, 'snow');
+ assertEq(log, "(d)");
+
+ print();
+}
+
+g.eval("function f() { debugger; return 'termination fail'; }");
+test("call", g.f);
+test("call", function () { return new g.f; });
+test("eval", function () { return g.eval("debugger; \'termination fail\';"); });
+test("global", function () { return g.evaluate("debugger; \'termination fail\';"); });
+throw 'TestComplete';
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-error.js b/js/src/jit-test/tests/debug/Frame-onPop-error.js
new file mode 100644
index 0000000000..777bf70c97
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-error.js
@@ -0,0 +1,59 @@
+// |jit-test| error: TestComplete
+// onPop fires when frames are terminated.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+// We use Debugger.Frame.prototype.eval and ignore the outer 'eval' frame so we
+// can catch the termination.
+
+function test(type, provocation) {
+ // Help people figure out which 'test' call failed.
+ print("type: " + JSON.stringify(type));
+ print("provocation: " + JSON.stringify(provocation));
+
+ var log;
+ dbg.onEnterFrame = function handleFirstFrame(f) {
+ log += 'f';
+ dbg.onDebuggerStatement = function handleDebugger(f) {
+ log += 'd';
+ return null;
+ };
+
+ dbg.onEnterFrame = function handleSecondFrame(f) {
+ log += 'e';
+ assertEq(f.type, 'eval');
+
+ dbg.onEnterFrame = function handleThirdFrame(f) {
+ log += '(';
+ assertEq(f.type, type);
+
+ dbg.onEnterFrame = function handleExtraFrames(f) {
+ // This should never be called.
+ assertEq(false, true);
+ };
+
+ f.onPop = function handlePop(c) {
+ log += ')';
+ assertEq(c, null);
+ };
+ };
+ };
+
+ assertEq(f.eval(provocation), null);
+ };
+
+ log = '';
+ // This causes handleFirstFrame to be called.
+ assertEq(typeof g.eval('eval'), 'function');
+ assertEq(log, 'fe(d)');
+
+ print();
+}
+
+g.eval('function f() { debugger; return \'termination fail\'; }');
+test('call', 'f();');
+test('call', 'new f;');
+test('eval', 'eval(\'debugger; \\\'termination fail\\\';\');');
+test('global', 'evaluate(\'debugger; \\\'termination fail\\\';\');');
+throw 'TestComplete';
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-generator-resumption-01.js b/js/src/jit-test/tests/debug/Frame-onPop-generator-resumption-01.js
new file mode 100644
index 0000000000..617baceb7b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-generator-resumption-01.js
@@ -0,0 +1,14 @@
+// A generator is left closed after frame.onPop returns a {return:} resumption value.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+dbg.onDebuggerStatement = frame => {
+ frame.onPop = completion => ({return: "ok"});
+};
+g.eval("function* g() { for (var i = 0; i < 10; i++) { debugger; yield i; } }");
+var it = g.g();
+var result = it.next();
+assertEq(result.value, "ok");
+assertEq(result.done, true);
+assertEq(it.next().value, undefined);
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-generators-01.js b/js/src/jit-test/tests/debug/Frame-onPop-generators-01.js
new file mode 100644
index 0000000000..6f64bdd6a4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-generators-01.js
@@ -0,0 +1,20 @@
+// Returning {throw:} from an onPop handler when yielding works.
+// It closes the generator-iterator.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+dbg.onDebuggerStatement = function handleDebugger(frame) {
+ frame.onPop = function (c) {
+ return {throw: "fit"};
+ };
+};
+g.eval("function* g() { for (var i = 0; i < 10; i++) { debugger; yield i; } }");
+g.eval("var it = g();");
+var rv = gw.executeInGlobal("it.next();");
+assertEq(rv.throw, "fit");
+
+dbg.enabled = false;
+assertEq(g.it.next().value, undefined);
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-generators-02.js b/js/src/jit-test/tests/debug/Frame-onPop-generators-02.js
new file mode 100644
index 0000000000..8b1ea96e60
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-generators-02.js
@@ -0,0 +1,19 @@
+// |jit-test| error: fit
+
+// Throwing an exception from an onPop handler when yielding terminates the debuggee
+// but does not close the generator-iterator.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+dbg.onDebuggerStatement = function handleDebugger(frame) {
+ frame.onPop = function (c) {
+ throw "fit";
+ };
+};
+g.eval("function* g() { for (var i = 0; i < 10; i++) { debugger; yield i; } }");
+g.eval("var it = g();");
+assertEq(gw.executeInGlobal("it.next();"), null);
+
+dbg.enabled = false;
+assertEq(g.it.next().value, 1);
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-generators-03.js b/js/src/jit-test/tests/debug/Frame-onPop-generators-03.js
new file mode 100644
index 0000000000..cb7b43a08a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-generators-03.js
@@ -0,0 +1,42 @@
+// onPop fires while the [[GeneratorState]] is still "executing".
+//
+// This test checks that Debugger doesn't accidentally make it possible to
+// reenter a generator frame that's on the stack.
+
+load(libdir + "asserts.js");
+
+let g = newGlobal({newCompartment: true});
+g.eval('function* f() { debugger; yield 1; debugger; yield 2; debugger; }');
+let dbg = Debugger(g);
+let genObj = g.f();
+
+let hits = 0;
+dbg.onDebuggerStatement = frame => {
+ frame.onPop = completion => {
+ dbg.removeDebuggee(g); // avoid the DebuggeeWouldRun exception
+ hits++;
+ if (hits < 3) {
+ // We're yielding. Calling .return(), .next(), or .throw() on a
+ // generator that's currently on the stack fails with a TypeError.
+ assertThrowsInstanceOf(() => genObj.next(), g.TypeError);
+ assertThrowsInstanceOf(() => genObj.throw("fit"), g.TypeError);
+ assertThrowsInstanceOf(() => genObj.return(), g.TypeError);
+ } else {
+ // This time we're returning. The generator has already been
+ // closed, so its methods work but are basically no-ops.
+ let result = genObj.next();
+ assertEq(result.done, true);
+ assertEq(result.value, undefined);
+
+ assertThrowsValue(() => genObj.throw("fit"), "fit");
+
+ result = genObj.return();
+ assertEq(result.done, true);
+ assertEq(result.value, undefined);
+ }
+ dbg.addDebuggee(g);
+ };
+};
+
+for (let x of genObj) {}
+assertEq(hits, 3);
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-generators-04.js b/js/src/jit-test/tests/debug/Frame-onPop-generators-04.js
new file mode 100644
index 0000000000..8e4ea689e6
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-generators-04.js
@@ -0,0 +1,26 @@
+// Terminating a generator from the onPop callback for its initial yield
+// leaves the Frame in a sane but inactive state.
+
+load(libdir + "asserts.js");
+
+let g = newGlobal({newCompartment: true});
+g.eval("function* f(x) { yield x; }");
+let dbg = new Debugger;
+let gw = dbg.addDebuggee(g);
+
+let genFrame = null;
+dbg.onDebuggerStatement = frame => {
+ dbg.onEnterFrame = frame => {
+ if (frame.callee == gw.getOwnPropertyDescriptor("f").value) {
+ genFrame = frame;
+ frame.onPop = completion => null;
+ }
+ };
+ assertEq(frame.eval("f(0);"), null);
+};
+
+g.eval("debugger;");
+
+assertEq(genFrame instanceof Debugger.Frame, true);
+assertEq(genFrame.onStack, false);
+assertThrowsInstanceOf(() => genFrame.callee, Error);
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-generators-05.js b/js/src/jit-test/tests/debug/Frame-onPop-generators-05.js
new file mode 100644
index 0000000000..09d66d7c84
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-generators-05.js
@@ -0,0 +1,29 @@
+// In .onPop for the "initial yield" of a generator, while the generator frame
+// is on the stack, the generator object's .next() method throws.
+
+let g = newGlobal({newCompartment: true});
+g.eval(`
+ function* f() {
+ return "ok";
+ }
+`);
+
+let hits = 0;
+let dbg = new Debugger;
+let gw = dbg.addDebuggee(g);
+dbg.onEnterFrame = frame => {
+ dbg.onEnterFrame = undefined; // Trigger only once.
+ frame.onPop = completion => {
+ // Initial yield.
+ let genObj = completion.return;
+ assertEq(genObj.class, "Generator");
+ let result = frame.evalWithBindings("genObj.next()", {genObj});
+ assertEq(result.throw.class, "TypeError");
+ assertEq(result.throw.getProperty("message").return,
+ "already executing generator");
+ hits++;
+ };
+};
+
+g.f();
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-generators-06.js b/js/src/jit-test/tests/debug/Frame-onPop-generators-06.js
new file mode 100644
index 0000000000..993e2e11bb
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-generators-06.js
@@ -0,0 +1,28 @@
+load(libdir + "asserts.js");
+load(libdir + 'match.js');
+load(libdir + 'match-debugger.js');
+const { Pattern } = Match;
+const { OBJECT_WITH_EXACTLY: EXACT } = Pattern;
+
+let g = newGlobal({newCompartment: true});
+let dbg = Debugger(g);
+const log = [];
+g.capture = function () {
+ dbg.getNewestFrame().onPop = completion => {
+ log.push(completion);
+ };
+};
+
+g.eval(`
+ function* f() {
+ capture();
+ yield 3;
+ return "ok";
+ }
+`);
+
+assertDeepEq([... g.f()], [3]);
+Pattern([
+ EXACT({ return: new DebuggerObjectPattern("Object", { value: 3, done: false }), yield: true }),
+ EXACT({ return: new DebuggerObjectPattern("Object", { value: "ok", done: true }) }),
+]).assert(log);
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-generators-07.js b/js/src/jit-test/tests/debug/Frame-onPop-generators-07.js
new file mode 100644
index 0000000000..7872b345e6
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-generators-07.js
@@ -0,0 +1,40 @@
+// Completion values for generators report yields and initial yields.
+
+load(libdir + "asserts.js");
+load(libdir + 'match.js');
+load(libdir + 'match-debugger.js');
+const { Pattern } = Match;
+const { OBJECT_WITH_EXACTLY: X } = Pattern;
+
+const g = newGlobal({ newCompartment: true });
+g.eval(`
+ function* f() {
+ yield "yielding";
+ return "returning";
+ }
+`);
+
+const dbg = new Debugger(g);
+const completions = [];
+dbg.onEnterFrame = frame => {
+ frame.onPop = completion => {
+ completions.push(completion);
+ };
+};
+
+assertDeepEq([... g.f()], ["yielding"]);
+print(JSON.stringify(completions));
+Pattern([
+ X({
+ return: new DebuggerObjectPattern("Generator", {}),
+ yield: true,
+ initial: true
+ }),
+ X({
+ return: new DebuggerObjectPattern("Object", { value: "yielding", done: false }),
+ yield: true
+ }),
+ X({
+ return: new DebuggerObjectPattern("Object", { value: "returning", done: true })
+ }),
+]).assert(completions);
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-generators-08.js b/js/src/jit-test/tests/debug/Frame-onPop-generators-08.js
new file mode 100644
index 0000000000..75238ee17c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-generators-08.js
@@ -0,0 +1,16 @@
+// Creating a new generator frame after the generator is closed.
+
+var g = newGlobal({ newCompartment: true });
+g.eval("function* gen(x) { debugger; }");
+var dbg = new Debugger(g);
+dbg.onDebuggerStatement = frame => {
+ frame.onPop = completion => {
+ assertEq(frame.callee.name, "gen");
+ assertEq(frame.eval("x").return, 3);
+ var f2 = (new Debugger(g)).getNewestFrame();
+ assertEq(f2.callee.name, "gen");
+ assertEq(f2.eval("x").return, 3);
+ };
+};
+g.gen(3).next();
+
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-multiple-01.js b/js/src/jit-test/tests/debug/Frame-onPop-multiple-01.js
new file mode 100644
index 0000000000..e5280bae6b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-multiple-01.js
@@ -0,0 +1,106 @@
+// Multiple debuggers all get their onPop handlers called.
+
+function completionsEqual(c1, c2) {
+ if (c1 && c2) {
+ if (c1.throw)
+ return c1.throw === c2.throw;
+ else
+ return c1.return === c2.return;
+ }
+ return c1 === c2;
+}
+
+function completionString(c) {
+ if (c == null)
+ return 'x';
+ if (c.return)
+ return 'r' + c.return;
+ if (c.throw)
+ return 't' + c.throw;
+ return '?';
+}
+
+var g = newGlobal({newCompartment: true}); // poor thing
+g.eval('function f() { debugger; return "1"; }');
+
+// A list of the debuggers' Debugger.Frame instances. When it's all over,
+// we test that they are all marked as no longer live.
+var frames = [];
+
+// We start off the test via Debugger.Frame.prototype.eval, so if we end
+// with a termination, we still catch it, instead of aborting the whole
+// test. (Debugger.Object.prototype.executeInGlobal would simplify this...)
+var dbg0 = new Debugger(g);
+dbg0.onEnterFrame = function handleOriginalEnter(frame) {
+ dbg0.log += '(';
+ dbg0.onEnterFrame = undefined;
+
+ assertEq(frame.onStack, true);
+ frames.push(frame);
+
+ var dbgs = [];
+ var log;
+
+ // Create a separate debugger to carry out each item in sequence.
+ for (let i = 0; i < 9; i++) {
+ // Each debugger's handlers close over a distinct 'dbg', but
+ // that's the only distinction between them. Otherwise, they're
+ // driven entirely by global data, so the order in which events are
+ // dispatched to them shouldn't matter.
+ let dbg = new Debugger(g);
+ dbgs.push(dbg);
+
+ dbg.onDebuggerStatement = function handleDebuggerStatement(f) {
+ log += 'd';
+ assertEq(f.onStack, true);
+ frames.push(f);
+ };
+
+ // First expect the 'eval'...
+ dbg.onEnterFrame = function handleEnterEval(f) {
+ log += 'e';
+ assertEq(f.type, 'eval');
+ assertEq(f.onStack, true);
+ frames.push(f);
+
+ // Then expect the call.
+ dbg.onEnterFrame = function handleEnterCall(f) {
+ log += '(';
+ assertEq(f.type, 'call');
+ assertEq(f.onStack, true);
+ frames.push(f);
+
+ // Don't expect any further frames.
+ dbg.onEnterFrame = function handleExtraEnter(f) {
+ log += 'z';
+ };
+
+ f.onPop = function handlePop(c) {
+ log += ')';
+ assertEq(this.onStack, true);
+ assertEq(completionsEqual(c, { return: '1' }), true);
+ frames.push(this);
+
+ // Check that this debugger is in the list, and then remove it.
+ var i = dbgs.indexOf(dbg);
+ assertEq(i != -1, true);
+ dbgs.splice(i,1);
+ };
+ };
+ };
+ }
+
+ log = '';
+ assertEq(completionsEqual(frame.eval('f()'), { return: '1' }), true);
+ assertEq(log, "eeeeeeeee(((((((((ddddddddd)))))))))");
+
+ dbg0.log += '.';
+};
+
+dbg0.log = '';
+g.eval('eval');
+assertEq(dbg0.log, '(.');
+
+// Check that all Debugger.Frame instances we ran into are now marked as dead.
+for (var i = 0; i < frames.length; i++)
+ assertEq(frames[i].onStack, false);
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-multiple-02.js b/js/src/jit-test/tests/debug/Frame-onPop-multiple-02.js
new file mode 100644
index 0000000000..6ab1283c9f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-multiple-02.js
@@ -0,0 +1,36 @@
+// One Debugger's onPop handler can remove another Debugger's onPop handler.
+var g = newGlobal({newCompartment: true});
+var dbg1 = new Debugger(g);
+var dbg2 = new Debugger(g);
+
+var log;
+var frames = [];
+var firstPop = true;
+
+function handleEnter(frame) {
+ log += '(';
+ frames.push(frame);
+ frame.onPop = function handlePop(completion) {
+ log += ')';
+ assertEq(completion.return, 42);
+ if (firstPop) {
+ // We can't say which frame's onPop handler will get called first.
+ if (this == frames[0])
+ frames[1].onPop = undefined;
+ else
+ frames[0].onPop = undefined;
+ gc();
+ } else {
+ assertEq("second pop handler was called",
+ "second pop handler should not be called");
+ }
+ firstPop = false;
+ };
+};
+
+dbg1.onEnterFrame = handleEnter;
+dbg2.onEnterFrame = handleEnter;
+
+log = '';
+assertEq(g.eval('40 + 2'), 42);
+assertEq(log, '(()');
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-multiple-04.js b/js/src/jit-test/tests/debug/Frame-onPop-multiple-04.js
new file mode 100644
index 0000000000..451359ce7e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-multiple-04.js
@@ -0,0 +1,27 @@
+// If one Debugger's onPop handler causes another Debugger to create a
+// Debugger.Frame instance referring to the same frame, that frame still
+// gets marked as not live after all the onPop handlers have run.
+var g = newGlobal({newCompartment: true});
+var dbg1 = new Debugger(g);
+var dbg2 = new Debugger(g);
+
+var log;
+var frame2;
+
+dbg1.onEnterFrame = function handleEnter(frame) {
+ log += '(';
+ frame.onPop = function handlerPop1(c) {
+ log += ')';
+ frame2 = dbg2.getNewestFrame();
+ assertEq(frame2.onStack, true);
+ frame2.onPop = function handlePop2(c) {
+ assertEq("late frame's onPop handler ran",
+ "late frame's onPop handler should not run");
+ };
+ };
+};
+
+log = '';
+assertEq(g.eval('40 + 2'), 42);
+assertEq(log, '()');
+assertEq(frame2.onStack, false);
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-return-error.js b/js/src/jit-test/tests/debug/Frame-onPop-return-error.js
new file mode 100644
index 0000000000..d01134f63e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-return-error.js
@@ -0,0 +1,59 @@
+// |jit-test| error: TestComplete
+// onPop can change a normal return into a termination.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+// We use Debugger.Frame.prototype.eval and ignore the outer 'eval' frame so we
+// can catch the termination.
+
+function test(type, provocation) {
+ // Help people figure out which 'test' call failed.
+ print("type: " + JSON.stringify(type));
+ print("provocation: " + JSON.stringify(provocation));
+
+ var log;
+ dbg.onEnterFrame = function handleFirstFrame(f) {
+ log += 'f';
+ dbg.onDebuggerStatement = function handleDebugger(f) {
+ log += 'd';
+ };
+
+ dbg.onEnterFrame = function handleSecondFrame(f) {
+ log += 'e';
+ assertEq(f.type, 'eval');
+
+ dbg.onEnterFrame = function handleThirdFrame(f) {
+ log += '(';
+ assertEq(f.type, type);
+
+ dbg.onEnterFrame = function handleExtraFrames(f) {
+ // This should never be called.
+ assertEq(false, true);
+ };
+
+ f.onPop = function handlePop(c) {
+ log += ')';
+ assertEq(c.return, 'compliment');
+ return null;
+ };
+ };
+ };
+
+ assertEq(f.eval(provocation), null);
+ };
+
+ log = '';
+ // This causes handleFirstFrame to be called.
+ assertEq(typeof g.eval('eval'), 'function');
+ assertEq(log, 'fe(d)');
+
+ print();
+}
+
+g.eval('function f() { debugger; return \'compliment\'; }');
+test('call', 'f();');
+test('call', 'new f;');
+test('eval', 'eval(\'debugger; \\\'compliment\\\';\');');
+test('global', 'evaluate(\'debugger; \\\'compliment\\\';\');');
+throw 'TestComplete';
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-return-return.js b/js/src/jit-test/tests/debug/Frame-onPop-return-return.js
new file mode 100644
index 0000000000..f8c8240b88
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-return-return.js
@@ -0,0 +1,46 @@
+// |jit-test| error: TestComplete
+// onPop can change a normal return into a normal return of a different value.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+function test(type, provocation) {
+ var log;
+ var wasConstructing;
+
+ // Help people figure out which 'test' call failed.
+ print("type: " + JSON.stringify(type));
+ print("provocation: " + JSON.stringify(provocation));
+
+ dbg.onDebuggerStatement = function handleDebuggerStatement(f) {
+ log += 'd';
+ };
+
+ dbg.onEnterFrame = function handleEnterFrame(f) {
+ log += '(';
+ assertEq(f.type, type);
+ wasConstructing = f.constructing;
+ f.onPop = function handlePop(c) {
+ log += ')';
+ assertEq(c.return, 'compliment');
+ return { return: 'favor' };
+ };
+ };
+
+ log = '';
+ var result = provocation();
+ if (wasConstructing)
+ assertEq(typeof result, "object");
+ else
+ assertEq(result, 'favor');
+ assertEq(log, "(d)");
+
+ print();
+}
+
+g.eval("function f() { debugger; return 'compliment'; }");
+test("call", g.f);
+test("call", function () { return new g.f; });
+test("eval", function () { return g.eval("debugger; \'compliment\';"); });
+test("global", function () { return g.evaluate("debugger; \'compliment\';"); });
+throw 'TestComplete';
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-return-throw.js b/js/src/jit-test/tests/debug/Frame-onPop-return-throw.js
new file mode 100644
index 0000000000..369f316119
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-return-throw.js
@@ -0,0 +1,41 @@
+// |jit-test| error: TestComplete
+// onPop can change a normal return into a throw.
+
+load(libdir + "asserts.js");
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+function test(type, provocation) {
+ var log;
+
+ // Help people figure out which 'test' call failed.
+ print("type: " + JSON.stringify(type));
+ print("provocation: " + JSON.stringify(provocation));
+
+ dbg.onDebuggerStatement = function handleDebuggerStatement(f) {
+ log += 'd';
+ };
+
+ dbg.onEnterFrame = function handleEnterFrame(f) {
+ log += '(';
+ assertEq(f.type, type);
+ f.onPop = function handlePop(c) {
+ log += ')';
+ assertEq(c.return, 'compliment');
+ return { throw: 'snow' };
+ };
+ };
+
+ log = '';
+ assertThrowsValue(provocation, 'snow');
+ assertEq(log, "(d)");
+
+ print();
+}
+
+g.eval("function f() { debugger; return 'compliment'; }");
+test("call", g.f);
+test("call", function () { return new g.f; });
+test("eval", function () { return g.eval("debugger; \'compliment\';"); });
+test("global", function () { return g.evaluate("debugger; \'compliment\';"); });
+throw 'TestComplete';
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-return.js b/js/src/jit-test/tests/debug/Frame-onPop-return.js
new file mode 100644
index 0000000000..e1288d8d3a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-return.js
@@ -0,0 +1,45 @@
+// |jit-test| error: TestComplete
+// onPop fires when frames return normally.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+function test(type, provocation) {
+ var log;
+ var wasConstructing;
+
+ // Help people figure out which 'test' call failed.
+ print("type: " + JSON.stringify(type));
+ print("provocation: " + JSON.stringify(provocation));
+
+ dbg.onDebuggerStatement = function handleDebuggerStatement(f) {
+ log += 'd';
+ };
+
+ dbg.onEnterFrame = function handleEnterFrame(f) {
+ log += '(';
+ assertEq(f.type, type);
+ wasConstructing = f.constructing;
+ f.onPop = function handlePop(c) {
+ log += ')';
+ assertEq(c.return, 'compliment');
+ };
+ };
+
+ log = '';
+ var result = provocation();
+ if (wasConstructing)
+ assertEq(typeof result, "object");
+ else
+ assertEq(result, 'compliment');
+ assertEq(log, "(d)");
+
+ print();
+}
+
+g.eval("function f() { debugger; return 'compliment'; }");
+test("call", g.f);
+test("call", function () { return new g.f; });
+test("eval", function () { return g.eval("debugger; \'compliment\';"); });
+test("global", function () { return g.evaluate("debugger; \'compliment\';"); });
+throw 'TestComplete';
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-throw-error.js b/js/src/jit-test/tests/debug/Frame-onPop-throw-error.js
new file mode 100644
index 0000000000..83dee69686
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-throw-error.js
@@ -0,0 +1,59 @@
+// |jit-test| error: TestComplete
+// onPop can change a throw into a termination.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+// We use Debugger.Frame.prototype.eval and ignore the outer 'eval' frame so we
+// can catch the termination.
+
+function test(type, provocation) {
+ // Help people figure out which 'test' call failed.
+ print("type: " + JSON.stringify(type));
+ print("provocation: " + JSON.stringify(provocation));
+
+ var log;
+ dbg.onEnterFrame = function handleFirstFrame(f) {
+ log += 'f';
+ dbg.onDebuggerStatement = function handleDebugger(f) {
+ log += 'd';
+ };
+
+ dbg.onEnterFrame = function handleSecondFrame(f) {
+ log += 'e';
+ assertEq(f.type, 'eval');
+
+ dbg.onEnterFrame = function handleThirdFrame(f) {
+ log += '(';
+ assertEq(f.type, type);
+
+ dbg.onEnterFrame = function handleExtraFrames(f) {
+ // This should never be called.
+ assertEq(false, true);
+ };
+
+ f.onPop = function handlePop(c) {
+ log += ')';
+ assertEq(c.throw, 'mud');
+ return null;
+ };
+ };
+ };
+
+ assertEq(f.eval(provocation), null);
+ };
+
+ log = '';
+ // This causes handleFirstFrame to be called.
+ assertEq(typeof g.eval('eval'), 'function');
+ assertEq(log, 'fe(d)');
+
+ print();
+}
+
+g.eval('function f() { debugger; throw \'mud\'; }');
+test('call', 'f();');
+test('call', 'new f;');
+test('eval', 'eval(\'debugger; throw \\\'mud\\\';\');');
+test('global', 'evaluate(\'debugger; throw \\\'mud\\\';\');');
+throw 'TestComplete';
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-throw-return.js b/js/src/jit-test/tests/debug/Frame-onPop-throw-return.js
new file mode 100644
index 0000000000..a7c323d5f2
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-throw-return.js
@@ -0,0 +1,46 @@
+// |jit-test| error: TestComplete
+// onPop can change a throw into a normal return.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+function test(type, provocation) {
+ var log;
+ var wasConstructing;
+
+ // Help people figure out which 'test' call failed.
+ print("type: " + JSON.stringify(type));
+ print("provocation: " + JSON.stringify(provocation));
+
+ dbg.onDebuggerStatement = function handleDebuggerStatement(f) {
+ log += 'd';
+ };
+
+ dbg.onEnterFrame = function handleEnterFrame(f) {
+ log += '(';
+ assertEq(f.type, type);
+ wasConstructing = f.constructing;
+ f.onPop = function handlePop(c) {
+ log += ')';
+ assertEq(c.throw, 'mud');
+ return { return: 'favor' };
+ };
+ };
+
+ log = '';
+ var result = provocation();
+ if (wasConstructing)
+ assertEq(typeof result, "object");
+ else
+ assertEq(result, 'favor');
+ assertEq(log, "(d)");
+
+ print();
+}
+
+g.eval("function f() { debugger; throw 'mud'; }");
+test("call", g.f);
+test("call", function () { return new g.f; });
+test("eval", function () { return g.eval("debugger; throw \'mud\';"); });
+test("global", function () { return g.evaluate("debugger; throw \'mud\';"); });
+throw 'TestComplete';
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-throw-throw.js b/js/src/jit-test/tests/debug/Frame-onPop-throw-throw.js
new file mode 100644
index 0000000000..5562b1f44c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-throw-throw.js
@@ -0,0 +1,41 @@
+// |jit-test| error: TestComplete
+// onPop can change a throw into a throw of a different value.
+
+load(libdir + "asserts.js");
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+function test(type, provocation) {
+ var log;
+
+ // Help people figure out which 'test' call failed.
+ print("type: " + JSON.stringify(type));
+ print("provocation: " + JSON.stringify(provocation));
+
+ dbg.onDebuggerStatement = function handleDebuggerStatement(f) {
+ log += 'd';
+ };
+
+ dbg.onEnterFrame = function handleEnterFrame(f) {
+ log += '(';
+ assertEq(f.type, type);
+ f.onPop = function handlePop(c) {
+ log += ')';
+ assertEq(c.throw, 'mud');
+ return { throw: 'snow' };
+ };
+ };
+
+ log = '';
+ assertThrowsValue(provocation, 'snow');
+ assertEq(log, "(d)");
+
+ print();
+}
+
+g.eval("function f() { debugger; throw 'mud'; }");
+test("call", g.f);
+test("call", function () { return new g.f; });
+test("eval", function () { return g.eval("debugger; throw \'mud\';"); });
+test("global", function () { return g.evaluate("debugger; throw \'mud\';"); });
+throw 'TestComplete';
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-throw.js b/js/src/jit-test/tests/debug/Frame-onPop-throw.js
new file mode 100644
index 0000000000..8e481a8736
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-throw.js
@@ -0,0 +1,40 @@
+// |jit-test| error: TestComplete
+// onPop fires when frames throw an exception.
+
+load(libdir + "asserts.js");
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+function test(type, provocation) {
+ var log;
+
+ // Help people figure out which 'test' call failed.
+ print("type: " + JSON.stringify(type));
+ print("provocation: " + JSON.stringify(provocation));
+
+ dbg.onDebuggerStatement = function handleDebuggerStatement(f) {
+ log += 'd';
+ };
+
+ dbg.onEnterFrame = function handleEnterFrame(f) {
+ log += '(';
+ assertEq(f.type, type);
+ f.onPop = function handlePop(c) {
+ log += ')';
+ assertEq(c.throw, 'mud');
+ };
+ };
+
+ log = '';
+ assertThrowsValue(provocation, 'mud');
+ assertEq(log, "(d)");
+
+ print();
+}
+
+g.eval("function f() { debugger; throw 'mud'; }");
+test("call", g.f);
+test("call", function () { return new g.f; });
+test("eval", function () { return g.eval("debugger; throw \'mud\';"); });
+test("global", function () { return g.evaluate("debugger; throw \'mud\';"); });
+throw 'TestComplete';
diff --git a/js/src/jit-test/tests/debug/Frame-onStack-01.js b/js/src/jit-test/tests/debug/Frame-onStack-01.js
new file mode 100644
index 0000000000..3e69a0a8b3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStack-01.js
@@ -0,0 +1,41 @@
+// Debugger.Frame.prototype.onStack is true for frames on the stack and false for
+// frames that have returned
+
+var desc = Object.getOwnPropertyDescriptor(Debugger.Frame.prototype, "live");
+assertEq(typeof desc.get, "function");
+assertEq(desc.set, undefined);
+assertEq(desc.configurable, true);
+assertEq(desc.enumerable, false);
+
+var loc;
+
+var g = newGlobal({newCompartment: true});
+g.debuggeeGlobal = this;
+g.eval("var hits = 0;");
+g.eval("(" + function () {
+ var a = [];
+ var dbg = Debugger(debuggeeGlobal);
+ dbg.onDebuggerStatement = function (frame) {
+ var loc = debuggeeGlobal.loc;
+ a[loc] = frame;
+ for (var i = 0; i < a.length; i++) {
+ assertEq(a[i] === frame, i === loc);
+ assertEq(!!(a[i] && a[i].onStack), i >= loc);
+ }
+ hits++;
+ };
+ } + ")()");
+
+function f(n) {
+ loc = n; debugger;
+ if (n !== 0) {
+ f(n - 1);
+ loc = n; debugger;
+ eval("f(n - 1);");
+ loc = n; debugger;
+ }
+}
+
+f(4);
+assertEq(g.hits, 16 + 8*3 + 4*3 + 2*3 + 1*3);
+
diff --git a/js/src/jit-test/tests/debug/Frame-onStack-02.js b/js/src/jit-test/tests/debug/Frame-onStack-02.js
new file mode 100644
index 0000000000..be21f1e18a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStack-02.js
@@ -0,0 +1,32 @@
+// Debugger.Frame.prototype.onStack is false for frames that have thrown or been thrown through
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+g.debuggeeGlobal = this;
+g.eval("var finalCheck;");
+g.eval("(" + function () {
+ var a = [];
+ var dbg = Debugger(debuggeeGlobal);
+ dbg.onDebuggerStatement = function (frame) {
+ a.push(frame);
+ for (var i = 0; i < a.length; i++)
+ assertEq(a[i].onStack, true);
+ };
+ finalCheck = function (n) {
+ assertEq(a.length, n);
+ for (var i = 0; i < n; i++)
+ assertEq(a[i].onStack, false);
+ };
+ } + ")()");
+
+function f(n) {
+ debugger;
+ if (--n > 0)
+ f(n);
+ else
+ throw "fit";
+}
+
+assertThrowsValue(function () { f(10); }, "fit");
+g.finalCheck(10);
diff --git a/js/src/jit-test/tests/debug/Frame-onStack-03.js b/js/src/jit-test/tests/debug/Frame-onStack-03.js
new file mode 100644
index 0000000000..af872782d5
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStack-03.js
@@ -0,0 +1,25 @@
+// frame properties throw if !frame.onStack
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var f;
+Debugger(g).onDebuggerStatement = function (frame) {
+ assertEq(frame.onStack, true);
+ assertEq(frame.type, "call");
+ assertEq(frame.this instanceof Object, true);
+ assertEq(frame.older instanceof Debugger.Frame, true);
+ assertEq(frame.callee instanceof Debugger.Object, true);
+ assertEq(frame.constructing, false);
+ assertEq(frame.arguments.length, 0);
+ f = frame;
+};
+
+g.eval("(function () { debugger; }).call({});");
+assertEq(f.onStack, false);
+assertThrowsInstanceOf(function () { f.type; }, Error);
+assertThrowsInstanceOf(function () { f.this; }, Error);
+assertThrowsInstanceOf(function () { f.older; }, Error);
+assertThrowsInstanceOf(function () { f.callee; }, Error);
+assertThrowsInstanceOf(function () { f.constructing; }, Error);
+assertThrowsInstanceOf(function () { f.arguments; }, Error);
diff --git a/js/src/jit-test/tests/debug/Frame-onStack-04.js b/js/src/jit-test/tests/debug/Frame-onStack-04.js
new file mode 100644
index 0000000000..cc9a132091
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStack-04.js
@@ -0,0 +1,31 @@
+// frame.onStack is false for frames discarded during uncatchable error unwinding.
+
+load(libdir + 'asserts.js');
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var hits = 0;
+var snapshot;
+dbg.onDebuggerStatement = function (frame) {
+ var stack = [];
+ for (var f = frame; f; f = f.older) {
+ if (f.type === "call" && f.script !== null)
+ stack.push(f);
+ }
+ snapshot = stack;
+ if (hits++ === 0)
+ assertEq(frame.eval("x();"), null);
+ else
+ return null;
+};
+
+g.eval("function z() { debugger; }");
+g.eval("function y() { z(); }");
+g.eval("function x() { y(); }");
+assertEq(g.eval("debugger; 'ok';"), "ok");
+assertEq(hits, 2);
+assertEq(snapshot.length, 3);
+for (var i = 0; i < snapshot.length; i++) {
+ assertEq(snapshot[i].onStack, false);
+ assertThrowsInstanceOf(() => frame.script, Error);
+}
diff --git a/js/src/jit-test/tests/debug/Frame-onStack-05.js b/js/src/jit-test/tests/debug/Frame-onStack-05.js
new file mode 100644
index 0000000000..7e8ce8c68d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStack-05.js
@@ -0,0 +1,33 @@
+// frame.onStack is false for frames removed after their compartments stopped being debuggees.
+
+load(libdir + 'asserts.js');
+
+var g1 = newGlobal({newCompartment: true});
+var g2 = newGlobal({newCompartment: true});
+var dbg = Debugger(g1, g2);
+var hits = 0;
+var snapshot = [];
+dbg.onDebuggerStatement = function (frame) {
+ if (hits++ === 0) {
+ assertEq(frame.eval("x();"), null);
+ } else {
+ for (var f = frame; f; f = f.older) {
+ if (f.type === "call" && f.script !== null)
+ snapshot.push(f);
+ }
+ dbg.removeDebuggee(g2);
+ return null;
+ }
+};
+
+g1.eval("function z() { debugger; }");
+g2.z = g1.z;
+g2.eval("function y() { z(); }");
+g2.eval("function x() { y(); }");
+assertEq(g2.eval("debugger; 'ok';"), "ok");
+assertEq(hits, 2);
+assertEq(snapshot.length, 3);
+for (var i = 0; i < snapshot.length; i++) {
+ assertEq(snapshot[i].onStack, false);
+ assertThrowsInstanceOf(() => frame.script, Error);
+}
diff --git a/js/src/jit-test/tests/debug/Frame-onStack-06.js b/js/src/jit-test/tests/debug/Frame-onStack-06.js
new file mode 100644
index 0000000000..8316da9b9e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStack-06.js
@@ -0,0 +1,26 @@
+// frame.onStack is false for generator frames after they return.
+
+let g = newGlobal({newCompartment: true});
+g.eval("function* f() { debugger; }");
+
+let dbg = Debugger(g);
+let savedFrame;
+
+dbg.onDebuggerStatement = frame => {
+ savedFrame = frame;
+ assertEq(frame.callee.name, "f");
+ assertEq(frame.onStack, true);
+ frame.onPop = function() {
+ assertEq(frame.onStack, true);
+ };
+};
+g.f().next();
+
+assertEq(savedFrame.onStack, false);
+try {
+ savedFrame.older;
+ throw new Error("expected exception, none thrown");
+} catch (exc) {
+ assertEq(exc.message, "Debugger.Frame is not on stack or suspended");
+}
+
diff --git a/js/src/jit-test/tests/debug/Frame-onStack-07.js b/js/src/jit-test/tests/debug/Frame-onStack-07.js
new file mode 100644
index 0000000000..055a7d41b2
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStack-07.js
@@ -0,0 +1,56 @@
+// frame.onStack is false for generator frames popped due to exception or termination.
+
+load(libdir + "/asserts.js");
+
+function test(when, what) {
+ let g = newGlobal({newCompartment: true});
+ g.eval("function* f(x) { yield x; }");
+
+ let dbg = new Debugger;
+ let gw = dbg.addDebuggee(g);
+ let fw = gw.getOwnPropertyDescriptor("f").value;
+
+ let t = 0;
+ let poppedFrame;
+
+ function tick(frame) {
+ if (frame.callee == fw) {
+ if (t == when) {
+ poppedFrame = frame;
+ dbg.onEnterFrame = undefined;
+ frame.onPop = undefined;
+ return what;
+ }
+ t++;
+ }
+ return undefined;
+ }
+
+ dbg.onDebuggerStatement = frame => {
+ dbg.onEnterFrame = frame => {
+ frame.onPop = function() {
+ return tick(this);
+ };
+ return tick(frame);
+ };
+ let result = frame.eval("for (let _ of f(0)) {}");
+ if (result && "stack" in result) {
+ result.stack = true;
+ }
+ assertDeepEq(result, what);
+ };
+ g.eval("debugger;");
+
+ assertEq(t, when);
+ assertEq(poppedFrame.onStack, false);
+ assertErrorMessage(() => poppedFrame.older,
+ Error,
+ "Debugger.Frame is not on stack or suspended");
+}
+
+for (let when = 0; when < 6; when++) {
+ for (let what of [null, {throw: "fit", stack: true}]) {
+ test(when, what);
+ }
+}
+
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-01.js b/js/src/jit-test/tests/debug/Frame-onStep-01.js
new file mode 100644
index 0000000000..dcab3c6add
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-01.js
@@ -0,0 +1,24 @@
+// Simple Debugger.Frame.prototype.onStep test.
+// Test that onStep fires often enough to see all four values of a.
+
+var g = newGlobal({newCompartment: true});
+g.a = 0;
+g.eval("function f() {\n" +
+ " a += 2;\n" +
+ " a += 2;\n" +
+ " a += 2;\n" +
+ " return a;\n" +
+ "}\n");
+
+var dbg = Debugger(g);
+var seen = [0, 0, 0, 0, 0, 0, 0];
+dbg.onEnterFrame = function (frame) {
+ frame.onStep = function () {
+ assertEq(arguments.length, 0);
+ assertEq(this, frame);
+ seen[g.a] = 1;
+ };
+}
+
+g.f();
+assertEq(seen.join(""), "1010101");
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-02.js b/js/src/jit-test/tests/debug/Frame-onStep-02.js
new file mode 100644
index 0000000000..ed30f041a0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-02.js
@@ -0,0 +1,27 @@
+// Setting frame.onStep to undefined turns off single-stepping.
+
+var g = newGlobal({newCompartment: true});
+g.a = 0;
+g.eval("function f() {\n" +
+ " a++;\n" +
+ " a++;\n" +
+ " a++;\n" +
+ " a++;\n" +
+ " return a;\n" +
+ "}\n");
+
+var dbg = Debugger(g);
+var seen = [0, 0, 0, 0, 0];
+dbg.onEnterFrame = function (frame) {
+ seen[g.a] = 1;
+ frame.onStep = function () {
+ seen[g.a] = 1;
+ if (g.a === 2) {
+ frame.onStep = undefined;
+ assertEq(frame.onStep, undefined);
+ }
+ };
+}
+
+g.f();
+assertEq(seen.join(","), "1,1,1,0,0");
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-03.js b/js/src/jit-test/tests/debug/Frame-onStep-03.js
new file mode 100644
index 0000000000..0451604abd
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-03.js
@@ -0,0 +1,28 @@
+// Setting onStep does not affect later calls to the same function.
+// (onStep is per-frame, not per-function.)
+
+var g = newGlobal({newCompartment: true});
+g.a = 1;
+g.eval("function f(a) {\n" +
+ " var x = 2 * a;\n" +
+ " return x * x;\n" +
+ "}\n");
+
+var dbg = Debugger(g);
+var log = '';
+dbg.onEnterFrame = function (frame) {
+ log += '+';
+ frame.onStep = function () {
+ if (log.charAt(log.length - 1) != 's')
+ log += 's';
+ };
+};
+
+g.f(1);
+log += '|';
+g.f(2);
+log += '|';
+dbg.onEnterFrame = undefined;
+g.f(3);
+
+assertEq(log, '+s|+s|');
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-04.js b/js/src/jit-test/tests/debug/Frame-onStep-04.js
new file mode 100644
index 0000000000..fb2d6f49e4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-04.js
@@ -0,0 +1,34 @@
+// When a recursive function has many frames on the stack, onStep may be set or
+// not independently on each frame.
+
+var g = newGlobal({newCompartment: true});
+g.eval("function f(x) {\n" +
+ " if (x > 0)\n" +
+ " f(x - 1);\n" +
+ " else\n" +
+ " debugger;\n" +
+ " return x;\n" +
+ "}");
+
+var dbg = Debugger(g);
+var seen = [0, 0, 0, 0, 0, 0, 0, 0];
+function step() {
+ seen[this.arguments[0]] = 1;
+}
+dbg.onEnterFrame = function (frame) {
+ // Turn on stepping for even-numbered frames.
+ var x = frame.arguments[0];
+ if (x % 2 === 0)
+ frame.onStep = step;
+};
+dbg.onDebuggerStatement = function (frame) {
+ // This is called with 8 call frames on the stack, 7 down to 0.
+ // At this point we should have seen all the even-numbered frames.
+ assertEq(seen.join(""), "10101010");
+
+ // Now reset seen to see which frames fire onStep on the way out.
+ seen = [0, 0, 0, 0, 0, 0, 0, 0];
+};
+
+g.f(7);
+assertEq(seen.join(""), "10101010");
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-05.js b/js/src/jit-test/tests/debug/Frame-onStep-05.js
new file mode 100644
index 0000000000..c5e5b146f1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-05.js
@@ -0,0 +1,14 @@
+// Upon returning to a frame with an onStep hook, the hook is called before the
+// next line.
+
+var g = newGlobal({newCompartment: true});
+g.log = '';
+g.eval("function f() { debugger; }");
+
+var dbg = Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+ frame.older.onStep = function () { g.log += 's'; };
+};
+g.eval("f();\n" +
+ "log += 'x';\n");
+assertEq(g.log.charAt(0), 's');
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-06.js b/js/src/jit-test/tests/debug/Frame-onStep-06.js
new file mode 100644
index 0000000000..ba5d7ea773
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-06.js
@@ -0,0 +1,58 @@
+// After returning from an implicit toString call, the calling frame's onStep
+// hook fires.
+
+var g = newGlobal({newCompartment: true});
+g.eval("var originalX = {toString: function () { debugger; log += 'x'; return 1; }};\n");
+
+var dbg = Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+ g.log += 'd';
+ frame.older.onStep = function () {
+ if (!g.log.match(/[sy]$/))
+ g.log += 's';
+ };
+};
+
+// expr is an expression that will trigger an implicit toString call.
+function check(expr) {
+ g.log = '';
+ g.x = g.originalX;
+ g.eval(expr + ";\n" +
+ "log += 'y';\n");
+ assertEq(g.log, 'dxsy');
+}
+
+check("'' + x");
+check("0 + x");
+check("0 - x");
+check("0 * x");
+check("0 / x");
+check("0 % x");
+check("+x");
+check("x in {}");
+check("x++");
+check("++x");
+check("x--");
+check("--x");
+check("x < 0");
+check("x > 0");
+check("x >= 0");
+check("x <= 0");
+check("x == 0");
+check("x != 0");
+check("x & 1");
+check("x | 1");
+check("x ^ 1");
+check("~x");
+check("x << 1");
+check("x >> 1");
+check("x >>> 1");
+
+g.eval("var getter = { get x() { debugger; return log += 'x'; } }");
+check("getter.x");
+
+g.eval("var setter = { set x(v) { debugger; return log += 'x'; } }");
+check("setter.x = 1");
+
+g.eval("Object.defineProperty(this, 'thisgetter', { get: function() { debugger; log += 'x'; }});");
+check("thisgetter");
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-07.js b/js/src/jit-test/tests/debug/Frame-onStep-07.js
new file mode 100644
index 0000000000..de8ce4e7c0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-07.js
@@ -0,0 +1,23 @@
+// The tracejit does not interfere with frame.onStep.
+//
+// The function f() writes 'L' to the log in a loop. If we enable stepping and
+// write an 's' each time frame.onStep is called, any two Ls should have at
+// least one 's' between them.
+
+var g = newGlobal({newCompartment: true});
+g.N = 11;
+g.log = '';
+g.eval("function f() {\n" +
+ " for (var i = 0; i <= N; i++)\n" +
+ " log += 'L';\n" +
+ "}\n");
+g.f();
+assertEq(/LL/.exec(g.log) !== null, true);
+
+var dbg = Debugger(g);
+dbg.onEnterFrame = function (frame) {
+ frame.onStep = function () { g.log += 's'; };
+};
+g.log = '';
+g.f();
+assertEq(/LL/.exec(g.log), null);
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-08.js b/js/src/jit-test/tests/debug/Frame-onStep-08.js
new file mode 100644
index 0000000000..857c94a50e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-08.js
@@ -0,0 +1,29 @@
+// frame.onStep can coexist with breakpoints.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var log = '';
+dbg.onEnterFrame = function (frame) {
+ var handler = {hit: function () { log += 'B'; }};
+ var lines = frame.script.getAllOffsets();
+ for (var line in lines) {
+ line = Number(line);
+ var offs = lines[line];
+ for (var i = 0; i < offs.length; i++)
+ frame.script.setBreakpoint(offs[i], handler);
+ }
+
+ frame.onStep = function () { log += 's'; };
+};
+
+g.eval("one = 1;\n" +
+ "two = 2;\n" +
+ "three = 3;\n" +
+ "four = 4;\n");
+assertEq(g.four, 4);
+
+// Breakpoints hit on all four lines, plus the final line.
+assertEq(log.replace(/[^B]/g, ''), 'BBBBB');
+
+// onStep was called between each pair of breakpoints.
+assertEq(/BB/.exec(log), null);
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-09.js b/js/src/jit-test/tests/debug/Frame-onStep-09.js
new file mode 100644
index 0000000000..1204ac21a9
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-09.js
@@ -0,0 +1,24 @@
+// After an implicit toString call throws an exception, the calling frame's
+// onStep hook fires.
+
+var g = newGlobal({newCompartment: true});
+g.eval("var x = {toString: function () { debugger; log += 'x'; throw 'mud'; }};");
+
+var dbg = Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+ g.log += 'd';
+ frame.older.onStep = function () {
+ if (!g.log.match(/[sy]$/))
+ g.log += 's';
+ };
+};
+
+g.log = '';
+g.eval("try { x + ''; } catch (x) { }\n" +
+ "log += 'y';\n");
+assertEq(g.log, "dxsy");
+
+g.log = '';
+g.eval("try { '' + x; } catch (x) { }\n" +
+ "log += 'y';\n");
+assertEq(g.log, "dxsy");
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-10.js b/js/src/jit-test/tests/debug/Frame-onStep-10.js
new file mode 100644
index 0000000000..758f64f1a3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-10.js
@@ -0,0 +1,28 @@
+// Throwing and catching an error in an onStep handler shouldn't interfere
+// with throwing and catching in the debuggee.
+
+var g = newGlobal({newCompartment: true});
+g.eval("function f() { debugger; throw 'mud'; }");
+
+var dbg = Debugger(g);
+var stepped = false;
+dbg.onDebuggerStatement = function (frame) {
+ frame.older.onStep = function () {
+ stepped = true;
+ try {
+ throw 'snow';
+ } catch (x) {
+ assertEq(x, 'snow');
+ }
+ };
+};
+
+stepped = false;
+g.eval("var caught;\n" +
+ "try {\n" +
+ " f();\n" +
+ "} catch (x) {\n" +
+ " caught = x;\n" +
+ "}\n");
+assertEq(stepped, true);
+assertEq(g.caught, 'mud');
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-11.js b/js/src/jit-test/tests/debug/Frame-onStep-11.js
new file mode 100644
index 0000000000..15dd847379
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-11.js
@@ -0,0 +1,36 @@
+// Stepping out of a finally should not appear to
+// step backward to some earlier statement.
+
+var g = newGlobal({newCompartment: true});
+g.eval(`function f() {
+ debugger; // +0
+ var x = 0; // +1
+ try { // +2
+ x = 1; // +3
+ throw 'something'; // +4
+ } catch (e) { // +5
+ x = 2; // +6
+ } finally { // +7
+ x = 3; // +8
+ } // +9
+ x = 4; // +10
+ }`); // +11
+
+var dbg = Debugger(g);
+
+let foundLines = '';
+
+dbg.onDebuggerStatement = function(frame) {
+ let debugLine = frame.script.getOffsetLocation(frame.offset).lineNumber;
+ frame.onStep = function() {
+ // Only record a stop when the offset is an entry point.
+ let foundLine = this.script.getOffsetLocation(this.offset).lineNumber;
+ if (foundLine != debugLine && this.script.getLineOffsets(foundLine).indexOf(this.offset) >= 0) {
+ foundLines += "," + (foundLine - debugLine);
+ }
+ };
+};
+
+g.f();
+
+assertEq(foundLines, ",1,2,3,4,5,6,7,8,10,11");
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-12.js b/js/src/jit-test/tests/debug/Frame-onStep-12.js
new file mode 100644
index 0000000000..c751c278a5
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-12.js
@@ -0,0 +1,118 @@
+// Check that stepping doesn't make it look like unreachable code is running.
+
+// Because our script source notes record only those bytecode offsets
+// at which source positions change, the default behavior in the
+// absence of a source note is to attribute a bytecode instruction to
+// the same source location as the preceding instruction. When control
+// flows from the preceding bytecode to the one we're emitting, that's
+// usually plausible. But successors in the bytecode stream are not
+// necessarily successors in the control flow graph. If the preceding
+// bytecode was a back edge of a loop, or the jump at the end of a
+// 'then' clause, its source position can be completely unrelated to
+// that of its successor.
+//
+// We try to avoid showing such nonsense offsets to the user by
+// requiring breakpoints and single-stepping to stop only at a line's
+// entry points, as reported by Debugger.Script.prototype.getLineOffsets;
+// and then ensuring that those entry points are all offsets mentioned
+// explicitly in the source notes, and hence deliberately attributed
+// to the given bytecode.
+//
+// This bit of JavaScript compiles to bytecode ending in a branch
+// instruction whose source position is the body of an unreachable
+// loop. The first instruction of the bytecode we emit following it
+// will inherit this nonsense position, if we have not explicitly
+// emitted a source note for said instruction.
+//
+// This test steps across such code and verifies that control never
+// appears to enter the unreachable loop.
+
+var bitOfCode = `debugger; // +0
+ if(false) { // +1
+ for(var b=0; b<0; b++) { // +2
+ c = 2 // +3
+ } // +4
+ }`; // +5
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+
+g.eval("function nothing() { }\n");
+
+var log = '';
+dbg.onDebuggerStatement = function(frame) {
+ let debugLine = frame.script.getOffsetLocation(frame.offset).lineNumber;
+ frame.onStep = function() {
+ let foundLine = this.script.getOffsetLocation(this.offset).lineNumber;
+ if (this.script.getLineOffsets(foundLine).indexOf(this.offset) >= 0) {
+ log += (foundLine - debugLine).toString(16);
+ }
+ };
+};
+
+function testOne(name, body, expected) {
+ print(name);
+ log = '';
+ g.eval(`function ${name} () { ${body} }`);
+ g.eval(`${name}();`);
+ assertEq(log, expected);
+}
+
+
+
+// Test the instructions at the end of a "try".
+testOne("testTryFinally",
+ `try {
+ ${bitOfCode}
+ } finally { // +6
+ } // +7
+ nothing(); // +8
+ `, "1689");
+
+// The same but without a finally clause.
+testOne("testTryCatch",
+ `try {
+ ${bitOfCode}
+ } catch (e) { // +6
+ } // +7
+ nothing(); // +8
+ `, "189");
+
+// Test the instructions at the end of a "catch".
+testOne("testCatchFinally",
+ `try {
+ throw new TypeError();
+ } catch (e) {
+ ${bitOfCode}
+ } finally { // +6
+ } // +7
+ nothing(); // +8
+ `, "1689");
+
+// Test the instruction at the end of a "finally" clause.
+testOne("testFinally",
+ `try {
+ } finally {
+ ${bitOfCode}
+ } // +6
+ nothing(); // +7
+ `, "178");
+
+// Test the instruction at the end of a "then" clause.
+testOne("testThen",
+ `if (1 === 1) {
+ ${bitOfCode}
+ } else { // +6
+ } // +7
+ nothing(); // +8
+ `, "189");
+
+// Test the instructions leaving a switch block.
+testOne("testSwitch",
+ `var x = 5;
+ switch (x) {
+ case 5:
+ ${bitOfCode}
+ } // +6
+ nothing(); // +7
+ `, "178");
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-13.js b/js/src/jit-test/tests/debug/Frame-onStep-13.js
new file mode 100644
index 0000000000..b2d4a3c1b1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-13.js
@@ -0,0 +1,29 @@
+// Stepping over a not-taken "if" that is at the end of the function
+// should move to the end of the function, not somewhere in the body
+// of the "if".
+
+var g = newGlobal({newCompartment: true});
+g.eval(`function f() { // 1
+ var a,c; // 2
+ debugger; // 3
+ if(false) { // 4
+ for(var b=0; b<0; b++) { // 5
+ c = 2; // 6
+ } // 7
+ } // 8
+} // 9
+`);
+
+var dbg = Debugger(g);
+var badStep = false;
+
+dbg.onDebuggerStatement = function(frame) {
+ let debugLine = frame.script.getOffsetLocation(frame.offset).lineNumber;
+ assertEq(debugLine, 3);
+ frame.onStep = function() {
+ let foundLine = this.script.getOffsetLocation(this.offset).lineNumber;
+ assertEq(foundLine <= 4 || foundLine >= 8, true);
+ };
+};
+
+g.eval("f();\n");
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-14.js b/js/src/jit-test/tests/debug/Frame-onStep-14.js
new file mode 100644
index 0000000000..a7139adb09
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-14.js
@@ -0,0 +1,46 @@
+// Test how stepping interacts with switch statements.
+
+var g = newGlobal({newCompartment: true});
+
+g.eval('function bob() { return "bob"; }');
+
+// Stepping into a sparse switch should not stop on literal cases.
+evaluate(`function lit(x) { // 1
+ debugger; // 2
+ switch(x) { // 3
+ case "nope": // 4
+ break; // 5
+ case "bob": // 6
+ break; // 7
+ } // 8
+}`, {lineNumber: 1, global: g});
+
+// Stepping into a sparse switch should stop on non-literal cases.
+evaluate(`function nonlit(x) { // 1
+ debugger; // 2
+ switch(x) { // 3
+ case bob(): // 4
+ break; // 5
+ } // 6
+}`, {lineNumber: 1, global: g});
+
+var dbg = Debugger(g);
+var badStep = false;
+
+function test(s, okLine) {
+ dbg.onDebuggerStatement = function(frame) {
+ frame.onStep = function() {
+ let thisLine = this.script.getOffsetLocation(this.offset).lineNumber;
+ // The stop at line 3 is the switch.
+ if (thisLine > 3) {
+ assertEq(thisLine, okLine)
+ frame.onStep = undefined;
+ }
+ };
+ };
+ g.eval(s);
+}
+
+test("lit('bob');", 7);
+
+test("nonlit('bob');", 4);
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-15.js b/js/src/jit-test/tests/debug/Frame-onStep-15.js
new file mode 100644
index 0000000000..819b0e4b6c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-15.js
@@ -0,0 +1,43 @@
+// Test how stepping interacts with for(;;) statements.
+
+let g = newGlobal({newCompartment: true});
+
+// We want a for(;;) loop whose body is evaluated at least once, to
+// see whether the loop head is hit a second time.
+g.eval(`function f() {
+ let x = 0;
+ debugger; // +0
+ for(;;) { // +1
+ if (x++ == 1) break; // +2
+ } // +3
+ debugger; // +4
+}`);
+
+let dbg = Debugger(g);
+
+function test(s, expected) {
+ let result = '';
+
+ dbg.onDebuggerStatement = function(frame) {
+ // On the second debugger statement, we're done.
+ dbg.onDebuggerStatement = function(frame) {
+ frame.onStep = undefined;
+ };
+
+ let debugLine = frame.script.getOffsetLocation(frame.offset).lineNumber;
+ frame.onStep = function() {
+ // Only examine stops at entry points for the line.
+ let lineNo = this.script.getOffsetLocation(this.offset).lineNumber;
+ if (this.script.getLineOffsets(lineNo).indexOf(this.offset) < 0) {
+ return undefined;
+ }
+
+ let delta = this.script.getOffsetLocation(this.offset).lineNumber - debugLine;
+ result += delta;
+ };
+ };
+ g.eval(s);
+ assertEq(result, expected);
+}
+
+test('f()', '2124');
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-16.js b/js/src/jit-test/tests/debug/Frame-onStep-16.js
new file mode 100644
index 0000000000..2acf76951d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-16.js
@@ -0,0 +1,34 @@
+// Stepping through a function with a return statement should pause on
+// the closing brace of the function.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var log;
+
+function test(fnStr) {
+ log = '';
+ g.eval(fnStr);
+
+ dbg.onDebuggerStatement = function(frame) {
+ frame.onStep = function() {
+ let {lineNumber, isEntryPoint} = frame.script.getOffsetLocation(frame.offset);
+ if (isEntryPoint) {
+ log += lineNumber + ' ';
+ }
+ };
+ };
+
+ g.eval("f(23);");
+}
+
+test("function f(x) {\n" + // 1
+ " debugger;\n" + // 2
+ " return 23 + x;\n" + // 3
+ "}\n"); // 4
+assertEq(log, '3 4 ');
+
+test("function f(x) {\n" + // 1
+ " debugger;\n" + // 2
+ " return;\n" + // 3
+ "}\n"); // 4
+assertEq(log, '3 4 ');
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-17.js b/js/src/jit-test/tests/debug/Frame-onStep-17.js
new file mode 100644
index 0000000000..a73bd2187c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-17.js
@@ -0,0 +1,35 @@
+// Test how stepping interacts with for-in/of statements.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+var log;
+var previous;
+
+dbg.onDebuggerStatement = function (frame) {
+ let debugLine = frame.script.getOffsetLocation(frame.offset).lineNumber;
+ log = '';
+ previous = '';
+ frame.onStep = function() {
+ let foundLine = this.script.getOffsetLocation(this.offset).lineNumber;
+ if (this.script.getLineOffsets(foundLine).indexOf(this.offset) >= 0) {
+ let thisline = (foundLine - debugLine).toString(16);
+ if (thisline !== previous) {
+ log += thisline;
+ previous = thisline;
+ }
+ }
+ };
+};
+
+function testOne(decl, loopKind) {
+ let body = "var array = [2, 4, 6];\ndebugger;\nfor (" + decl + " iter " +
+ loopKind + " array) {\n print(iter);\n}\n";
+ g.eval(body);
+ assertEq(log, "12121214");
+}
+
+for (let decl of ["", "var", "let"]) {
+ testOne(decl, "in");
+ testOne(decl, "of");
+}
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-18.js b/js/src/jit-test/tests/debug/Frame-onStep-18.js
new file mode 100644
index 0000000000..0484a45cf7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-18.js
@@ -0,0 +1,22 @@
+// Regression test for bug 1370648.
+
+let g = newGlobal({newCompartment: true});
+
+let dbg = Debugger(g);
+let lines = [0, 0, 0, 0, 0];
+dbg.onDebuggerStatement = function (frame) {
+ let dLine = frame.script.getOffsetLocation(frame.offset).lineNumber;
+ lines[0] = 1;
+ frame.onStep = function () {
+ lines[frame.script.getOffsetLocation(this.offset).lineNumber - dLine] = 1;
+ };
+}
+
+let s = `
+ debugger; // 0
+ if (1 !== 1) { // 1
+ print("dead code!?"); // 2
+ } // 3
+`;
+g.eval(s);
+assertEq(lines.join(""), "11001");
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-19.js b/js/src/jit-test/tests/debug/Frame-onStep-19.js
new file mode 100644
index 0000000000..019c026a8e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-19.js
@@ -0,0 +1,41 @@
+// Stepping should ignore nested function declarations.
+
+// Nested functions are hoisted to the top of the function body,
+// so technically the first thing that happens when you call the outer function
+// is that each inner function is created and bound to a local variable.
+// But users don't actually want to see that happen when they're stepping.
+// It's super confusing.
+
+load(libdir + "stepping.js");
+
+testStepping(
+ `\
+ (function() { // line 1
+ let x = 1; // line 2
+ funcb("funcb"); // line 3
+ function funcb(msg) { // line 4
+ console.log(msg)
+ }
+ }) // line 7
+ `,
+ [1, 2, 3, 7]);
+
+// Stopping at the ClassDeclaration on line 8 is fine. For that matter,
+// stopping on line 5 wouldn't be so bad if we did it after line 3 and before
+// line 8; alas, the actual order of execution is 5, 2, 3, 8... which is too
+// confusing.
+testStepping(
+ `\
+ function f() { // 1
+ var x = 0; // 2
+ a(); // 3
+
+ function a() { // 5
+ x += 1; // 6
+ } // 7
+ class Car {} // 8
+ return x; // 9
+ } // 10
+ f
+ `,
+ [1, 2, 3, 8, 9, 10]);
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-20.js b/js/src/jit-test/tests/debug/Frame-onStep-20.js
new file mode 100644
index 0000000000..e070037c36
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-20.js
@@ -0,0 +1,41 @@
+// Stepping should always pause in a frame between two function calls.
+
+let g = newGlobal({newCompartment: true});
+g.evaluate(`
+ class X {
+ constructor() { this._p = 0; }
+ m() { return this; }
+ get p() { return this._p; }
+ set p(value) { this._p = value; }
+ }
+ let x = new X;
+
+ function f() { return 1; }
+ function inc(x) { return x + 1; }
+`);
+
+let dbg = Debugger(g);
+
+// `code` is a snippet of JS that performs two JS calls.
+function test(code) {
+ let hits = 0;
+ let log = "";
+ dbg.onEnterFrame = frame => {
+ if (hits++ === 0)
+ frame.onStep = () => { log += "s"; };
+ else
+ log += "E";
+ };
+
+ g.eval(code);
+ assertEq(log.includes("EE"), false, "should have received onStep between onEnterFrame events");
+ assertEq(log.match(/^s+Es+Es*$/) !== null, true,
+ "should get two calls, with steps before, between, and possibly after");
+}
+
+test("f(); f();");
+test("f() + f()");
+test("inc(f())");
+test("x.m().m()");
+test("new X().m()");
+test("x.p = x.p"); // getter, then setter
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-assign-function.js b/js/src/jit-test/tests/debug/Frame-onStep-assign-function.js
new file mode 100644
index 0000000000..5125359292
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-assign-function.js
@@ -0,0 +1,49 @@
+// Changing onStep while the function is dead is allowed.
+
+load(libdir + "asserts.js");
+
+const g = newGlobal({ newCompartment: true });
+const dbg = new Debugger(g);
+
+let steps = new Set();
+dbg.onDebuggerStatement = function(frame) {
+ // Setting 'onStep' while alive is allowed.
+ steps.add("debugger 1");
+ assertEq(frame.onStack, true);
+ frame.onStep = function() {
+ steps.add("onstep 1");
+ };
+
+ dbg.onDebuggerStatement = function() {
+ // Clear the 'onStep' while dead.
+ steps.add("debugger 2");
+ assertEq(frame.onStack, false);
+
+ // Clearing 'onStep' while dead is allowed.
+ frame.onStep = undefined;
+
+ // Setting 'onStep' while dead is allowed.
+ frame.onStep = function() {
+ steps.add("onstep 2");
+ };
+ };
+};
+
+g.eval(
+ `
+ function myGen() {
+ debugger;
+ print("make sure we have a place to step to inside the frame");
+ }
+
+ const g = myGen();
+
+ debugger;
+ `
+);
+
+assertDeepEq(Array.from(steps), [
+ "debugger 1",
+ "onstep 1",
+ "debugger 2",
+]);
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-assign-generator.js b/js/src/jit-test/tests/debug/Frame-onStep-assign-generator.js
new file mode 100644
index 0000000000..2a733f3a83
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-assign-generator.js
@@ -0,0 +1,68 @@
+// Changing onStep while the generator is suspended/dead is allowed.
+
+load(libdir + "asserts.js");
+
+const g = newGlobal({ newCompartment: true });
+const dbg = new Debugger(g);
+
+let steps = new Set();
+dbg.onDebuggerStatement = function(frame) {
+ // Setting 'onStep' while alive is allowed.
+ steps.add("debugger 1");
+ assertEq(frame.onStack, true);
+ frame.onStep = function() {
+ steps.add("onstep 1");
+ };
+
+ dbg.onDebuggerStatement = function() {
+ // Clear the 'onStep' while suspended.
+ steps.add("debugger 2");
+ assertEq(frame.onStack, false);
+
+ // Clearing 'onStep' while suspended is allowed.
+ frame.onStep = undefined;
+
+ // Setting 'onStep' while suspended is allowed.
+ frame.onStep = function() {
+ steps.add("onstep 2");
+ };
+
+ dbg.onDebuggerStatement = function() {
+ steps.add("debugger 3");
+ assertEq(frame.onStack, false);
+
+ // Clearing 'onStep' while dead is allowed.
+ frame.onStep = undefined;
+
+ // Setting 'onStep' while dead is allowed.
+ frame.onStep = function() {
+ steps.add("onstep 3");
+ };
+ };
+ };
+};
+
+g.eval(
+ `
+ function* myGen() {
+ debugger;
+ yield;
+ }
+
+ const g = myGen();
+ g.next();
+
+ debugger;
+ g.next();
+
+ debugger;
+ `
+);
+
+assertDeepEq(Array.from(steps), [
+ "debugger 1",
+ "onstep 1",
+ "debugger 2",
+ "onstep 2",
+ "debugger 3",
+]);
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-async-01.js b/js/src/jit-test/tests/debug/Frame-onStep-async-01.js
new file mode 100644
index 0000000000..4490524f2c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-async-01.js
@@ -0,0 +1,35 @@
+// Stepping works across `await` in async functions.
+
+// Set up debuggee.
+var g = newGlobal({newCompartment: true});
+g.log = "";
+g.eval(` // line 1
+async function aloop() { // 2
+ for (let i = 0; i < 3; i++) { // 3
+ await i; // 4
+ log += " "; // 5
+ } // 6
+ log += "^"; // 7
+}
+`);
+
+// Set up debugger.
+let previousLine = -1;
+let dbg = new Debugger(g);
+dbg.onEnterFrame = frame => {
+ frame.onStep = function () {
+ assertEq(this, frame);
+ let line = frame.script.getOffsetLocation(frame.offset).lineNumber;
+ if (previousLine != line) {
+ g.log += line; // We stepped to a new line.
+ previousLine = line;
+ }
+ };
+ dbg.onEnterFrame = undefined;
+};
+
+// Run.
+g.aloop();
+drainJobQueue();
+
+assertEq(g.log, "2345 345 345 37^");
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-async-02.js b/js/src/jit-test/tests/debug/Frame-onStep-async-02.js
new file mode 100644
index 0000000000..33dcdf9ba0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-async-02.js
@@ -0,0 +1,87 @@
+// With enough hackery, stepping in and out of async functions can be made to
+// work as users expect.
+//
+// This test exercises the common case when we have syntactically `await
+// $ASYNC_FN($ARGS)` so that the calls nest as if they were synchronous
+// calls. It works, but there's a problem.
+//
+// onStep fires in extra places that end users would find very confusing--see
+// the comment marked (!) below. As a result, Debugger API consumers must do
+// some extra work to skip pausing there. This test is a proof of concept that
+// shows what sort of effort is needed. It maintains a single `asyncStack` and
+// skips the onStep hook if we're not running the function at top of the async
+// stack. Real debuggers would have to maintain multiple async stacks.
+
+// Set up debuggee.
+var g = newGlobal({newCompartment: true});
+g.eval(`\
+async function outer() { // line 1
+ return (await inner()) + (await inner()) + "!"; // 2
+} // 3
+async function inner() { // 4
+ return (await leaf()) + (await leaf()); // 5
+} // 6
+async function leaf() { // 7
+ return (await Promise.resolve("m")); // 8
+} // 9
+`);
+
+// Set up debugger.
+let previousLine = -1;
+let dbg = new Debugger(g);
+let log = "";
+let asyncStack = [];
+
+dbg.onEnterFrame = frame => {
+ assertEq(frame.type, "call");
+
+ // If we're entering this frame for the first time, push it to the async
+ // stack.
+ if (!frame.seen) {
+ frame.seen = true;
+ asyncStack.push(frame);
+ log += "(";
+ }
+
+ frame.onStep = () => {
+ // When stepping, we sometimes pause at opcodes in older frames (!)
+ // where all that's happening is async function administrivia.
+ //
+ // For example, the first time `leaf()` yields, `inner()` and
+ // `outer()` are still on the stack; they haven't awaited yet because
+ // control has not returned from `leaf()` to them yet. So stepping will
+ // hop from line 8 to line 5 to line 2 as we unwind the stack, then
+ // resume on line 8.
+ //
+ // Anyway: skip that noise.
+ if (frame !== asyncStack[asyncStack.length - 1])
+ return;
+
+ let line = frame.script.getOffsetLocation(frame.offset).lineNumber;
+ if (previousLine != line) {
+ log += line; // We stepped to a new line.
+ previousLine = line;
+ }
+ };
+
+ frame.onPop = completion => {
+ // Popping the frame. But async function frames are popped multiple
+ // times: for the "initial suspend", at each await, and on return. The
+ // debugger offers no easy way to distinguish them (bug 1470558).
+ // For now there's an "await" property, but bug 1470558 may come up
+ // with a different solution, so don't rely on it!
+ if (!completion.await) {
+ // Returning (not awaiting or at initial suspend).
+ assertEq(asyncStack.pop(), frame);
+ log += ")";
+ }
+ };
+};
+
+// Run.
+let result;
+g.outer().then(v => { result = v; });
+drainJobQueue();
+
+assertEq(result, "mmmm!");
+assertEq(log, "(12(45(789)5(789)56)2(45(789)5(789)56)23)");
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-async-03.js b/js/src/jit-test/tests/debug/Frame-onStep-async-03.js
new file mode 100644
index 0000000000..ecbd0ddf2c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-async-03.js
@@ -0,0 +1,18 @@
+// Bug 1501666: assertions about the script's step mode count must take
+// suspended calls into account. This should not crash.
+
+var g = newGlobal({ newCompartment: true });
+g.eval(`
+ async function f(y) {
+ await true;
+ await true;
+ };
+`);
+
+g.f();
+g.f();
+
+var dbg = Debugger(g);
+dbg.onEnterFrame = function(frame) {
+ frame.onStep = function() {}
+}
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-async-gc-01.js b/js/src/jit-test/tests/debug/Frame-onStep-async-gc-01.js
new file mode 100644
index 0000000000..6ff59d6ce8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-async-gc-01.js
@@ -0,0 +1,28 @@
+// An onStep handler on a suspended async function frame keeps a Debugger alive.
+
+let g = newGlobal({newCompartment: true});
+g.eval(`
+ async function f() {
+ debugger;
+ await Promise.resolve(0);
+ return 'ok';
+ }
+`);
+
+let dbg = Debugger(g);
+let hit = false;
+dbg.onDebuggerStatement = frame => {
+ frame.onPop = completion => {
+ frame.onStep = () => { hit = true; };
+ frame.onPop = undefined;
+ };
+ dbg.onDebuggerStatement = undefined;
+ dbg = null;
+};
+
+g.f();
+assertEq(dbg, null);
+gc();
+assertEq(hit, false);
+drainJobQueue();
+assertEq(hit, true);
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-generator-resumption-01.js b/js/src/jit-test/tests/debug/Frame-onStep-generator-resumption-01.js
new file mode 100644
index 0000000000..ad61ccd9e4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-generator-resumption-01.js
@@ -0,0 +1,42 @@
+// The debugger can't force return from a generator before the initial yield.
+
+let g = newGlobal({newCompartment: true});
+g.eval(`
+ function* f() {
+ yield 1;
+ }
+`);
+
+let dbg = new Debugger(g);
+let steps = 0;
+let uncaughtErrorsReported = 0;
+dbg.onEnterFrame = frame => {
+ assertEq(frame.callee.name, "f");
+ dbg.onEnterFrame = undefined;
+ frame.onStep = () => {
+ steps++;
+
+ // This test case never resumes the generator after the initial
+ // yield. Therefore the initial yield has not happened yet. So this
+ // force-return will be an error.
+ return {return: "ponies"};
+ };
+
+ // Having an onPop hook exercises some assertions that don't happen
+ // otherwise.
+ frame.onPop = completion => {};
+};
+
+dbg.uncaughtExceptionHook = (reason) => {
+ // When onEnterFrame returns an invalid resumption value,
+ // the error is reported here.
+ assertEq(reason instanceof TypeError, true);
+ uncaughtErrorsReported++;
+ return undefined; // Cancel the force-return. Let the debuggee continue.
+};
+
+let result = g.f();
+assertEq(result instanceof g.f, true);
+
+assertEq(steps > 0, true);
+assertEq(uncaughtErrorsReported, steps);
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-generator-resumption-02.js b/js/src/jit-test/tests/debug/Frame-onStep-generator-resumption-02.js
new file mode 100644
index 0000000000..25f9186c77
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-generator-resumption-02.js
@@ -0,0 +1,78 @@
+// Like Frame-onStep-generator-resumption-01.js, but bail out by throwing.
+
+let g = newGlobal({newCompartment: true});
+g.eval(`
+ function* f() {
+ yield 1;
+ }
+`);
+
+// Try force-returning from one of the instructions in `f` before the initial
+// yield. In detail:
+//
+// * This test calls `g.f()` under the Debugger.
+// * It uses the Debugger to step `ttl` times.
+// If we reach the initial yield before stepping the `ttl`th time, we're done.
+// * Otherwise, the test tries to force-return from `f`.
+// * That's an error, so the uncaughtExceptionHook is called.
+// * The uncaughtExceptionHook returns a `throw` completion value.
+//
+// Returns `true` if we reached the initial yield, false otherwise.
+//
+// Note that this function is called in a loop so that every possible relevant
+// value of `ttl` is tried once.
+function test(ttl) {
+ let dbg = new Debugger(g);
+ let exiting = false; // we ran out of time-to-live and have forced return
+ let done = false; // we reached the initial yield without forced return
+ let reported = false; // a TypeError was reported.
+
+ dbg.onEnterFrame = frame => {
+ assertEq(frame.callee.name, "f");
+ dbg.onEnterFrame = undefined;
+ frame.onStep = () => {
+ if (ttl == 0) {
+ exiting = true;
+ // This test case never resumes the generator after the initial
+ // yield. Therefore the initial yield has not happened yet. So this
+ // force-return will be an error.
+ return {return: "ponies"};
+ }
+ ttl--;
+ };
+ frame.onPop = completion => {
+ if (!exiting)
+ done = true;
+ };
+ };
+
+ dbg.uncaughtExceptionHook = (exc) => {
+ // When onStep returns an invalid resumption value,
+ // the error is reported here.
+ assertEq(exc instanceof TypeError, true);
+ reported = true;
+ return {throw: "FAIL"}; // Bail out of the test.
+ };
+
+ let result;
+ let caught = undefined;
+ try {
+ result = g.f();
+ } catch (exc) {
+ caught = exc;
+ }
+
+ if (done) {
+ assertEq(reported, false);
+ assertEq(result instanceof g.f, true);
+ assertEq(caught, undefined);
+ } else {
+ assertEq(reported, true);
+ assertEq(caught, "FAIL");
+ }
+
+ dbg.enabled = false;
+ return done;
+}
+
+for (let ttl = 0; !test(ttl); ttl++) {}
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-generator-resumption-03.js b/js/src/jit-test/tests/debug/Frame-onStep-generator-resumption-03.js
new file mode 100644
index 0000000000..3b2e8d2b3a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-generator-resumption-03.js
@@ -0,0 +1,49 @@
+// Don't crash on {return:} from onStep in a generator, before the initial suspend.
+
+// This test tries to force-return from each bytecode instruction in a
+// generator, up to the initial suspend.
+
+load(libdir + "asserts.js");
+
+let g = newGlobal({newCompartment: true});
+g.values = [1, 2, 3];
+g.eval(`function* f(arr=values) { yield* arr; }`);
+
+let dbg = Debugger(g);
+
+function test(ttl) {
+ let hits = 0;
+ dbg.onEnterFrame = frame => {
+ assertEq(frame.callee.name, "f");
+ frame.onStep = () => {
+ if (--ttl === 0)
+ return {return: 123};
+ };
+ };
+ dbg.uncaughtExceptionHook = exc => {
+ return {throw: "debugger error: " + exc};
+ };
+
+ let val = undefined;
+ let caught = undefined;
+ try {
+ val = g.f();
+ } catch (exc) {
+ caught = exc;
+ }
+
+ if (val === undefined) {
+ // Tried to force-return before the initial suspend.
+ assertEq(caught, "debugger error: TypeError: can't force return from a generator before the initial yield");
+ assertEq(ttl, 0);
+ return "pass";
+ } else {
+ // Reached the initial suspend without forcing a return.
+ assertEq(typeof val, "object");
+ assertEq(val instanceof g.f, true);
+ assertEq(ttl, 1);
+ return "done";
+ }
+}
+
+for (let i = 1; test(i) === "pass"; i++) {}
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-generators-01.js b/js/src/jit-test/tests/debug/Frame-onStep-generators-01.js
new file mode 100644
index 0000000000..7fbf26a94d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-generators-01.js
@@ -0,0 +1,31 @@
+// Stepping into the `.next()` method of a generator works as expected.
+
+let g = newGlobal({newCompartment: true});
+g.eval(`\
+function* nums() { // line 1
+ yield 1; // 2
+ yield 2; // 3
+} // 4
+function f() { // 5
+ let gen = nums(); // 6
+ gen.next(); // 7
+ gen.next(); // 8
+ gen.next(); // 9
+} // 10
+`);
+
+let log = [];
+let previousLine = -1;
+let dbg = new Debugger(g);
+dbg.onEnterFrame = frame => {
+ frame.onStep = () => {
+ let line = frame.script.getOffsetLocation(frame.offset).lineNumber;
+ if (previousLine != line) { // We stepped to a new line.
+ log.push(line);
+ previousLine = line;
+ }
+ };
+};
+
+g.f();
+assertEq(log.join(" "), "5 6 1 6 7 1 2 7 8 2 3 8 9 3 9 10");
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-generators-02.js b/js/src/jit-test/tests/debug/Frame-onStep-generators-02.js
new file mode 100644
index 0000000000..3e1fa6a433
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-generators-02.js
@@ -0,0 +1,44 @@
+// Stepping into the `.throw()` method of a generator with no relevant catch block.
+//
+// The debugger fires onEnterFrame and then frame.onPop for the generator frame when
+// `gen.throw()` is called.
+
+load(libdir + "asserts.js");
+
+let g = newGlobal({newCompartment: true});
+g.eval(`\
+function* z() { // line 1
+ yield 1; // 2
+ yield 2; // 3
+} // 4
+function f() { // 5
+ let gen = z(); // 6
+ gen.next(); // 7
+ gen.throw("fit"); // 8
+} // 9
+`);
+
+let log = "";
+let previousLine = -1;
+let dbg = new Debugger(g);
+dbg.onEnterFrame = frame => {
+ log += frame.callee.name + "{";
+ frame.onStep = () => {
+ let line = frame.script.getOffsetLocation(frame.offset).lineNumber;
+ if (previousLine != line) { // We stepped to a new line.
+ log += line;
+ previousLine = line;
+ }
+ };
+ frame.onPop = completion => {
+ if ("throw" in completion)
+ log += "!";
+ log += "}";
+ }
+};
+
+assertThrowsValue(() => g.f(), "fit");
+// z{1} is the initial generator setup.
+// z{12} is the first .next() call, running to `yield 1` on line 2
+// The final `z{2!}` is for the .throw() call.
+assertEq(log, "f{56z{1}67z{12}78z{2!}!}");
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-generators-03.js b/js/src/jit-test/tests/debug/Frame-onStep-generators-03.js
new file mode 100644
index 0000000000..b9f6f5e5fd
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-generators-03.js
@@ -0,0 +1,45 @@
+// Stepping into the `.throw()` method of a generator with a relevant catch block.
+
+load(libdir + "asserts.js");
+
+let g = newGlobal({newCompartment: true});
+g.eval(`\
+function* z() { // line 1
+ try { // 2
+ yield 1; // 3
+ } catch (exc) { // 4
+ yield 2; // 5
+ } // 6
+} // 7
+function f() { // 8
+ let gen = z(); // 9
+ gen.next(); // 10
+ gen.throw("fit"); // 11
+} // 12
+`);
+
+let log = [];
+let previousLine = -1;
+let dbg = new Debugger(g);
+dbg.onEnterFrame = frame => {
+ log.push(frame.callee.name + " in");
+ frame.onStep = () => {
+ let line = frame.script.getOffsetLocation(frame.offset).lineNumber;
+ if (previousLine != line) { // We stepped to a new line.
+ log.push(line);
+ previousLine = line;
+ }
+ };
+ frame.onPop = completion => {
+ log.push(frame.callee.name + " out");
+ };
+};
+
+g.f();
+assertEq(
+ log.join(", "),
+ "f in, 8, 9, z in, 1, z out, " +
+ "9, 10, z in, 1, 2, 3, z out, " +
+ "10, 11, z in, 3, 2, 4, 5, z out, " + // not sure why we hit line 2 here, source notes bug maybe
+ "11, 12, f out"
+);
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-generators-04.js b/js/src/jit-test/tests/debug/Frame-onStep-generators-04.js
new file mode 100644
index 0000000000..ef33571a6b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-generators-04.js
@@ -0,0 +1,36 @@
+// Stepping works across `yield` in generators.
+
+// Set up debuggee.
+var g = newGlobal({newCompartment: true});
+g.log = "";
+g.eval(`
+function* range(stop) { // line 2
+ for (let i = 0; i < stop; i++) { // 3
+ yield i; // 4
+ log += " "; // 5
+ } // 6
+ log += "^"; // 7
+}
+`);
+
+// Set up debugger.
+let previousLine = -1;
+let dbg = new Debugger(g);
+dbg.onEnterFrame = frame => {
+ frame.onStep = function () {
+ assertEq(this, frame);
+ let line = frame.script.getOffsetLocation(frame.offset).lineNumber;
+ if (previousLine != line) {
+ g.log += line; // We stepped to a new line.
+ previousLine = line;
+ }
+ };
+ dbg.onEnterFrame = undefined;
+};
+
+// Run.
+for (let value of g.range(3)) {
+ g.log += "*";
+}
+
+assertEq(g.log, "234*5 34*5 34*5 37^");
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-generators-05.js b/js/src/jit-test/tests/debug/Frame-onStep-generators-05.js
new file mode 100644
index 0000000000..b4996ab217
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-generators-05.js
@@ -0,0 +1,14 @@
+// GC'ing a Debugger.Frame and its AbstractGeneratorObject adjusts the script's
+// stepper count.
+
+let g = newGlobal({newCompartment: true});
+g.eval(`function* f() {}`);
+for (let i = 0; i < 2; i++) {
+ let dbg = new Debugger(g);
+ dbg.onEnterFrame = frame => {
+ frame.onStep = () => {};
+ };
+ g.f();
+ dbg.onEnterFrame = undefined;
+ gc();
+}
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-generators-defaults.js b/js/src/jit-test/tests/debug/Frame-onStep-generators-defaults.js
new file mode 100644
index 0000000000..ea32cc0094
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-generators-defaults.js
@@ -0,0 +1,35 @@
+// onStep works during the evaluation of default parameter values in generators.
+//
+// (They're evaluated at a weird time in the generator life cycle, before the
+// generator object is created.)
+
+load(libdir + "asserts.js");
+
+let g = newGlobal({newCompartment: true});
+g.eval(`\
+ function f1() {} // line 1
+ function f2() {} // 2
+ function f3() {} // 3
+ // 4
+ function* gen( // 5
+ name, // 6
+ schema = f1(), // 7
+ timeToLive = f2(), // 8
+ lucidity = f3() // 9
+ ) { // 10
+ } // 11
+`);
+
+let dbg = Debugger(g);
+let log = [];
+dbg.onEnterFrame = frame => {
+ frame.onStep = () => {
+ let line = frame.script.getOffsetLocation(frame.offset).lineNumber;
+ if (log.length == 0 || line != log[log.length - 1]) {
+ log.push(line);
+ }
+ };
+};
+
+g.gen(0);
+assertDeepEq(log, [5, 7, 1, 8, 2, 9, 3, 10]);
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-generators-gc-01.js b/js/src/jit-test/tests/debug/Frame-onStep-generators-gc-01.js
new file mode 100644
index 0000000000..54a77260f3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-generators-gc-01.js
@@ -0,0 +1,84 @@
+// onStep hooks on suspended Frames can keep Debuggers alive, even chaining them.
+
+// The path through the heap we're building and testing here is:
+// gen0 (generator object) -> frame1 (suspended Frame with .onStep) -> dbg1 (Debugger object)
+// -> gen1 -> frame2 -> dbg2
+// where everything after `gen1` is otherwise unreachable, and the edges
+// `frame1 -> dbg1` and `frames2 -> dbg2` are due to the .onStep handlers, not
+// strong refrences.
+//
+// There is no easy way to thread an event through this whole path; when we
+// call gen0.next(), it will fire frame1.onStep(), but from there, making sure
+// gen1.next() is called requires some minor heroics (see the WeakMap below).
+
+var gen0;
+
+var hits2 = 0;
+var resuming2 = false;
+
+function onStep2() {
+ if (resuming2) {
+ hits2++;
+ resuming2 = false;
+ }
+}
+
+function setup() {
+ let g1 = newGlobal({newCompartment: true});
+ g1.eval(`
+ function* gf1() {
+ debugger;
+ yield 1;
+ return 'done';
+ }
+ `);
+ gen0 = g1.gf1();
+
+ let g2 = newGlobal({newCompartment: true});
+ g2.eval(`
+ function* gf2() { debugger; yield 1; return 'done'; }
+
+ var resuming1 = false;
+
+ function makeOnStepHook1(dbg1) {
+ // We use this WeakMap as a weak reference from frame1.onStep to dbg1.
+ var weak = new WeakMap();
+ weak.set(dbg1, {});
+ return () => {
+ if (resuming1) {
+ var dbg1Arr = nondeterministicGetWeakMapKeys(weak);
+ assertEq(dbg1Arr.length, 1);
+ dbg1Arr[0].gen1.next();
+ resuming1 = false;
+ }
+ };
+ }
+
+ function test(g1, gen0) {
+ let dbg1 = Debugger(g1);
+ dbg1.onDebuggerStatement = frame1 => {
+ frame1.onStep = makeOnStepHook1(dbg1);
+ dbg1.onDebuggerStatement = undefined;
+ };
+ gen0.next(); // run to yield point, creating frame1 and setting its onStep hook
+ resuming1 = true;
+ dbg1.gen1 = gf2();
+ return dbg1.gen1;
+ }
+ `);
+
+ let dbg2 = Debugger(g2);
+ dbg2.onDebuggerStatement = frame2 => {
+ frame2.onStep = onStep2;
+ dbg2.onDebuggerStatement = undefined;
+ };
+ var gen1 = g2.test(g1, gen0);
+ gen1.next(); // run to yield point, creating frame2 and setting its onStep hook
+ resuming2 = true;
+}
+
+setup();
+gc();
+assertEq(hits2, 0);
+gen0.next(); // fires frame1.onStep, which calls gen1.next(), which fires frame2.onStep
+assertEq(hits2, 1);
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-iterators.js b/js/src/jit-test/tests/debug/Frame-onStep-iterators.js
new file mode 100644
index 0000000000..2437abaab1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-iterators.js
@@ -0,0 +1,20 @@
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+var log;
+var a = [];
+
+dbg.onDebuggerStatement = function (frame) {
+ log += 'd';
+ frame.onStep = function () {
+ // This handler must not wipe out the debuggee's value in JSContext::iterValue.
+ log += 's';
+ // This will use JSContext::iterValue in the debugger.
+ for (let i of a)
+ log += 'i';
+ };
+};
+
+log = '';
+g.eval("debugger; for (let i of [1,2,3]) print(i);");
+assertEq(!!log.match(/^ds*$/), true);
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-lines-01.js b/js/src/jit-test/tests/debug/Frame-onStep-lines-01.js
new file mode 100644
index 0000000000..a32a0535da
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-lines-01.js
@@ -0,0 +1,78 @@
+// Test that a frame's onStep handler gets called at least once on each line of a function.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+// When we hit a 'debugger' statement, set offsets to the frame's script's
+// table of line offsets --- a sparse array indexed by line number. Begin
+// single-stepping the current frame; for each source line we hit, delete
+// the line's entry in offsets. Thus, at the end, offsets is an array with
+// an element for each line we did not reach.
+var doSingleStep = true;
+var offsets;
+dbg.onDebuggerStatement = function (frame) {
+ var script = frame.script;
+ offsets = script.getAllOffsets();
+ print("debugger line: " + script.getOffsetLocation(frame.offset).lineNumber);
+ print("original lines: " + JSON.stringify(Object.keys(offsets)));
+ if (doSingleStep) {
+ frame.onStep = function onStepHandler() {
+ var line = script.getOffsetLocation(this.offset).lineNumber;
+ delete offsets[line];
+ };
+ }
+};
+
+g.eval(
+ 'function t(a, b, c) { \n' +
+ ' debugger; \n' +
+ ' var x = a; \n' +
+ ' x += b; \n' +
+ ' if (x < 10) \n' +
+ ' x -= c; \n' +
+ ' return x; \n' +
+ '} \n'
+ );
+
+// This should stop at every line but the first of the function.
+g.eval('t(1,2,3)');
+assertEq(Object.keys(offsets).length, 1);
+
+// This should stop at every line but the first of the function, and the
+// body of the 'if'.
+g.eval('t(10,20,30)');
+assertEq(Object.keys(offsets).length, 2);
+
+// This shouldn't stop at all. It's the frame that's in single-step mode,
+// not the script, so the prior execution of t in single-step mode should
+// have no effect on this one.
+doSingleStep = false;
+g.eval('t(0, 0, 0)');
+assertEq(Object.keys(offsets).length, 7);
+doSingleStep = true;
+
+// Single-step in an eval frame. This should reach every line but the
+// first.
+g.eval(
+ 'debugger; \n' +
+ 'var a=1, b=2, c=3; \n' +
+ 'var x = a; \n' +
+ 'x += b; \n' +
+ 'if (x < 10) \n' +
+ ' x -= c; \n'
+ );
+print("final lines: " + JSON.stringify(Object.keys(offsets)));
+assertEq(Object.keys(offsets).length, 1);
+
+// Single-step in a global code frame. This should reach every line but the
+// first.
+g.evaluate(
+ 'debugger; \n' +
+ 'var a=1, b=2, c=3; \n' +
+ 'var x = a; \n' +
+ 'x += b; \n' +
+ 'if (x < 10) \n' +
+ ' x -= c; \n'
+ );
+print("final lines: " + JSON.stringify(Object.keys(offsets)));
+assertEq(Object.keys(offsets).length, 1);
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-resumption-01.js b/js/src/jit-test/tests/debug/Frame-onStep-resumption-01.js
new file mode 100644
index 0000000000..9fe94fb21f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-resumption-01.js
@@ -0,0 +1,14 @@
+// If frame.onStep returns {return:val}, the frame returns.
+
+var g = newGlobal({newCompartment: true});
+g.eval("function f(x) {\n" +
+ " var a = x * x;\n" +
+ " return a;\n" +
+ "}\n");
+
+var dbg = Debugger(g);
+dbg.onEnterFrame = function (frame) {
+ frame.onStep = function () { return {return: "pass"}; };
+};
+
+assertEq(g.f(4), "pass");
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-resumption-02.js b/js/src/jit-test/tests/debug/Frame-onStep-resumption-02.js
new file mode 100644
index 0000000000..852160b03c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-resumption-02.js
@@ -0,0 +1,17 @@
+// If frame.onStep returns {throw:}, an exception is thrown in the debuggee.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+g.eval("function h() { debugger; }\n" +
+ "function f() {\n" +
+ " h();\n" +
+ " return 'fail';\n" +
+ "}\n");
+
+var dbg = Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+ frame.older.onStep = function () { return {throw: "pass"}; };
+};
+
+assertThrowsValue(g.f, "pass");
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-resumption-03.js b/js/src/jit-test/tests/debug/Frame-onStep-resumption-03.js
new file mode 100644
index 0000000000..ab3ecdd855
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-resumption-03.js
@@ -0,0 +1,19 @@
+// If frame.onStep returns null, the debuggee terminates.
+
+var g = newGlobal({newCompartment: true});
+g.eval("function h() { debugger; }");
+
+var dbg = Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ hits++;
+ if (hits == 1) {
+ var rv = frame.eval("h();\n" +
+ "throw 'fail';\n");
+ assertEq(rv, null);
+ } else {
+ frame.older.onStep = function () { return null; };
+ }
+};
+g.h();
+assertEq(hits, 2);
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-resumption-04.js b/js/src/jit-test/tests/debug/Frame-onStep-resumption-04.js
new file mode 100644
index 0000000000..d557152a14
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-resumption-04.js
@@ -0,0 +1,31 @@
+// If frame.onStep returns null, debuggee catch and finally blocks are skipped.
+
+var g = newGlobal({newCompartment: true});
+g.eval("function h() { debugger; }");
+
+var dbg = Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ hits++;
+ if (hits == 1) {
+ var rv = frame.eval("try {\n" +
+ " h();\n" +
+ " throw 'fail';\n" +
+ "} catch (exc) {\n" +
+ " caught = exc;\n" +
+ "} finally {\n" +
+ " finallyHit = true;\n" +
+ "}\n");
+ assertEq(rv, null);
+ } else {
+ frame.older.onStep = function () {
+ this.onStep = undefined;
+ return null;
+ };
+ }
+};
+
+g.h();
+assertEq(hits, 2);
+assertEq("caught" in g, false);
+assertEq("finallyHit" in g, false);
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-resumption-05.js b/js/src/jit-test/tests/debug/Frame-onStep-resumption-05.js
new file mode 100644
index 0000000000..f49e68051f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-resumption-05.js
@@ -0,0 +1,55 @@
+// Test that invoking the interrupt callback counts as a step.
+
+function testResumptionVal(resumptionVal, turnOffDebugMode) {
+ var g = newGlobal({newCompartment: true});
+ var dbg = new Debugger;
+ g.log = "";
+ g.resumptionVal = resumptionVal;
+
+ setInterruptCallback(function () {
+ g.log += "i";
+ dbg.addDebuggee(g);
+ var frame = dbg.getNewestFrame();
+ frame.onStep = function () {
+ g.log += "s";
+ frame.onStep = undefined;
+
+ if (turnOffDebugMode)
+ dbg.removeDebuggee(g);
+
+ return resumptionVal;
+ };
+ return true;
+ });
+
+ try {
+ return g.eval("(" + function f() {
+ log += "f";
+ invokeInterruptCallback(function (interruptRv) {
+ log += "r";
+ assertEq(interruptRv, resumptionVal == undefined);
+ });
+ log += "a";
+ return 42;
+ } + ")();");
+ } finally {
+ assertEq(g.log, resumptionVal == undefined ? "fisra" : "fisr");
+ }
+}
+
+assertEq(testResumptionVal(undefined), 42);
+assertEq(testResumptionVal({ return: "not 42" }), "not 42");
+try {
+ testResumptionVal({ throw: "thrown 42" });
+} catch (e) {
+ assertEq(e, "thrown 42");
+}
+
+assertEq(testResumptionVal(undefined, true), 42);
+assertEq(testResumptionVal({ return: "not 42" }, true), "not 42");
+
+try {
+ testResumptionVal({ throw: "thrown 42" }, true);
+} catch (e) {
+ assertEq(e, "thrown 42");
+}
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-resumption-06.js b/js/src/jit-test/tests/debug/Frame-onStep-resumption-06.js
new file mode 100644
index 0000000000..3170c0319c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-resumption-06.js
@@ -0,0 +1,31 @@
+// |jit-test| error:all-jobs-completed-successfully
+// Verifiy that onStep's force-return queues the promise microtask to run in
+// the debuggee's job queue, not the debugger's
+// AutoDebuggerJobQueueInterruption.
+
+let g = newGlobal({ newCompartment: true });
+g.eval(`
+ async function asyncFn(x) {
+ await Promise.resolve();
+ }
+ function enterDebuggee(){}
+`);
+const dbg = new Debugger(g);
+
+(async function() {
+ let it = g.asyncFn();
+
+ // Force-return when the await resumes and steps.
+ dbg.onEnterFrame = frame => {
+ dbg.onEnterFrame = undefined;
+ frame.onStep = () => ({ return: "exit" });
+ };
+
+ const result = await it;
+ assertEq(result, "exit");
+ // If execution here is resumed from the debugger's queue, this call will
+ // trigger DebuggeeWouldRun exception.
+ g.enterDebuggee();
+
+ throw "all-jobs-completed-successfully";
+})();
diff --git a/js/src/jit-test/tests/debug/Frame-script-01.js b/js/src/jit-test/tests/debug/Frame-script-01.js
new file mode 100644
index 0000000000..9246f42d69
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-script-01.js
@@ -0,0 +1,25 @@
+// Frame.prototype.script for eval frames.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+// Apply |f| to each frame that is |skip| frames up from each frame that
+// executes a 'debugger' statement when evaluating |code| in the global g.
+function ApplyToFrameScript(code, skip, f) {
+ dbg.onDebuggerStatement = function (frame) {
+ while (skip-- > 0)
+ frame = frame.older;
+ assertEq(frame.type, "eval");
+ f(frame.script);
+ };
+ g.eval(code);
+}
+
+ApplyToFrameScript('debugger;', 0,
+ function (script) {
+ assertEq(script instanceof Debugger.Script, true);
+ });
+ApplyToFrameScript("(function () { eval('debugger;'); })();", 0,
+ function (script) {
+ assertEq(script instanceof Debugger.Script, true);
+ });
diff --git a/js/src/jit-test/tests/debug/Frame-script-02.js b/js/src/jit-test/tests/debug/Frame-script-02.js
new file mode 100644
index 0000000000..3b5bf12c63
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-script-02.js
@@ -0,0 +1,27 @@
+// Frame.prototype.script for call frames.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+// Apply |f| to each frame that is |skip| frames up from each frame that
+// executes a 'debugger' statement when evaluating |code| in the global g.
+function ApplyToFrameScript(code, skip, f) {
+ dbg.onDebuggerStatement = function (frame) {
+ while (skip-- > 0)
+ frame = frame.older;
+ assertEq(frame.type, "call");
+ f(frame.script);
+ };
+ g.eval(code);
+}
+
+ApplyToFrameScript('(function () { debugger; })();', 0,
+ function (script) {
+ assertEq(script instanceof Debugger.Script, true);
+ });
+
+// This would be nice, once we can get host call frames:
+// ApplyToFrameScript("(function () { debugger; }).call(null);", 1,
+// function (script) {
+// assertEq(script, null);
+// });
diff --git a/js/src/jit-test/tests/debug/Frame-script-03.js b/js/src/jit-test/tests/debug/Frame-script-03.js
new file mode 100644
index 0000000000..c29de59e25
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-script-03.js
@@ -0,0 +1,8 @@
+// frame.script can create a Debugger.Script for a JS_Evaluate* script.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var s;
+dbg.onDebuggerStatement = function (frame) { s = frame.script; };
+g.evaluate("debugger;");
+assertEq(s instanceof Debugger.Script, true);
diff --git a/js/src/jit-test/tests/debug/Frame-script-04.js b/js/src/jit-test/tests/debug/Frame-script-04.js
new file mode 100644
index 0000000000..8f0f953b1d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-script-04.js
@@ -0,0 +1,35 @@
+// Frame.prototype.script for generator frames.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({ newCompartment: true });
+var dbg = new Debugger(g);
+g.eval(`
+function* f() {}
+`);
+
+let frame;
+let script;
+dbg.onEnterFrame = function(f) {
+ frame = f;
+ script = frame.script;
+};
+
+const it = g.f();
+
+assertEq(frame instanceof Debugger.Frame, true);
+assertEq(script instanceof Debugger.Script, true);
+assertEq(frame.script, script);
+
+const lastFrame = frame;
+const lastScript = script;
+frame = null;
+script = null;
+
+it.next();
+
+assertEq(frame, lastFrame);
+assertEq(script, lastScript);
+
+// The frame has finished evaluating, so the script is no longer accessible.
+assertThrowsInstanceOf(() => frame.script, Error);
diff --git a/js/src/jit-test/tests/debug/Frame-script-05.js b/js/src/jit-test/tests/debug/Frame-script-05.js
new file mode 100644
index 0000000000..7c1094f01d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-script-05.js
@@ -0,0 +1,37 @@
+// Frame.prototype.script for async function frames.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({ newCompartment: true });
+var dbg = new Debugger(g);
+g.eval(`
+async function f() {
+ await Promise.resolve()
+}
+`);
+
+let frame;
+let script;
+dbg.onEnterFrame = function(f) {
+ frame = f;
+ script = frame.script;
+};
+
+const promise = g.f();
+
+assertEq(frame instanceof Debugger.Frame, true);
+assertEq(script instanceof Debugger.Script, true);
+assertEq(frame.script, script);
+
+const lastFrame = frame;
+const lastScript = script;
+frame = null;
+script = null;
+
+promise.then(() => {
+ assertEq(frame, lastFrame);
+ assertEq(script, lastScript);
+
+ // The frame has finished evaluating, so the script is no longer accessible.
+ assertThrowsInstanceOf(() => frame.script, Error);
+});
diff --git a/js/src/jit-test/tests/debug/Frame-script-06.js b/js/src/jit-test/tests/debug/Frame-script-06.js
new file mode 100644
index 0000000000..a01000347a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-script-06.js
@@ -0,0 +1,48 @@
+// Frame.prototype.script for async generator frames.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({ newCompartment: true });
+var dbg = new Debugger(g);
+g.eval(`
+async function* f() {
+ await Promise.resolve();
+}
+`);
+
+let frame;
+let script;
+dbg.onEnterFrame = function(f) {
+ frame = f;
+ script = frame.script;
+};
+
+const it = g.f();
+
+assertEq(frame instanceof Debugger.Frame, true);
+assertEq(script instanceof Debugger.Script, true);
+assertEq(frame.script, script);
+
+let lastFrame = frame;
+let lastScript = script;
+frame = null;
+script = null;
+
+let promise = it.next();
+
+assertEq(frame, lastFrame);
+assertEq(script, lastScript);
+assertEq(frame.script, script);
+
+lastFrame = frame;
+lastScript = script;
+frame = null;
+script = null;
+
+promise.then(() => {
+ assertEq(frame, lastFrame);
+ assertEq(script, lastScript);
+
+ // The frame has finished evaluating, so the script is no longer accessible.
+ assertThrowsInstanceOf(() => frame.script, Error);
+});
diff --git a/js/src/jit-test/tests/debug/Frame-script-environment-nondebuggee.js b/js/src/jit-test/tests/debug/Frame-script-environment-nondebuggee.js
new file mode 100644
index 0000000000..2d11b00c3b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-script-environment-nondebuggee.js
@@ -0,0 +1,32 @@
+// The script and environment of a non-debuggee frame are inaccessible.
+
+load(libdir + 'asserts.js');
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+
+var log;
+dbg.onDebuggerStatement = function (frame) {
+ log += frame.type;
+ // Initially, 'frame' is a debuggee frame, and we should be able to see its script and environment.
+ assertEq(frame.script instanceof Debugger.Script, true);
+ assertEq(frame.environment instanceof Debugger.Environment, true);
+
+ // If we make g no longer a debuggee, then trying to touch the frame at
+ // all should throw.
+ dbg.removeDebuggee(g);
+ assertThrowsInstanceOf(() => frame.script, Error);
+ assertThrowsInstanceOf(() => frame.environment, Error);
+}
+
+g.eval('function f() { debugger; }');
+
+log = '';
+dbg.addDebuggee(g);
+g.f();
+assertEq(log, 'call');
+
+log = '';
+dbg.addDebuggee(g);
+g.eval('debugger;');
+assertEq(log, 'eval');
diff --git a/js/src/jit-test/tests/debug/Frame-terminated-01.js b/js/src/jit-test/tests/debug/Frame-terminated-01.js
new file mode 100644
index 0000000000..d1b3de79e0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-terminated-01.js
@@ -0,0 +1,19 @@
+// Check `.terminated` functionality for normal functions.
+
+const g = newGlobal({ newCompartment: true });
+const dbg = new Debugger(g);
+
+g.eval(`
+function f(){}
+`);
+
+let frame;
+dbg.onEnterFrame = function(f) {
+ frame = f;
+ assertEq(frame.terminated, false);
+};
+
+g.f();
+
+assertEq(frame instanceof Debugger.Frame, true);
+assertEq(frame.terminated, true);
diff --git a/js/src/jit-test/tests/debug/Frame-terminated-02.js b/js/src/jit-test/tests/debug/Frame-terminated-02.js
new file mode 100644
index 0000000000..86323db5cb
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-terminated-02.js
@@ -0,0 +1,27 @@
+// Check `.terminated` functionality for async functions.
+
+const g = newGlobal({ newCompartment: true });
+const dbg = new Debugger(g);
+
+g.eval(`
+async function f(){
+ await Promise.resolve();
+}
+`);
+
+let frame;
+dbg.onEnterFrame = function(f) {
+ frame = f;
+ assertEq(frame.terminated, false);
+};
+
+(async () => {
+ const promise = g.f();
+
+ assertEq(frame instanceof Debugger.Frame, true);
+ assertEq(frame.terminated, false);
+
+ await promise;
+
+ assertEq(frame.terminated, true);
+})();
diff --git a/js/src/jit-test/tests/debug/Frame-terminated-03.js b/js/src/jit-test/tests/debug/Frame-terminated-03.js
new file mode 100644
index 0000000000..c5ca4febda
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-terminated-03.js
@@ -0,0 +1,23 @@
+// Check `.terminated` functionality for generator functions.
+
+const g = newGlobal({ newCompartment: true });
+const dbg = new Debugger(g);
+
+g.eval(`
+function* f(){}
+`);
+
+let frame;
+dbg.onEnterFrame = function(f) {
+ frame = f;
+ assertEq(frame.terminated, false);
+};
+
+const it = g.f();
+
+assertEq(frame instanceof Debugger.Frame, true);
+assertEq(frame.terminated, false);
+
+it.next();
+
+assertEq(frame.terminated, true);
diff --git a/js/src/jit-test/tests/debug/Frame-terminated-04.js b/js/src/jit-test/tests/debug/Frame-terminated-04.js
new file mode 100644
index 0000000000..74d00c8a5b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-terminated-04.js
@@ -0,0 +1,31 @@
+// Check `.terminated` functionality for async generator functions.
+
+const g = newGlobal({ newCompartment: true });
+const dbg = new Debugger(g);
+
+g.eval(`
+async function* f(){
+ await Promise.resolve();
+}
+`);
+
+let frame;
+dbg.onEnterFrame = function(f) {
+ frame = f;
+ assertEq(frame.terminated, false);
+};
+
+(async () => {
+ const it = g.f();
+
+ assertEq(frame instanceof Debugger.Frame, true);
+ assertEq(frame.terminated, false);
+
+ const promise = it.next();
+
+ assertEq(frame.terminated, false);
+
+ await promise;
+
+ assertEq(frame.terminated, true);
+})();
diff --git a/js/src/jit-test/tests/debug/Frame-this-01.js b/js/src/jit-test/tests/debug/Frame-this-01.js
new file mode 100644
index 0000000000..147276624e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-this-01.js
@@ -0,0 +1,24 @@
+// Frame.prototype.this in strict-mode functions, with primitive values
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ hits++;
+ assertEq(frame.this, g.v);
+};
+
+g.eval("function f() { 'use strict'; debugger; }");
+
+g.eval("Boolean.prototype.f = f; v = true; v.f();");
+g.eval("f.call(v);");
+g.eval("Number.prototype.f = f; v = 3.14; v.f();");
+g.eval("f.call(v);");
+g.eval("String.prototype.f = f; v = 'hello'; v.f();");
+g.eval("f.call(v);");
+g.eval("Symbol.prototype.f = f; v = Symbol('world'); v.f();");
+g.eval("f.call(v);");
+g.eval("v = undefined; f.call(v);");
+g.eval("v = null; f.call(v);");
+
+assertEq(hits, 10);
diff --git a/js/src/jit-test/tests/debug/Frame-this-02.js b/js/src/jit-test/tests/debug/Frame-this-02.js
new file mode 100644
index 0000000000..a94ba5cc69
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-this-02.js
@@ -0,0 +1,17 @@
+// Frame.prototype.this in strict direct eval frames
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ hits++;
+ assertEq(frame.this, g.v);
+};
+
+g.eval("function f() { 'use strict'; eval('debugger;'); }");
+
+g.eval("Boolean.prototype.f = f; v = true; v.f();");
+g.eval("f.call(v);");
+g.eval("v = null; f.call(v);");
+
+assertEq(hits, 3);
diff --git a/js/src/jit-test/tests/debug/Frame-this-03.js b/js/src/jit-test/tests/debug/Frame-this-03.js
new file mode 100644
index 0000000000..18f8663479
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-this-03.js
@@ -0,0 +1,29 @@
+// Frame.prototype.this in non-strict-mode functions, with primitive values
+
+function classOf(obj) {
+ return Object.prototype.toString.call(obj).match(/^\[object (.*)\]$/)[1];
+}
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ hits++;
+ assertEq(frame.this instanceof Debugger.Object, true);
+ assertEq(frame.this.class, g.v == null ? classOf(g) : classOf(Object(g.v)));
+};
+
+g.eval("function f() { debugger; }");
+
+g.eval("Boolean.prototype.f = f; v = true; v.f();");
+g.eval("f.call(v);");
+g.eval("Number.prototype.f = f; v = 3.14; v.f();");
+g.eval("f.call(v);");
+g.eval("String.prototype.f = f; v = 'hello'; v.f();");
+g.eval("f.call(v);");
+g.eval("Symbol.prototype.f = f; v = Symbol('world'); v.f();");
+g.eval("f.call(v);");
+g.eval("v = undefined; f.call(v);");
+g.eval("v = null; f.call(v);");
+
+assertEq(hits, 10);
diff --git a/js/src/jit-test/tests/debug/Frame-this-04.js b/js/src/jit-test/tests/debug/Frame-this-04.js
new file mode 100644
index 0000000000..9111ac13ff
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-this-04.js
@@ -0,0 +1,25 @@
+// Debugger.Frame.prototype.this in functions, with object values
+
+function classOf(obj) {
+ return Object.prototype.toString.call(obj).match(/^\[object (.*)\]$/)[1];
+}
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ hits++;
+ assertEq(frame.this instanceof Debugger.Object, true);
+ assertEq(frame.this.class, classOf(Object(g.v)));
+};
+
+g.eval("function f() { debugger; }");
+
+g.eval("v = {}; f.call(v);");
+g.eval("v.f = f; v.f();");
+g.eval("v = new Date; f.call(v);");
+g.eval("v.f = f; v.f();");
+g.eval("v = []; f.call(v);");
+g.eval("Object.prototype.f = f; v.f();");
+g.eval("v = this; f();");
+assertEq(hits, 7);
diff --git a/js/src/jit-test/tests/debug/Frame-this-05.js b/js/src/jit-test/tests/debug/Frame-this-05.js
new file mode 100644
index 0000000000..3c38f10bca
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-this-05.js
@@ -0,0 +1,23 @@
+// Frame.this and evalInFrame in the global scope.
+var g = newGlobal({newCompartment: true});
+g.eval("x = 4; this['.this'] = 222;");
+var dbg = new Debugger(g);
+var res;
+dbg.onDebuggerStatement = function (frame) {
+ res = frame.eval("this.x").return;
+ res += frame.this.unsafeDereference().x;
+};
+g.eval("debugger;");
+assertEq(res, 8);
+
+// And inside eval.
+g.eval("x = 3; eval('debugger')");
+assertEq(res, 6);
+g.eval("x = 2; eval('eval(\\'debugger\\')')");
+assertEq(res, 4);
+
+// And inside arrow functions.
+g.eval("x = 1; (() => { debugger; })()");
+assertEq(res, 2);
+g.eval("x = 5; (() => { eval('debugger'); })()");
+assertEq(res, 10);
diff --git a/js/src/jit-test/tests/debug/Frame-this-06.js b/js/src/jit-test/tests/debug/Frame-this-06.js
new file mode 100644
index 0000000000..d38398645f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-this-06.js
@@ -0,0 +1,22 @@
+// Frame.this and evalInFrame with missing this, strict and non-strict.
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var evalThis, frameThis;
+dbg.onEnterFrame = function (frame) {
+ if (frame.type === "eval")
+ return;
+ assertEq(frame.type, "call");
+ evalThis = frame.eval("this");
+ frameThis = frame.this;
+};
+
+// Strict, this is primitive.
+g.eval("var foo = function() { 'use strict'; }; foo.call(33);");
+assertEq(evalThis.return, 33);
+assertEq(frameThis, 33);
+
+// Non-strict, this has to be boxed.
+g.eval("var bar = function() { }; bar.call(22);");
+assertEq(typeof evalThis.return, "object");
+assertEq(evalThis.return.unsafeDereference().valueOf(), 22);
+assertEq(frameThis.unsafeDereference().valueOf(), 22);
diff --git a/js/src/jit-test/tests/debug/Frame-this-07.js b/js/src/jit-test/tests/debug/Frame-this-07.js
new file mode 100644
index 0000000000..50d938f46c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-this-07.js
@@ -0,0 +1,19 @@
+// Frame.this can be marked as optimized-out in some cases. Here we call an
+// arrow function but its enclosing function is no longer live, so it's
+// impossible to recover its missing 'this' binding.
+var g = newGlobal({newCompartment: true});
+g.eval("x = 4");
+g.eval("var foo = function() { return () => 1; }; var arrow = foo.call(3);");
+var dbg = new Debugger(g);
+var log = "";
+dbg.onEnterFrame = function (frame) {
+ if (frame.type === "eval")
+ return;
+ assertEq(frame.type, "call");
+ assertEq(frame.this.optimizedOut, true);
+ frame.eval("try { print(this.x); } catch(e) { exc = e; }");
+ assertEq(typeof g.exc, "object");
+ log += "d";
+};
+g.eval("arrow();");
+assertEq(log, "d");
diff --git a/js/src/jit-test/tests/debug/Frame-this-08.js b/js/src/jit-test/tests/debug/Frame-this-08.js
new file mode 100644
index 0000000000..4346115865
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-this-08.js
@@ -0,0 +1,16 @@
+// Frame.this and evalInFrame in arrow function that uses 'this'.
+var g = newGlobal({newCompartment: true});
+g.eval("x = 4");
+g.eval("var foo = function() { 'use strict'; return () => this; }; var arrow = foo.call(3);");
+var dbg = new Debugger(g);
+var hits = 0;
+dbg.onEnterFrame = function (frame) {
+ if (frame.type === "eval")
+ return;
+ hits++;
+ assertEq(frame.type, "call");
+ assertEq(frame.this, 3);
+ assertEq(frame.eval("this + 1").return, 4);
+};
+g.eval("arrow();");
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Frame-this-09.js b/js/src/jit-test/tests/debug/Frame-this-09.js
new file mode 100644
index 0000000000..3ff6cc2a96
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-this-09.js
@@ -0,0 +1,45 @@
+// Ensure |Frame.this| returns the right value even if we're still in the
+// script's prologue, before JSOP_FUNCTIONTHIS.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var hits = 0;
+dbg.onEnterFrame = function (frame) {
+ if (frame.type === 'eval')
+ return;
+ hits++;
+
+ var frameThis = frame.this;
+ if (frameThis !== null && typeof frameThis === "object")
+ g.gotThis = frameThis.unsafeDereference();
+ else
+ g.gotThis = frameThis;
+
+ assertEq(frame.this, frameThis, "getter should not return a different object");
+};
+
+// Strict mode, function uses |this|.
+g.eval("function strictfun() { 'use strict'; return this; }");
+g.eval("strictfun.call(Math); assertEq(gotThis, Math);");
+g.eval("strictfun.call(true); assertEq(gotThis, true);");
+g.eval("strictfun.call(); assertEq(gotThis, undefined);");
+
+// Strict mode, function doesn't use |this|.
+g.eval("function strictfunNoThis() { 'use strict'; }");
+g.eval("strictfunNoThis.call(Math); assertEq(gotThis, Math);");
+g.eval("strictfunNoThis.call(true); assertEq(gotThis, true);");
+g.eval("strictfunNoThis.call(null); assertEq(gotThis, null);");
+
+// Non-strict mode (primitive |this| is boxed), function uses |this|.
+g.eval("function nonstrictfun() { return this; }");
+g.eval("nonstrictfun.call(Math); assertEq(gotThis, Math);");
+g.eval("nonstrictfun.call(null); assertEq(gotThis, this);");
+g.eval("nonstrictfun.call(); assertEq(gotThis, this);");
+
+// Non-strict mode (primitive |this| is boxed), function doesn't use |this|.
+g.eval("function nonstrictfunNoThis() {}");
+g.eval("nonstrictfunNoThis.call(Math); assertEq(gotThis, Math);");
+g.eval("nonstrictfunNoThis.call(null); assertEq(gotThis, this);");
+g.eval("nonstrictfunNoThis.call(); assertEq(gotThis, this);");
+
+assertEq(hits, 12);
diff --git a/js/src/jit-test/tests/debug/Frame-this-10.js b/js/src/jit-test/tests/debug/Frame-this-10.js
new file mode 100644
index 0000000000..5ca65a0c93
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-this-10.js
@@ -0,0 +1,42 @@
+// Check the Frame.this getter always returns the same object for a given frame.
+// Primitive this-values should not be boxed multiple times.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var framesEntered = 0;
+var framesPopped = 0;
+var numSteps = 0;
+dbg.onEnterFrame = function (frame) {
+ if (frame.type === 'eval')
+ return;
+ framesEntered++;
+
+ var frameThis = frame.this;
+ assertEq(frame.this, frameThis);
+
+ frame.onPop = function() {
+ framesPopped++;
+ assertEq(frame.this, frameThis);
+ };
+
+ frame.onStep = function() {
+ numSteps++;
+ assertEq(frame.this, frameThis);
+ }
+
+ g.gotThis = frameThis.unsafeDereference();
+};
+
+g.eval("function nonstrictfun() { return this; }");
+g.eval("nonstrictfun.call(Math); assertEq(gotThis, Math);");
+g.eval("nonstrictfun.call(true); assertEq(gotThis.valueOf(), true);");
+g.eval("nonstrictfun.call(); assertEq(gotThis, this);");
+
+g.eval("function nonstrictfunNoThis() { return 1; }");
+g.eval("nonstrictfunNoThis.call(Math); assertEq(gotThis, Math);");
+g.eval("nonstrictfunNoThis.call(true); assertEq(gotThis.valueOf(), true);");
+g.eval("nonstrictfunNoThis.call(); assertEq(gotThis, this);");
+
+assertEq(framesEntered, 6);
+assertEq(framesPopped, 6);
+assertEq(numSteps > 15, true);
diff --git a/js/src/jit-test/tests/debug/Frame-this-11.js b/js/src/jit-test/tests/debug/Frame-this-11.js
new file mode 100644
index 0000000000..9e137dba87
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-this-11.js
@@ -0,0 +1,46 @@
+// Ensure evalInFrame("this") returns the right value even if we're still in the
+// script's prologue, before JSOP_FUNCTIONTHIS.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var hits = 0;
+dbg.onEnterFrame = function (frame) {
+ if (frame.type === 'eval')
+ return;
+ hits++;
+
+ var frameThis = frame.eval('this').return;
+ if (frameThis !== null && typeof frameThis === "object")
+ g.gotThis = frameThis.unsafeDereference();
+ else
+ g.gotThis = frameThis;
+
+ assertEq(frame.this, frameThis);
+ assertEq(frame.eval('this').return, frameThis);
+};
+
+// Strict mode, function uses |this|.
+g.eval("function strictfun() { 'use strict'; return this; }");
+g.eval("strictfun.call(Math); assertEq(gotThis, Math);");
+g.eval("strictfun.call(true); assertEq(gotThis, true);");
+g.eval("strictfun.call(); assertEq(gotThis, undefined);");
+
+// Strict mode, function doesn't use |this|.
+g.eval("function strictfunNoThis() { 'use strict'; }");
+g.eval("strictfunNoThis.call(Math); assertEq(gotThis, Math);");
+g.eval("strictfunNoThis.call(true); assertEq(gotThis, true);");
+g.eval("strictfunNoThis.call(null); assertEq(gotThis, null);");
+
+// Non-strict mode (primitive |this| is boxed), function uses |this|.
+g.eval("function nonstrictfun() { return this; }");
+g.eval("nonstrictfun.call(Math); assertEq(gotThis, Math);");
+g.eval("nonstrictfun.call(null); assertEq(gotThis, this);");
+g.eval("nonstrictfun.call(); assertEq(gotThis, this);");
+
+// Non-strict mode (primitive |this| is boxed), function doesn't use |this|.
+g.eval("function nonstrictfunNoThis() {}");
+g.eval("nonstrictfunNoThis.call(Math); assertEq(gotThis, Math);");
+g.eval("nonstrictfunNoThis.call(null); assertEq(gotThis, this);");
+g.eval("nonstrictfunNoThis.call(); assertEq(gotThis, this);");
+
+assertEq(hits, 12);
diff --git a/js/src/jit-test/tests/debug/Frame-this-12.js b/js/src/jit-test/tests/debug/Frame-this-12.js
new file mode 100644
index 0000000000..3db0c4a8d6
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-this-12.js
@@ -0,0 +1,42 @@
+// Check evalInFrame("this") always returns the same object for a given frame.
+// Primitive this-values should not be boxed multiple times.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var framesEntered = 0;
+var framesPopped = 0;
+var numSteps = 0;
+dbg.onEnterFrame = function (frame) {
+ if (frame.type === 'eval')
+ return;
+ framesEntered++;
+
+ var frameThis = frame.eval('this').return;
+
+ frame.onPop = function() {
+ framesPopped++;
+ assertEq(frame.eval('this').return, frameThis);
+ };
+
+ frame.onStep = function() {
+ numSteps++;
+ assertEq(frame.eval('this').return, frameThis);
+ }
+
+ g.gotThis = frameThis.unsafeDereference();
+ assertEq(frame.this, frameThis);
+};
+
+g.eval("function nonstrictfun() { return this; }");
+g.eval("nonstrictfun.call(Math); assertEq(gotThis, Math);");
+g.eval("nonstrictfun.call(true); assertEq(gotThis.valueOf(), true);");
+g.eval("nonstrictfun.call(); assertEq(gotThis, this);");
+
+g.eval("function nonstrictfunNoThis() { return 1; }");
+g.eval("nonstrictfunNoThis.call(Math); assertEq(gotThis, Math);");
+g.eval("nonstrictfunNoThis.call(true); assertEq(gotThis.valueOf(), true);");
+g.eval("nonstrictfunNoThis.call(); assertEq(gotThis, this);");
+
+assertEq(framesEntered, 6);
+assertEq(framesPopped, 6);
+assertEq(numSteps > 15, true);
diff --git a/js/src/jit-test/tests/debug/Frame-this-13.js b/js/src/jit-test/tests/debug/Frame-this-13.js
new file mode 100644
index 0000000000..4200cecc45
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-this-13.js
@@ -0,0 +1,26 @@
+// Test that Debugger.Frame.prototype.this works on normal functions.
+
+load(libdir + "asserts.js");
+
+const g = newGlobal({ newCompartment: true });
+const dbg = new Debugger();
+const gDO = dbg.addDebuggee(g);
+
+g.eval(`
+var context = {};
+var f = function() {
+ return this;
+}.bind(context);
+`);
+
+let frame;
+dbg.onEnterFrame = f => {
+ frame = f;
+ assertEq(frame.this, gDO.makeDebuggeeValue(g.context));
+ dbg.onEnterFrame = undefined;
+};
+
+g.f();
+
+assertEq(!!frame, true);
+assertThrowsInstanceOf(() => frame.this, Error);
diff --git a/js/src/jit-test/tests/debug/Frame-this-14.js b/js/src/jit-test/tests/debug/Frame-this-14.js
new file mode 100644
index 0000000000..a47b203ea1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-this-14.js
@@ -0,0 +1,31 @@
+// Test that Debugger.Frame.prototype.this works on a suspended generator
+// function.
+
+load(libdir + "asserts.js");
+
+const g = newGlobal({ newCompartment: true });
+const dbg = new Debugger();
+const gDO = dbg.addDebuggee(g);
+
+g.eval(`
+var context = {};
+var f = function*() {
+ return this;
+}.bind(context);
+`);
+
+let frame;
+dbg.onEnterFrame = f => {
+ frame = f;
+ assertEq(frame.this, gDO.makeDebuggeeValue(g.context));
+ dbg.onEnterFrame = undefined;
+};
+
+const it = g.f();
+
+assertEq(!!frame, true);
+assertEq(frame.this, gDO.makeDebuggeeValue(g.context));
+
+it.next();
+
+assertThrowsInstanceOf(() => frame.this, Error);
diff --git a/js/src/jit-test/tests/debug/Frame-this-15.js b/js/src/jit-test/tests/debug/Frame-this-15.js
new file mode 100644
index 0000000000..350fa508a9
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-this-15.js
@@ -0,0 +1,34 @@
+// Test that Debugger.Frame.prototype.this works on a suspended async
+// function.
+
+load(libdir + "asserts.js");
+
+const g = newGlobal({ newCompartment: true });
+const dbg = new Debugger();
+const gDO = dbg.addDebuggee(g);
+
+g.eval(`
+var context = {};
+var f = async function() {
+ await Promise.resolve();
+ return this;
+}.bind(context);
+`);
+
+let frame;
+dbg.onEnterFrame = f => {
+ frame = f;
+ assertEq(frame.this, gDO.makeDebuggeeValue(g.context));
+ dbg.onEnterFrame = undefined;
+};
+
+(async () => {
+ const promise = g.f();
+
+ assertEq(!!frame, true);
+ assertEq(frame.this, gDO.makeDebuggeeValue(g.context));
+
+ await promise;
+
+ assertThrowsInstanceOf(() => frame.this, Error);
+})();
diff --git a/js/src/jit-test/tests/debug/Frame-this-16.js b/js/src/jit-test/tests/debug/Frame-this-16.js
new file mode 100644
index 0000000000..43aec1f2c5
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-this-16.js
@@ -0,0 +1,38 @@
+// Test that Debugger.Frame.prototype.this works on a suspended async
+// generator function.
+
+load(libdir + "asserts.js");
+
+const g = newGlobal({ newCompartment: true });
+const dbg = new Debugger();
+const gDO = dbg.addDebuggee(g);
+
+g.eval(`
+var context = {};
+var f = async function*() {
+ await Promise.resolve();
+ return this;
+}.bind(context);
+`);
+
+let frame;
+dbg.onEnterFrame = f => {
+ frame = f;
+ assertEq(frame.this, gDO.makeDebuggeeValue(g.context));
+ dbg.onEnterFrame = undefined;
+};
+
+(async () => {
+ const it = g.f();
+
+ assertEq(!!frame, true);
+ assertEq(frame.this, gDO.makeDebuggeeValue(g.context));
+
+ const promise = it.next();
+
+ assertEq(frame.this, gDO.makeDebuggeeValue(g.context));
+
+ await promise;
+
+ assertThrowsInstanceOf(() => frame.this, Error);
+})();
diff --git a/js/src/jit-test/tests/debug/Frame-type-01.js b/js/src/jit-test/tests/debug/Frame-type-01.js
new file mode 100644
index 0000000000..b17338874c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-type-01.js
@@ -0,0 +1,22 @@
+// Debugger.Frame.prototype.type on a normal function.
+
+load(libdir + "asserts.js");
+
+const g = newGlobal({ newCompartment: true });
+const dbg = Debugger(g);
+
+g.eval(`
+function f() {}
+`);
+
+let frame;
+dbg.onEnterFrame = function(f) {
+ frame = f;
+ assertEq(frame.type, "call");
+};
+
+g.f();
+
+assertEq(!!frame, true);
+// Throws because the frame has terminated.
+assertThrowsInstanceOf(() => frame.type, Error);
diff --git a/js/src/jit-test/tests/debug/Frame-type-02.js b/js/src/jit-test/tests/debug/Frame-type-02.js
new file mode 100644
index 0000000000..07e32ae29a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-type-02.js
@@ -0,0 +1,27 @@
+// Debugger.Frame.prototype.type on a generator.
+
+load(libdir + "asserts.js");
+
+const g = newGlobal({ newCompartment: true });
+const dbg = Debugger(g);
+
+g.eval(`
+function* f() {}
+`);
+
+let frame;
+dbg.onEnterFrame = function(f) {
+ frame = f;
+ assertEq(frame.type, "call");
+};
+
+const it = g.f();
+
+assertEq(frame.type, "call");
+frame = null;
+
+it.next();
+
+assertEq(!!frame, true);
+// Throws because the frame has terminated.
+assertThrowsInstanceOf(() => frame.type, Error);
diff --git a/js/src/jit-test/tests/debug/Frame-type-03.js b/js/src/jit-test/tests/debug/Frame-type-03.js
new file mode 100644
index 0000000000..38dc37d1c3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-type-03.js
@@ -0,0 +1,31 @@
+// Debugger.Frame.prototype.type on an async function.
+
+load(libdir + "asserts.js");
+
+const g = newGlobal({ newCompartment: true });
+const dbg = Debugger(g);
+
+g.eval(`
+async function f() {
+ await Promise.resolve();
+}
+`);
+
+let frame;
+dbg.onEnterFrame = function(f) {
+ frame = f;
+ assertEq(frame.type, "call");
+};
+
+(async () => {
+ const promise = g.f();
+
+ assertEq(frame.type, "call");
+ frame = null;
+
+ await promise;
+
+ assertEq(!!frame, true);
+ // Throws because the frame has terminated.
+ assertThrowsInstanceOf(() => frame.type, Error);
+})();
diff --git a/js/src/jit-test/tests/debug/Frame-type-04.js b/js/src/jit-test/tests/debug/Frame-type-04.js
new file mode 100644
index 0000000000..4c4a65b5e9
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-type-04.js
@@ -0,0 +1,35 @@
+// Debugger.Frame.prototype.type on an async generator.
+
+load(libdir + "asserts.js");
+
+const g = newGlobal({ newCompartment: true });
+const dbg = Debugger(g);
+
+g.eval(`
+async function* f() {
+ await Promise.resolve();
+}
+`);
+
+let frame;
+dbg.onEnterFrame = function(f) {
+ frame = f;
+ assertEq(frame.type, "call");
+};
+
+(async () => {
+ const it = g.f();
+
+ assertEq(frame.type, "call");
+ frame = null;
+
+ const promise = it.next();
+
+ assertEq(!!frame, true);
+ assertEq(frame.type, "call");
+
+ await promise;
+
+ // Throws because the frame has terminated.
+ assertThrowsInstanceOf(() => frame.type, Error);
+})();
diff --git a/js/src/jit-test/tests/debug/Memory-01.js b/js/src/jit-test/tests/debug/Memory-01.js
new file mode 100644
index 0000000000..7dc00b9ee8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-01.js
@@ -0,0 +1,6 @@
+assertEq(typeof Debugger.Memory, "function");
+let dbg = new Debugger;
+assertEq(dbg.memory instanceof Debugger.Memory, true);
+
+load(libdir + "asserts.js");
+assertThrowsInstanceOf(() => new Debugger.Memory, TypeError);
diff --git a/js/src/jit-test/tests/debug/Memory-allocationSamplingProbability-01.js b/js/src/jit-test/tests/debug/Memory-allocationSamplingProbability-01.js
new file mode 100644
index 0000000000..3dbd7be9d9
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-allocationSamplingProbability-01.js
@@ -0,0 +1,45 @@
+// Test that setting Debugger.Memory.prototype.allocationSamplingProbability to
+// a bad number throws.
+
+load(libdir + "asserts.js");
+
+const root = newGlobal({newCompartment: true});
+
+const dbg = new Debugger();
+const wrappedRoot = dbg.addDebuggee(root);
+
+var mem = dbg.memory;
+
+// Out of range, negative
+assertThrowsInstanceOf(() => mem.allocationSamplingProbability = -Number.MAX_VALUE,
+ TypeError);
+assertThrowsInstanceOf(() => mem.allocationSamplingProbability = -1,
+ TypeError);
+assertThrowsInstanceOf(() => mem.allocationSamplingProbability = -Number.MIN_VALUE,
+ TypeError);
+
+// In range
+mem.allocationSamplingProbability = -0.0;
+mem.allocationSamplingProbability = 0.0;
+mem.allocationSamplingProbability = Number.MIN_VALUE;
+mem.allocationSamplingProbability = 1 / 3;
+mem.allocationSamplingProbability = .5;
+mem.allocationSamplingProbability = 2 / 3;
+mem.allocationSamplingProbability = 1 - Math.pow(2, -53);
+mem.allocationSamplingProbability = 1;
+
+// Out of range, positive
+assertThrowsInstanceOf(() => mem.allocationSamplingProbability = 1 + Number.EPSILON,
+ TypeError);
+assertThrowsInstanceOf(() => mem.allocationSamplingProbability = 2,
+ TypeError);
+assertThrowsInstanceOf(() => mem.allocationSamplingProbability = Number.MAX_VALUE,
+ TypeError);
+
+// Out of range, non-finite
+assertThrowsInstanceOf(() => mem.allocationSamplingProbability = -Infinity,
+ TypeError);
+assertThrowsInstanceOf(() => mem.allocationSamplingProbability = Infinity,
+ TypeError);
+assertThrowsInstanceOf(() => mem.allocationSamplingProbability = NaN,
+ TypeError);
diff --git a/js/src/jit-test/tests/debug/Memory-allocationSamplingProbability-02.js b/js/src/jit-test/tests/debug/Memory-allocationSamplingProbability-02.js
new file mode 100644
index 0000000000..bbd2582a68
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-allocationSamplingProbability-02.js
@@ -0,0 +1,36 @@
+// Test that we only sample about allocationSamplingProbability * 100 percent of
+// allocations.
+
+const root = newGlobal({newCompartment: true});
+
+const dbg = new Debugger();
+const wrappedRoot = dbg.addDebuggee(root);
+
+root.eval(`
+ objs = [];
+ objs.push(new Object);
+`);
+
+root.eval("" + function makeSomeAllocations() {
+ for (var i = 0; i < 100; i++) {
+ objs.push(new Object);
+ }
+});
+
+function measure(P, expected) {
+ root.setSavedStacksRNGState(Number.MAX_SAFE_INTEGER - 1);
+ dbg.memory.allocationSamplingProbability = P;
+ root.makeSomeAllocations();
+ assertEq(dbg.memory.drainAllocationsLog().length, expected);
+}
+
+dbg.memory.trackingAllocationSites = true;
+
+// These are the sample counts that were correct when this test was last
+// updated; changes to SpiderMonkey may occasionally cause changes
+// here. Anything that is within a plausible range for the given sampling
+// probability is fine.
+measure(0.0, 0);
+measure(1.0, 100);
+measure(0.1, 11);
+measure(0.5, 49);
diff --git a/js/src/jit-test/tests/debug/Memory-allocationsLogOverflowed-01.js b/js/src/jit-test/tests/debug/Memory-allocationsLogOverflowed-01.js
new file mode 100644
index 0000000000..5e7176db34
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-allocationsLogOverflowed-01.js
@@ -0,0 +1,24 @@
+// Test basic usage of `Debugger.Memory.prototype.allocationsLogOverflowed`.
+
+const root = newGlobal({newCompartment: true});
+const dbg = new Debugger(root);
+dbg.memory.trackingAllocationSites = true;
+dbg.memory.maxAllocationsLogLength = 1;
+
+root.eval("(" + function immediate() {
+ // Allocate more than the max log length.
+ this.objs = [{}, {}, {}, {}];
+} + "());");
+
+// The log should have overflowed.
+assertEq(dbg.memory.allocationsLogOverflowed, true);
+
+// Once drained, the flag should be reset.
+const allocs = dbg.memory.drainAllocationsLog();
+assertEq(dbg.memory.allocationsLogOverflowed, false);
+
+// If we keep allocations under the max log length, then we shouldn't have
+// overflowed.
+dbg.memory.maxAllocationsLogLength = 10000;
+root.eval("this.objs = [{}, {}, {}, {}];");
+assertEq(dbg.memory.allocationsLogOverflowed, false);
diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-01.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-01.js
new file mode 100644
index 0000000000..8b7599c73d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-01.js
@@ -0,0 +1,31 @@
+// Test basic usage of drainAllocationsLog()
+
+const root = newGlobal({newCompartment: true});
+const dbg = new Debugger();
+const wrappedRoot = dbg.addDebuggee(root)
+dbg.memory.trackingAllocationSites = true;
+
+root.eval("(" + function immediate() {
+ this.tests = [
+ {x: 1},
+ [],
+ /(two|2)\s*problems/,
+ new function Ctor(){},
+ new Object(),
+ new Array(),
+ new Date(),
+ ];
+} + "());");
+
+const allocs = dbg.memory.drainAllocationsLog();
+print(allocs.join("\n--------------------------------------------------------------------------\n"));
+print("Total number of allocations logged: " + allocs.length);
+
+let idx = -1;
+for (let object of root.tests) {
+ let wrappedObject = wrappedRoot.makeDebuggeeValue(object);
+ let allocSite = wrappedObject.allocationSite;
+ let newIdx = allocs.map(x => x.frame).indexOf(allocSite);
+ assertEq(newIdx > idx, true);
+ idx = newIdx;
+}
diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-02.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-02.js
new file mode 100644
index 0000000000..e9d56bf103
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-02.js
@@ -0,0 +1,14 @@
+// Test that drainAllocationsLog fails when we aren't trackingAllocationSites.
+
+load(libdir + 'asserts.js');
+
+const root = newGlobal();
+const dbg = new Debugger();
+
+dbg.memory.trackingAllocationSites = true;
+root.eval("this.alloc1 = {}");
+dbg.memory.trackingAllocationSites = false;
+root.eval("this.alloc2 = {};");
+
+assertThrowsInstanceOf(() => dbg.memory.drainAllocationsLog(),
+ Error);
diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-03.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-03.js
new file mode 100644
index 0000000000..fb0ba5e0d5
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-03.js
@@ -0,0 +1,24 @@
+// Test when there are more allocations than the maximum log length.
+
+const root = newGlobal({newCompartment: true});
+const dbg = new Debugger();
+dbg.addDebuggee(root)
+
+dbg.memory.maxAllocationsLogLength = 3;
+dbg.memory.trackingAllocationSites = true;
+
+root.eval([
+ "this.alloc1 = {};", // line 1
+ "this.alloc2 = {};", // line 2
+ "this.alloc3 = {};", // line 3
+ "this.alloc4 = {};", // line 4
+].join("\n"));
+
+const allocs = dbg.memory.drainAllocationsLog();
+
+// Should have stayed at the maximum length.
+assertEq(allocs.length, 3);
+// Should have kept the most recent allocation.
+assertEq(allocs[2].frame.line, 4);
+// Should have thrown away the oldest allocation.
+assertEq(allocs.map(x => x.frame.line).indexOf(1), -1);
diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-04.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-04.js
new file mode 100644
index 0000000000..1a0c55f276
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-04.js
@@ -0,0 +1,21 @@
+// Test that when we shorten the maximum log length, we won't get a longer log
+// than that new maximum afterwards.
+
+const root = newGlobal({newCompartment: true});
+const dbg = new Debugger();
+dbg.addDebuggee(root)
+
+dbg.memory.trackingAllocationSites = true;
+
+root.eval([
+ "this.alloc1 = {};", // line 1
+ "this.alloc2 = {};", // line 2
+ "this.alloc3 = {};", // line 3
+ "this.alloc4 = {};", // line 4
+].join("\n"));
+
+dbg.memory.maxAllocationsLogLength = 1;
+const allocs = dbg.memory.drainAllocationsLog();
+
+// Should have trimmed down to the new maximum length.
+assertEq(allocs.length, 1);
diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-05.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-05.js
new file mode 100644
index 0000000000..59f9094d9b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-05.js
@@ -0,0 +1,9 @@
+// Test an empty allocation log.
+
+const root = newGlobal({newCompartment: true});
+const dbg = new Debugger();
+dbg.addDebuggee(root)
+
+dbg.memory.trackingAllocationSites = true;
+const allocs = dbg.memory.drainAllocationsLog();
+assertEq(allocs.length, 0);
diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-06.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-06.js
new file mode 100644
index 0000000000..e112cd8c0e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-06.js
@@ -0,0 +1,23 @@
+// Test doing a GC while we have a non-empty log.
+
+const root = newGlobal({newCompartment: true});
+const dbg = new Debugger();
+dbg.addDebuggee(root)
+dbg.memory.trackingAllocationSites = true;
+
+root.eval("(" + function immediate() {
+ this.tests = [
+ ({}),
+ [],
+ /(two|2)\s*problems/,
+ new function Ctor(){},
+ new Object(),
+ new Array(),
+ new Date(),
+ ];
+} + "());");
+
+gc();
+
+const allocs = dbg.memory.drainAllocationsLog();
+assertEq(allocs.length >= root.tests.length, true);
diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-07.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-07.js
new file mode 100644
index 0000000000..21ffb3aaf6
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-07.js
@@ -0,0 +1,10 @@
+// Test retrieving the log when allocation tracking hasn't ever been enabled.
+
+load(libdir + 'asserts.js');
+
+const root = newGlobal({newCompartment: true});
+const dbg = new Debugger();
+dbg.addDebuggee(root)
+
+assertThrowsInstanceOf(() => dbg.memory.drainAllocationsLog(),
+ Error);
diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-08.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-08.js
new file mode 100644
index 0000000000..78803b9aa7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-08.js
@@ -0,0 +1,30 @@
+// Test retrieving the log multiple times.
+
+const root = newGlobal({newCompartment: true});
+const dbg = new Debugger();
+dbg.addDebuggee(root)
+
+root.eval([
+ "this.allocs = [];",
+ "this.doFirstAlloc = " + function () {
+ this.allocs.push({}); this.firstAllocLine = Error().lineNumber;
+ },
+ "this.doSecondAlloc = " + function () {
+ this.allocs.push(new Object()); this.secondAllocLine = Error().lineNumber;
+ }
+].join("\n"));
+
+dbg.memory.trackingAllocationSites = true;
+
+root.doFirstAlloc();
+let allocs1 = dbg.memory.drainAllocationsLog();
+root.doSecondAlloc();
+let allocs2 = dbg.memory.drainAllocationsLog();
+
+let allocs1Lines = allocs1.filter(x => !!x.frame).map(x => x.frame.line);
+assertEq(allocs1Lines.indexOf(root.firstAllocLine) != -1, true);
+assertEq(allocs1Lines.indexOf(root.secondAllocLine) == -1, true);
+
+let allocs2Lines = allocs2.filter(x => !!x.frame).map(x => x.frame.line);
+assertEq(allocs2Lines.indexOf(root.secondAllocLine) != -1, true);
+assertEq(allocs2Lines.indexOf(root.firstAllocLine) == -1, true);
diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-09.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-09.js
new file mode 100644
index 0000000000..62834fcc1b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-09.js
@@ -0,0 +1,20 @@
+// Test logs that contain allocations from many debuggee compartments.
+
+const dbg = new Debugger();
+
+const root1 = newGlobal({newCompartment: true});
+const root2 = newGlobal({newCompartment: true});
+const root3 = newGlobal({newCompartment: true});
+
+dbg.addDebuggee(root1);
+dbg.addDebuggee(root2);
+dbg.addDebuggee(root3);
+
+dbg.memory.trackingAllocationSites = true;
+
+root1.eval("this.alloc = {}");
+root2.eval("this.alloc = {}");
+root3.eval("this.alloc = {}");
+
+const allocs = dbg.memory.drainAllocationsLog();
+assertEq(allocs.length >= 3, true);
diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-10.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-10.js
new file mode 100644
index 0000000000..0bcad27a33
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-10.js
@@ -0,0 +1,21 @@
+// Test logs that contain allocations from debuggee compartments added as we are
+// logging.
+
+const dbg = new Debugger();
+
+dbg.memory.trackingAllocationSites = true;
+
+const root1 = newGlobal({newCompartment: true});
+dbg.addDebuggee(root1);
+root1.eval("this.alloc = {}");
+
+const root2 = newGlobal({newCompartment: true});
+dbg.addDebuggee(root2);
+root2.eval("this.alloc = {}");
+
+const root3 = newGlobal({newCompartment: true});
+dbg.addDebuggee(root3);
+root3.eval("this.alloc = {}");
+
+const allocs = dbg.memory.drainAllocationsLog();
+assertEq(allocs.length >= 3, true);
diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-11.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-11.js
new file mode 100644
index 0000000000..7e5d567a81
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-11.js
@@ -0,0 +1,25 @@
+// Test logs that shouldn't contain allocations from debuggee compartments
+// removed as we are logging.
+
+const dbg = new Debugger();
+
+const root1 = newGlobal({newCompartment: true});
+dbg.addDebuggee(root1);
+const root2 = newGlobal({newCompartment: true});
+dbg.addDebuggee(root2);
+const root3 = newGlobal({newCompartment: true});
+dbg.addDebuggee(root3);
+
+dbg.memory.trackingAllocationSites = true;
+
+dbg.removeDebuggee(root1);
+root1.eval("this.alloc = {}");
+
+dbg.removeDebuggee(root2);
+root2.eval("this.alloc = {}");
+
+dbg.removeDebuggee(root3);
+root3.eval("this.alloc = {}");
+
+const allocs = dbg.memory.drainAllocationsLog();
+assertEq(allocs.length, 0);
diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-13.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-13.js
new file mode 100644
index 0000000000..df294bce91
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-13.js
@@ -0,0 +1,16 @@
+// |jit-test| skip-if: helperThreadCount() === 0
+
+// Test that we don't crash while logging allocations and there is
+// off-main-thread compilation. OMT compilation will allocate functions and
+// regexps, but we just punt on measuring that accurately.
+
+const root = newGlobal({newCompartment: true});
+root.eval("this.dbg = new Debugger()");
+root.dbg.addDebuggee(this);
+root.dbg.memory.trackingAllocationSites = true;
+
+offThreadCompileScript(
+ "function foo() {\n" +
+ " print('hello world');\n" +
+ "}"
+);
diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-14.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-14.js
new file mode 100644
index 0000000000..39b2674c31
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-14.js
@@ -0,0 +1,47 @@
+// Test that drainAllocationsLog returns some timestamps.
+
+load(libdir + 'asserts.js');
+
+var allocTimes = [];
+
+allocTimes.push(timeSinceCreation());
+
+const root = newGlobal({newCompartment: true});
+const dbg = new Debugger(root);
+
+dbg.memory.trackingAllocationSites = true;
+root.eval("this.alloc1 = {}");
+allocTimes.push(timeSinceCreation());
+root.eval("this.alloc2 = {}");
+allocTimes.push(timeSinceCreation());
+root.eval("this.alloc3 = {}");
+allocTimes.push(timeSinceCreation());
+root.eval("this.alloc4 = {}");
+allocTimes.push(timeSinceCreation());
+
+allocs = dbg.memory.drainAllocationsLog();
+assertEq(allocs.length >= 4, true);
+assertEq(allocs[0].timestamp >= allocTimes[0], true);
+var seenAlloc = 0;
+var lastIndexSeenAllocIncremented = 0;
+for (i = 1; i < allocs.length; ++i) {
+ assertEq(allocs[i].timestamp >= allocs[i - 1].timestamp, true);
+ // It isn't possible to exactly correlate the entries in the
+ // allocs array with the entries in allocTimes, because we can't
+ // control exactly how many allocations are done during the course
+ // of a given eval. However, we can assume that there is some
+ // allocation recorded after each entry in allocTimes. So, we
+ // track the allocTimes entry we've passed, and then after the
+ // loop assert that we've seen them all. We also assert that a
+ // non-zero number of allocations has happened since the last seen
+ // increment.
+ while (seenAlloc < allocTimes.length
+ && allocs[i].timestamp >= allocTimes[seenAlloc]) {
+ assertEq(i - lastIndexSeenAllocIncremented > 0, true);
+ lastIndexSeenAllocIncremented = i;
+ ++seenAlloc;
+ }
+}
+// There should be one entry left in allocTimes, because we recorded a
+// time after the last possible allocation in the array.
+assertEq(seenAlloc, allocTimes.length -1);
diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-15.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-15.js
new file mode 100644
index 0000000000..d8af3c9319
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-15.js
@@ -0,0 +1,30 @@
+// Test drainAllocationsLog() and [[Class]] names.
+const root = newGlobal({newCompartment: true});
+const dbg = new Debugger();
+const wrappedRoot = dbg.addDebuggee(root)
+
+root.eval(
+ `
+ this.tests = [
+ { expected: "Object", test: () => new Object },
+ { expected: "Array", test: () => [] },
+ { expected: "Date", test: () => new Date },
+ { expected: "RegExp", test: () => /problems/ },
+ { expected: "Int8Array", test: () => new Int8Array },
+ { expected: "Promise", test: () => new Promise(function (){})},
+ ];
+ `
+);
+
+
+for (let { expected, test } of root.tests) {
+ print(expected);
+
+ dbg.memory.trackingAllocationSites = true;
+ test();
+ let allocs = dbg.memory.drainAllocationsLog();
+ dbg.memory.trackingAllocationSites = false;
+
+ assertEq(allocs.some(a => a.class === expected), true);
+}
+
diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-17.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-17.js
new file mode 100644
index 0000000000..b0faf83641
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-17.js
@@ -0,0 +1,55 @@
+// Test drainAllocationsLog() and byte sizes.
+
+const root = newGlobal({newCompartment: true});
+const dbg = new Debugger();
+const wrappedRoot = dbg.addDebuggee(root);
+
+root.eval(
+ `
+ function AsmModule(stdlib, foreign, heap) {
+ "use asm";
+
+ function test() {
+ return 5|0;
+ }
+
+ return { test: test };
+ }
+ const buf = new ArrayBuffer(1024*8);
+
+ function Ctor() {}
+ this.tests = [
+ { name: "new UInt8Array(256)", fn: () => new Uint8Array(256) },
+ { name: "arguments", fn: function () { return arguments; } },
+ { name: "asm.js module", fn: () => AsmModule(this, {}, buf) },
+ { name: "/2manyproblemz/g", fn: () => /2manyproblemz/g },
+ { name: "iterator", fn: () => [1,2,3][Symbol.iterator]() },
+ { name: "Error()", fn: () => Error() },
+ { name: "new Ctor", fn: () => new Ctor },
+ { name: "{}", fn: () => ({}) },
+ { name: "new Date", fn: () => new Date },
+ { name: "[1,2,3]", fn: () => [1,2,3] },
+ ];
+ `
+);
+
+for (let { name, fn } of root.tests) {
+ print("Test: " + name);
+
+ dbg.memory.trackingAllocationSites = true;
+
+ fn();
+
+ let entries = dbg.memory.drainAllocationsLog();
+
+ for (let {size} of entries) {
+ print(" " + size + " bytes");
+ // We should get some kind of byte size. We aren't testing that in depth
+ // here, it is tested pretty thoroughly in
+ // js/src/jit-test/tests/heap-analysis/byteSize-of-object.js.
+ assertEq(typeof size, "number");
+ assertEq(size > 0, true);
+ }
+
+ dbg.memory.trackingAllocationSites = false;
+}
diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-18.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-18.js
new file mode 100644
index 0000000000..6ab17714e6
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-18.js
@@ -0,0 +1,31 @@
+// |jit-test| skip-if: !('gczeal' in this)
+
+// Test drainAllocationsLog() entries' inNursery flag.
+
+gczeal(0);
+
+const root = newGlobal({newCompartment: true});
+const dbg = new Debugger();
+const wrappedRoot = dbg.addDebuggee(root);
+
+dbg.memory.trackingAllocationSites = true;
+
+root.eval(
+ `
+ for (let i = 0; i < 10; i++)
+ allocationMarker({ nursery: true });
+
+ for (let i = 0; i < 10; i++)
+ allocationMarker({ nursery: false });
+ `
+);
+
+let entries = dbg.memory.drainAllocationsLog().filter(e => e.class == "AllocationMarker");
+
+assertEq(entries.length, 20);
+
+for (let i = 0; i < 10; i++)
+ assertEq(entries[i].inNursery, true);
+
+for (let i = 10; i < 20; i++)
+ assertEq(entries[i].inNursery, false);
diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-01.js b/js/src/jit-test/tests/debug/Memory-takeCensus-01.js
new file mode 100644
index 0000000000..d5972a5ec6
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-takeCensus-01.js
@@ -0,0 +1,23 @@
+// Debugger.Memory.prototype.takeCensus returns a value of an appropriate shape.
+
+var dbg = new Debugger;
+
+function checkProperties(census) {
+ assertEq(typeof census, 'object');
+ for (prop of Object.getOwnPropertyNames(census)) {
+ var desc = Object.getOwnPropertyDescriptor(census, prop);
+ assertEq(desc.enumerable, true);
+ assertEq(desc.configurable, true);
+ assertEq(desc.writable, true);
+ if (typeof desc.value === 'object')
+ checkProperties(desc.value);
+ else
+ assertEq(typeof desc.value, 'number');
+ }
+}
+
+checkProperties(dbg.memory.takeCensus());
+
+var g = newGlobal({newCompartment: true});
+dbg.addDebuggee(g);
+checkProperties(dbg.memory.takeCensus());
diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-02.js b/js/src/jit-test/tests/debug/Memory-takeCensus-02.js
new file mode 100644
index 0000000000..0ab49cb2de
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-takeCensus-02.js
@@ -0,0 +1,49 @@
+// Debugger.Memory.prototype.takeCensus behaves plausibly as we allocate objects.
+
+// Exact object counts vary in ways we can't predict. For example,
+// BaselineScripts can hold onto "template objects", which exist only to hold
+// the shape and type for newly created objects. When BaselineScripts are
+// discarded, these template objects go with them.
+//
+// So instead of expecting precise counts, we expect counts that are at least as
+// many as we would expect given the object graph we've built.
+
+load(libdir + 'census.js');
+
+// A Debugger with no debuggees had better not find anything.
+var dbg = new Debugger;
+var census0 = dbg.memory.takeCensus();
+Census.walkCensus(census0, "census0", Census.assertAllZeros);
+
+function newGlobalWithDefs() {
+ var g = newGlobal({newCompartment: true});
+ g.eval(`
+ function times(n, fn) {
+ var a=[];
+ for (var i = 0; i<n; i++)
+ a.push(fn());
+ return a;
+ }`);
+ return g;
+}
+
+// Allocate a large number of various types of objects, and check that census
+// finds them.
+var g = newGlobalWithDefs();
+dbg.addDebuggee(g);
+
+g.eval('var objs = times(100, () => ({}));');
+g.eval('var rxs = times(200, () => /foo/);');
+g.eval('var ars = times(400, () => []);');
+g.eval('var fns = times(800, () => () => {});');
+
+var census1 = dbg.memory.takeCensus();
+Census.walkCensus(census1, "census1",
+ Census.assertAllNotLessThan(
+ { 'objects':
+ { 'Object': { count: 100 },
+ 'RegExp': { count: 200 },
+ 'Array': { count: 400 },
+ 'Function': { count: 800 }
+ }
+ }));
diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-03.js b/js/src/jit-test/tests/debug/Memory-takeCensus-03.js
new file mode 100644
index 0000000000..4284f642d8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-takeCensus-03.js
@@ -0,0 +1,26 @@
+// Debugger.Memory.prototype.takeCensus behaves plausibly as we add and remove debuggees.
+
+load(libdir + 'census.js');
+
+var dbg = new Debugger;
+
+var census0 = dbg.memory.takeCensus();
+Census.walkCensus(census0, "census0", Census.assertAllZeros);
+
+var g1 = newGlobal({newCompartment: true});
+dbg.addDebuggee(g1);
+var census1 = dbg.memory.takeCensus();
+Census.walkCensus(census1, "census1", Census.assertAllNotLessThan(census0));
+
+var g2 = newGlobal({newCompartment: true});
+dbg.addDebuggee(g2);
+var census2 = dbg.memory.takeCensus();
+Census.walkCensus(census2, "census2", Census.assertAllNotLessThan(census1), new Set(["bytes"]));
+
+dbg.removeDebuggee(g2);
+var census3 = dbg.memory.takeCensus();
+Census.walkCensus(census3, "census3", Census.assertAllEqual(census1), new Set(["bytes"]));
+
+dbg.removeDebuggee(g1);
+var census4 = dbg.memory.takeCensus();
+Census.walkCensus(census4, "census4", Census.assertAllEqual(census0));
diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-04.js b/js/src/jit-test/tests/debug/Memory-takeCensus-04.js
new file mode 100644
index 0000000000..33fbeaa163
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-takeCensus-04.js
@@ -0,0 +1,27 @@
+// Test that Debugger.Memory.prototype.takeCensus finds GC roots that are on the
+// stack.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+g.eval(`
+ function withAllocationMarkerOnStack(f) {
+ (function () {
+ var onStack = allocationMarker();
+ f();
+ return onStack; // To prevent the JIT from optimizing out onStack.
+ }())
+ }
+`);
+
+assertEq("AllocationMarker" in dbg.memory.takeCensus().objects, false,
+ "There shouldn't exist any allocation markers in the census.");
+
+var allocationMarkerCount;
+g.withAllocationMarkerOnStack(() => {
+ allocationMarkerCount = dbg.memory.takeCensus().objects.AllocationMarker.count;
+});
+
+assertEq(allocationMarkerCount, 1,
+ "Should have one allocation marker in the census, because there " +
+ "was one on the stack.");
diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-05.js b/js/src/jit-test/tests/debug/Memory-takeCensus-05.js
new file mode 100644
index 0000000000..4cd99158c1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-takeCensus-05.js
@@ -0,0 +1,14 @@
+// Test that Debugger.Memory.prototype.takeCensus finds cross compartment
+// wrapper GC roots.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+assertEq("AllocationMarker" in dbg.memory.takeCensus().objects, false,
+ "There shouldn't exist any allocation markers in the census.");
+
+this.ccw = g.allocationMarker();
+
+assertEq(dbg.memory.takeCensus().objects.AllocationMarker.count, 1,
+ "Should have one allocation marker in the census, because there " +
+ "is one cross-compartment wrapper referring to it.");
diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-06.js b/js/src/jit-test/tests/debug/Memory-takeCensus-06.js
new file mode 100644
index 0000000000..51b46d7e14
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-takeCensus-06.js
@@ -0,0 +1,115 @@
+// Check Debugger.Memory.prototype.takeCensus handling of 'breakdown' argument.
+
+load(libdir + 'match.js');
+var Pattern = Match.Pattern;
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+Pattern({ count: Pattern.NATURAL,
+ bytes: Pattern.NATURAL })
+ .assert(dbg.memory.takeCensus({ breakdown: { by: 'count' } }));
+
+let census = dbg.memory.takeCensus({ breakdown: { by: 'count', count: false, bytes: false } });
+assertEq('count' in census, false);
+assertEq('bytes' in census, false);
+
+census = dbg.memory.takeCensus({ breakdown: { by: 'count', count: true, bytes: false } });
+assertEq('count' in census, true);
+assertEq('bytes' in census, false);
+
+census = dbg.memory.takeCensus({ breakdown: { by: 'count', count: false, bytes: true } });
+assertEq('count' in census, false);
+assertEq('bytes' in census, true);
+
+census = dbg.memory.takeCensus({ breakdown: { by: 'count', count: true, bytes: true } });
+assertEq('count' in census, true);
+assertEq('bytes' in census, true);
+
+
+// Pattern doesn't mind objects with extra properties, so we'll restrict this
+// list to the object classes we're pretty sure are going to stick around for
+// the forseeable future.
+Pattern({
+ Function: { count: Pattern.NATURAL },
+ Object: { count: Pattern.NATURAL },
+ Debugger: { count: Pattern.NATURAL },
+ global: { count: Pattern.NATURAL },
+
+ // The below are all Debugger prototype objects.
+ Source: { count: Pattern.NATURAL },
+ Environment: { count: Pattern.NATURAL },
+ Script: { count: Pattern.NATURAL },
+ Memory: { count: Pattern.NATURAL },
+ Frame: { count: Pattern.NATURAL }
+ })
+ .assert(dbg.memory.takeCensus({ breakdown: { by: 'objectClass' } }));
+
+Pattern({
+ objects: { count: Pattern.NATURAL },
+ scripts: { count: Pattern.NATURAL },
+ strings: { count: Pattern.NATURAL },
+ other: { count: Pattern.NATURAL }
+ })
+ .assert(dbg.memory.takeCensus({ breakdown: { by: 'coarseType' } }));
+
+// As for { by: 'objectClass' }, restrict our pattern to the types
+// we predict will stick around for a long time.
+Pattern({
+ JSString: { count: Pattern.NATURAL },
+ 'js::Shape': { count: Pattern.NATURAL },
+ JSObject: { count: Pattern.NATURAL },
+ })
+ .assert(dbg.memory.takeCensus({ breakdown: { by: 'internalType' } }));
+
+
+// Nested breakdowns.
+
+let coarse_type_pattern = {
+ objects: { count: Pattern.NATURAL },
+ scripts: { count: Pattern.NATURAL },
+ strings: { count: Pattern.NATURAL },
+ other: { count: Pattern.NATURAL }
+};
+
+Pattern({
+ JSString: coarse_type_pattern,
+ 'js::Shape': coarse_type_pattern,
+ JSObject: coarse_type_pattern,
+ })
+ .assert(dbg.memory.takeCensus({
+ breakdown: { by: 'internalType',
+ then: { by: 'coarseType' }
+ }
+ }));
+
+Pattern({
+ Function: { count: Pattern.NATURAL },
+ Object: { count: Pattern.NATURAL },
+ Debugger: { count: Pattern.NATURAL },
+ global: { count: Pattern.NATURAL },
+ other: coarse_type_pattern
+ })
+ .assert(dbg.memory.takeCensus({
+ breakdown: {
+ by: 'objectClass',
+ then: { by: 'count' },
+ other: { by: 'coarseType' }
+ }
+ }));
+
+Pattern({
+ objects: { count: Pattern.NATURAL, label: "object" },
+ scripts: { count: Pattern.NATURAL, label: "scripts" },
+ strings: { count: Pattern.NATURAL, label: "strings" },
+ other: { count: Pattern.NATURAL, label: "other" }
+ })
+ .assert(dbg.memory.takeCensus({
+ breakdown: {
+ by: 'coarseType',
+ objects: { by: 'count', label: 'object' },
+ scripts: { by: 'count', label: 'scripts' },
+ strings: { by: 'count', label: 'strings' },
+ other: { by: 'count', label: 'other' }
+ }
+ }));
diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-07.js b/js/src/jit-test/tests/debug/Memory-takeCensus-07.js
new file mode 100644
index 0000000000..985a47c96e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-takeCensus-07.js
@@ -0,0 +1,75 @@
+// Debugger.Memory.prototype.takeCensus breakdown: check error handling on
+// property gets.
+
+load(libdir + 'asserts.js');
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+assertThrowsValue(() => {
+ dbg.memory.takeCensus({
+ breakdown: { get by() { throw "ಠ_ಠ" } }
+ });
+}, "ಠ_ಠ");
+
+
+
+assertThrowsValue(() => {
+ dbg.memory.takeCensus({
+ breakdown: { by: 'count', get count() { throw "ಠ_ಠ" } }
+ });
+}, "ಠ_ಠ");
+
+assertThrowsValue(() => {
+ dbg.memory.takeCensus({
+ breakdown: { by: 'count', get bytes() { throw "ಠ_ಠ" } }
+ });
+}, "ಠ_ಠ");
+
+
+
+assertThrowsValue(() => {
+ dbg.memory.takeCensus({
+ breakdown: { by: 'objectClass', get then() { throw "ಠ_ಠ" } }
+ });
+}, "ಠ_ಠ");
+
+assertThrowsValue(() => {
+ dbg.memory.takeCensus({
+ breakdown: { by: 'objectClass', get other() { throw "ಠ_ಠ" } }
+ });
+}, "ಠ_ಠ");
+
+
+
+assertThrowsValue(() => {
+ dbg.memory.takeCensus({
+ breakdown: { by: 'coarseType', get objects() { throw "ಠ_ಠ" } }
+ });
+}, "ಠ_ಠ");
+
+assertThrowsValue(() => {
+ dbg.memory.takeCensus({
+ breakdown: { by: 'coarseType', get scripts() { throw "ಠ_ಠ" } }
+ });
+}, "ಠ_ಠ");
+
+assertThrowsValue(() => {
+ dbg.memory.takeCensus({
+ breakdown: { by: 'coarseType', get strings() { throw "ಠ_ಠ" } }
+ });
+}, "ಠ_ಠ");
+
+assertThrowsValue(() => {
+ dbg.memory.takeCensus({
+ breakdown: { by: 'coarseType', get other() { throw "ಠ_ಠ" } }
+ });
+}, "ಠ_ಠ");
+
+
+
+assertThrowsValue(() => {
+ dbg.memory.takeCensus({
+ breakdown: { by: 'internalType', get then() { throw "ಠ_ಠ" } }
+ });
+}, "ಠ_ಠ");
diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-08.js b/js/src/jit-test/tests/debug/Memory-takeCensus-08.js
new file mode 100644
index 0000000000..8b8e21f81c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-takeCensus-08.js
@@ -0,0 +1,73 @@
+// Debugger.Memory.prototype.takeCensus: test by: 'count' breakdown
+
+let g = newGlobal({newCompartment: true});
+let dbg = new Debugger(g);
+
+g.eval(`
+ var stuff = [];
+ function add(n, c) {
+ for (let i = 0; i < n; i++)
+ stuff.push(c());
+ }
+
+ let count = 0;
+
+ function obj() { return { count: count++ }; }
+ obj.factor = 1;
+
+ // This creates a closure (a function JSObject) that has captured
+ // a Call object. So each call creates two items.
+ function fun() { let v = count; return () => { return v; } }
+ fun.factor = 2;
+
+ function str() { return 'perambulator' + count++; }
+ str.factor = 1;
+
+ // Eval a fresh text each time, allocating:
+ // - a fresh ScriptSourceObject
+ // - a new JSScripts, not an eval cache hits
+ // - a fresh prototype object
+ // - a fresh Call object, since the eval makes 'ev' heavyweight
+ // - the new function itself
+ function ev() {
+ return eval(\`(function () { return \${ count++ } })\`);
+ }
+ ev.factor = 5;
+
+ // A new object (1) with a new shape (2) with a new atom (3)
+ function shape() { return { [ 'theobroma' + count++ ]: count }; }
+ shape.factor = 3;
+ `);
+
+let baseline = 0;
+function countIncreasedByAtLeast(n) {
+ let oldBaseline = baseline;
+
+ // Since a census counts only reachable objects, one might assume that calling
+ // GC here would have no effect on the census results. But GC also throws away
+ // JIT code and any objects it might be holding (template objects, say);
+ // takeCensus reaches those. Shake everything loose that we can, to make the
+ // census approximate reachability a bit more closely, and make our results a
+ // bit more predictable.
+ gc(g, 'shrinking');
+
+ baseline = dbg.memory.takeCensus({ breakdown: { by: 'count' } }).count;
+ return baseline >= oldBaseline + n;
+}
+
+countIncreasedByAtLeast(0);
+
+g.add(100, g.obj);
+assertEq(countIncreasedByAtLeast(g.obj.factor * 100), true);
+
+g.add(100, g.fun);
+assertEq(countIncreasedByAtLeast(g.fun.factor * 100), true);
+
+g.add(100, g.str);
+assertEq(countIncreasedByAtLeast(g.str.factor * 100), true);
+
+g.add(100, g.ev);
+assertEq(countIncreasedByAtLeast(g.ev.factor * 100), true);
+
+g.add(100, g.shape);
+assertEq(countIncreasedByAtLeast(g.shape.factor * 100), true);
diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-09.js b/js/src/jit-test/tests/debug/Memory-takeCensus-09.js
new file mode 100644
index 0000000000..ff4823ee7a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-takeCensus-09.js
@@ -0,0 +1,74 @@
+// Debugger.Memory.prototype.takeCensus: by: allocationStack breakdown
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+g.evaluate(`
+ var log = [];
+ function f() { log.push(allocationMarker()); }
+ function g() { f(); }
+ function h() { f(); }
+ `,
+ { fileName: "Rockford", lineNumber: 1000 });
+
+// Create one allocationMarker with tracking turned off,
+// so it will have no associated stack.
+g.f();
+
+dbg.memory.allocationSamplingProbability = 1;
+dbg.memory.trackingAllocationSites = true;
+
+for ([f, n] of [[g.f, 20], [g.g, 10], [g.h, 5]])
+ for (let i = 0; i < n; i++)
+ f(); // all allocations of allocationMarker occur with this line as the
+ // oldest stack frame.
+
+let census = dbg.memory.takeCensus({ breakdown: { by: 'objectClass',
+ then: { by: 'allocationStack',
+ then: { by: 'count',
+ label: 'haz stack'
+ },
+ noStack: { by: 'count',
+ label: 'no haz stack'
+ }
+ }
+ }
+ });
+
+let map = census.AllocationMarker;
+assertEq(map instanceof Map, true);
+
+// Gather the stacks we are expecting to appear as keys, and
+// check that there are no unexpected keys.
+let stacks = { };
+
+map.forEach((v, k) => {
+ if (k === 'noStack') {
+ // No need to save this key.
+ } else if (k.functionDisplayName === 'f' &&
+ k.parent.functionDisplayName === null) {
+ stacks.f = k;
+ } else if (k.functionDisplayName === 'f' &&
+ k.parent.functionDisplayName === 'g' &&
+ k.parent.parent.functionDisplayName === null) {
+ stacks.fg = k;
+ } else if (k.functionDisplayName === 'f' &&
+ k.parent.functionDisplayName === 'h' &&
+ k.parent.parent.functionDisplayName === null) {
+ stacks.fh = k;
+ } else {
+ assertEq(true, false);
+ }
+});
+
+assertEq(map.get('noStack').label, 'no haz stack');
+assertEq(map.get('noStack').count, 1);
+
+assertEq(map.get(stacks.f).label, 'haz stack');
+assertEq(map.get(stacks.f).count, 20);
+
+assertEq(map.get(stacks.fg).label, 'haz stack');
+assertEq(map.get(stacks.fg).count, 10);
+
+assertEq(map.get(stacks.fh).label, 'haz stack');
+assertEq(map.get(stacks.fh).count, 5);
diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-10.js b/js/src/jit-test/tests/debug/Memory-takeCensus-10.js
new file mode 100644
index 0000000000..abe01d56d4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-takeCensus-10.js
@@ -0,0 +1,57 @@
+// Check byte counts produced by takeCensus.
+
+let g = newGlobal({newCompartment: true});
+let dbg = new Debugger(g);
+
+let sizeOfAM = byteSize(allocationMarker());
+
+// Allocate a single allocation marker, and check that we can find it.
+g.eval('var hold = allocationMarker();');
+let census = dbg.memory.takeCensus({ breakdown: { by: 'objectClass' } });
+assertEq(census.AllocationMarker.count, 1);
+assertEq(census.AllocationMarker.bytes, sizeOfAM);
+
+g.evaluate(`
+ var objs = [];
+ function fnerd() {
+ objs.push(allocationMarker());
+ for (let i = 0; i < 10; i++)
+ objs.push(allocationMarker());
+ }
+ `,
+ { fileName: 'J. Edgar Hoover', lineNumber: 2000 });
+
+dbg.memory.allocationSamplingProbability = 1;
+dbg.memory.trackingAllocationSites = true;
+
+g.hold = null;
+g.fnerd();
+
+census = dbg.memory.takeCensus({
+ breakdown: { by: 'objectClass',
+ then: { by: 'allocationStack' }
+ }
+});
+
+let seen = 0;
+census.AllocationMarker.forEach((v, k) => {
+ assertEq(k.functionDisplayName, 'fnerd');
+ assertEq(k.source, 'J. Edgar Hoover');
+ switch (k.line) {
+ case 2003:
+ assertEq(v.count, 1);
+ assertEq(v.bytes, sizeOfAM);
+ seen++;
+ break;
+
+ case 2005:
+ assertEq(v.count, 10);
+ assertEq(v.bytes, 10 * sizeOfAM);
+ seen++;
+ break;
+
+ default: assertEq(true, false);
+ }
+});
+
+assertEq(seen, 2);
diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-11.js b/js/src/jit-test/tests/debug/Memory-takeCensus-11.js
new file mode 100644
index 0000000000..06accbe74d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-takeCensus-11.js
@@ -0,0 +1,49 @@
+// |jit-test| skip-if: isLcovEnabled()
+
+// NOTE: Code coverage keeps top-level script alive even if normally it would be
+// GC'd. Skip this test in that case.
+
+// Check byte counts produced by takeCensus.
+
+const g = newGlobal({newCompartment: true, disableLazyParsing: true });
+g.eval("setJitCompilerOption('ion.warmup.trigger', 1000)");
+
+const dbg = new Debugger(g);
+
+g.evaluate("function one() {}", { fileName: "one.js" });
+g.evaluate(`function two1() {}
+ function two2() {}`,
+ { fileName: "two.js" });
+g.evaluate(`function three1() {}
+ function three2() {}
+ function three3() {}`,
+ { fileName: "three.js" });
+
+const report = dbg.memory.takeCensus({
+ breakdown: {
+ by: "coarseType",
+ scripts: {
+ by: "filename",
+ then: { by: "count", count: true, bytes: false },
+ noFilename: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: false }
+ }
+ },
+
+ // Not really interested in these, but they're here for completeness.
+ objects: { by: "count", count: true, byte: false },
+ strings: { by: "count", count: true, byte: false },
+ other: { by: "count", count: true, byte: false },
+ }
+});
+
+print(JSON.stringify(report, null, 4));
+
+assertEq(report.scripts["one.js"].count, 1);
+assertEq(report.scripts["two.js"].count, 2);
+assertEq(report.scripts["three.js"].count, 3);
+
+const noFilename = report.scripts.noFilename;
+assertEq(!!noFilename, true);
+assertEq(typeof noFilename, "object");
diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-12.js b/js/src/jit-test/tests/debug/Memory-takeCensus-12.js
new file mode 100644
index 0000000000..cb4f64ebc5
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-takeCensus-12.js
@@ -0,0 +1,61 @@
+// Sanity test that we can accumulate matching individuals in a bucket.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+var bucket = { by: "bucket" };
+var count = { by: "count", count: true, bytes: false };
+
+var all = dbg.memory.takeCensus({ breakdown: bucket });
+var allCount = dbg.memory.takeCensus({ breakdown: count }).count;
+
+var coarse = dbg.memory.takeCensus({
+ breakdown: {
+ by: "coarseType",
+ objects: bucket,
+ strings: bucket,
+ scripts: bucket,
+ other: bucket
+ }
+});
+var coarseCount = dbg.memory.takeCensus({
+ breakdown: {
+ by: "coarseType",
+ objects: count,
+ strings: count,
+ scripts: count,
+ other: count
+ }
+});
+
+assertEq(all.length > 0, true);
+assertEq(all.length, allCount);
+
+assertEq(coarse.objects.length > 0, true);
+assertEq(coarseCount.objects.count, coarse.objects.length);
+
+assertEq(coarse.strings.length > 0, true);
+assertEq(coarseCount.strings.count, coarse.strings.length);
+
+assertEq(coarse.scripts.length > 0, true);
+assertEq(coarseCount.scripts.count, coarse.scripts.length);
+
+assertEq(coarse.other.length > 0, true);
+assertEq(coarseCount.other.count, coarse.other.length);
+
+assertEq(all.length >= coarse.objects.length, true);
+assertEq(all.length >= coarse.strings.length, true);
+assertEq(all.length >= coarse.scripts.length, true);
+assertEq(all.length >= coarse.other.length, true);
+
+function assertIsIdentifier(id) {
+ assertEq(id, Math.floor(id));
+ assertEq(id > 0, true);
+ assertEq(id <= Math.pow(2, 48), true);
+}
+
+all.forEach(assertIsIdentifier);
+coarse.objects.forEach(assertIsIdentifier);
+coarse.strings.forEach(assertIsIdentifier);
+coarse.scripts.forEach(assertIsIdentifier);
+coarse.other.forEach(assertIsIdentifier);
diff --git a/js/src/jit-test/tests/debug/Memory-trackingAllocationSites-01.js b/js/src/jit-test/tests/debug/Memory-trackingAllocationSites-01.js
new file mode 100644
index 0000000000..46c0e1c34b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-trackingAllocationSites-01.js
@@ -0,0 +1,37 @@
+// Test that we can track allocation sites by setting
+// Debugger.Memory.prototype.trackingAllocationSites to true and then get the
+// allocation site via Debugger.Object.prototype.allocationSite.
+
+const root = newGlobal({newCompartment: true});
+
+const dbg = new Debugger();
+const wrappedRoot = dbg.addDebuggee(root);
+
+assertEq(dbg.memory.trackingAllocationSites, false);
+dbg.memory.trackingAllocationSites = true;
+assertEq(dbg.memory.trackingAllocationSites, true);
+
+root.eval("(" + function immediate() {
+ this.tests = [
+ { name: "object literal", object: ({}), line: Error().lineNumber },
+ { name: "array literal", object: [], line: Error().lineNumber },
+ { name: "regexp literal", object: /(two|2)\s*problems/, line: Error().lineNumber },
+ { name: "new constructor", object: new function Ctor(){}, line: Error().lineNumber },
+ { name: "new Object", object: new Object(), line: Error().lineNumber },
+ { name: "new Array", object: new Array(), line: Error().lineNumber },
+ { name: "new Date", object: new Date(), line: Error().lineNumber }
+ ];
+} + "());");
+
+dbg.memory.trackingAllocationSites = false;
+assertEq(dbg.memory.trackingAllocationSites, false);
+
+for (let { name, object, line } of root.tests) {
+ print("Entering test: " + name);
+
+ let wrappedObject = wrappedRoot.makeDebuggeeValue(object);
+ let allocationSite = wrappedObject.allocationSite;
+ print("Allocation site: " + allocationSite);
+
+ assertEq(allocationSite.line, line);
+}
diff --git a/js/src/jit-test/tests/debug/Memory-trackingAllocationSites-02.js b/js/src/jit-test/tests/debug/Memory-trackingAllocationSites-02.js
new file mode 100644
index 0000000000..5fa91f002a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-trackingAllocationSites-02.js
@@ -0,0 +1,19 @@
+// Test that we don't get allocation sites when nobody has asked for them.
+
+const root = newGlobal({newCompartment: true});
+
+const dbg = new Debugger();
+const wrappedRoot = dbg.addDebuggee(root);
+
+dbg.memory.trackingAllocationSites = true;
+root.eval("this.obj = {};");
+dbg.memory.trackingAllocationSites = false;
+root.eval("this.obj2 = {};");
+
+let wrappedObj = wrappedRoot.makeDebuggeeValue(root.obj);
+let allocationSite = wrappedObj.allocationSite;
+assertEq(allocationSite != null && typeof allocationSite == "object", true);
+
+let wrappedObj2 = wrappedRoot.makeDebuggeeValue(root.obj2);
+let allocationSite2 = wrappedObj2.allocationSite;
+assertEq(allocationSite2, null);
diff --git a/js/src/jit-test/tests/debug/Memory-trackingAllocationSites-03.js b/js/src/jit-test/tests/debug/Memory-trackingAllocationSites-03.js
new file mode 100644
index 0000000000..62205b8249
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-trackingAllocationSites-03.js
@@ -0,0 +1,76 @@
+// Test that multiple Debuggers behave reasonably.
+
+load(libdir + "asserts.js");
+
+let dbg1, dbg2, root1, root2;
+
+function isTrackingAllocations(global, dbgObj) {
+ const site = dbgObj.makeDebuggeeValue(global.eval("({})")).allocationSite;
+ if (site) {
+ assertEq(typeof site, "object");
+ }
+ return !!site;
+}
+
+function test(name, fn) {
+ print();
+ print(name);
+
+ // Reset state.
+ root1 = newGlobal({newCompartment: true});
+ root2 = newGlobal({newCompartment: true});
+ dbg1 = new Debugger;
+ dbg2 = new Debugger;
+
+ // Run the test.
+ fn();
+
+ print(" OK");
+}
+
+test("Can track allocations even if a different debugger is already tracking " +
+ "them.",
+ () => {
+ let d1r1 = dbg1.addDebuggee(root1);
+ let d2r1 = dbg2.addDebuggee(root1);
+ dbg1.memory.trackingAllocationSites = true;
+ dbg2.memory.trackingAllocationSites = true;
+ assertEq(isTrackingAllocations(root1, d1r1), true);
+ assertEq(isTrackingAllocations(root1, d2r1), true);
+ });
+
+test("Removing root1 as a debuggee from all debuggers should disable the " +
+ "allocation hook.",
+ () => {
+ dbg1.memory.trackingAllocationSites = true;
+ let d1r1 = dbg1.addDebuggee(root1);
+ dbg1.removeAllDebuggees();
+ assertEq(isTrackingAllocations(root1, d1r1), false);
+ });
+
+test("Adding a new debuggee to a debugger that is tracking allocations should " +
+ "enable the hook for the new debuggee.",
+ () => {
+ dbg1.memory.trackingAllocationSites = true;
+ let d1r1 = dbg1.addDebuggee(root1);
+ assertEq(isTrackingAllocations(root1, d1r1), true);
+ });
+
+test("Setting trackingAllocationSites to true should throw if the debugger " +
+ "cannot install the allocation hooks for *every* debuggee.",
+ () => {
+ let d1r1 = dbg1.addDebuggee(root1);
+ let d1r2 = dbg1.addDebuggee(root2);
+
+ // Can't install allocation hooks for root2 with this set.
+ root2.enableShellAllocationMetadataBuilder();
+
+ assertThrowsInstanceOf(() => dbg1.memory.trackingAllocationSites = true,
+ Error);
+
+ // And after it throws, its trackingAllocationSites accessor should reflect that
+ // allocation site tracking is still disabled in that Debugger.
+ assertEq(dbg1.memory.trackingAllocationSites, false);
+ assertEq(isTrackingAllocations(root1, d1r1), false);
+ assertEq(isTrackingAllocations(root2, d1r2), false);
+ });
diff --git a/js/src/jit-test/tests/debug/Object-01.js b/js/src/jit-test/tests/debug/Object-01.js
new file mode 100644
index 0000000000..fa28890c1c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-01.js
@@ -0,0 +1,17 @@
+// Debugger.Object basics
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ assertEq(frame.arguments[0], frame.callee);
+ assertEq(Object.getPrototypeOf(frame.arguments[0]), Debugger.Object.prototype);
+ assertEq(frame.arguments[0] instanceof Debugger.Object, true);
+ assertEq(frame.arguments[0] !== frame.arguments[1], true);
+ assertEq(Object.getPrototypeOf(frame.arguments[1]), Debugger.Object.prototype);
+ assertEq(frame.arguments[1] instanceof Debugger.Object, true);
+ hits++;
+};
+
+g.eval("var obj = {}; function f(a, b) { debugger; } f(f, obj);");
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Object-02.js b/js/src/jit-test/tests/debug/Object-02.js
new file mode 100644
index 0000000000..b5111cb8bd
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-02.js
@@ -0,0 +1,13 @@
+// Debugger.Object referents can be transparent wrappers of objects in the debugger compartment.
+
+var g = newGlobal({newCompartment: true});
+g.f = function (a, b) { return a + "/" + b; };
+var dbg = Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ var f = frame.eval("f").return;
+ assertEq(f.call(null, "a", "b").return, "a/b");
+ hits++;
+};
+g.eval("debugger;");
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Object-apply-01.js b/js/src/jit-test/tests/debug/Object-apply-01.js
new file mode 100644
index 0000000000..4abc32bfe1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-apply-01.js
@@ -0,0 +1,59 @@
+// tests calling script functions via Debugger.Object.prototype.apply/call
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+g.eval("function f() { debugger; }");
+var dbg = new Debugger(g);
+
+var hits = 0;
+function test(usingApply) {
+ dbg.onDebuggerStatement = function (frame) {
+ var fn = frame.arguments[0];
+ var cv = usingApply ? fn.apply(null, [9, 16]) : fn.call(null, 9, 16);
+ assertEq(Object.keys(cv).join(","), "return");
+ assertEq(Object.getPrototypeOf(cv), Object.prototype);
+ assertEq(cv.return, 25);
+
+ cv = usingApply ? fn.apply(null, ["hello ", "world"]) : fn.call(null, "hello ", "world");
+ assertEq(Object.keys(cv).join(","), "return");
+ assertEq(cv.return, "hello world");
+
+ // Handle more or less arguments.
+ assertEq((usingApply ? fn.apply(null, [1, 5, 100]) : fn.call(null, 1, 5, 100)).return, 6);
+ assertEq((usingApply ? fn.apply(null, []) : fn.call(null)).return, NaN);
+ assertEq((usingApply ? fn.apply() : fn.call()).return, NaN);
+
+ // Throw if a this-value or argument is an object but not a Debugger.Object.
+ assertThrowsInstanceOf(function () { usingApply ? fn.apply({}, []) : fn.call({}); },
+ TypeError);
+ assertThrowsInstanceOf(function () { usingApply ? fn.apply(null, [{}]) : fn.call(null, {}); },
+ TypeError);
+ hits++;
+ };
+ g.eval("f(function (a, b) { return a + b; });");
+
+ // The callee receives the right arguments even if more arguments are provided
+ // than the callee's .length.
+ dbg.onDebuggerStatement = function (frame) {
+ assertEq((usingApply ? frame.arguments[0].apply(null, ['one', 'two'])
+ : frame.arguments[0].call(null, 'one', 'two')).return,
+ 2);
+ hits++;
+ };
+ g.eval("f(function () { return arguments.length; });");
+
+ // Exceptions are reported as {throw,stack} completion values.
+ dbg.onDebuggerStatement = function (frame) {
+ var lose = frame.arguments[0];
+ var cv = usingApply ? lose.apply(null, []) : lose.call(null);
+ assertEq(Object.keys(cv).join(","), "throw,stack");
+ assertEq(cv.throw, frame.callee);
+ hits++;
+ };
+ g.eval("f(function lose() { throw f; });");
+}
+
+test(true);
+test(false);
+assertEq(hits, 6);
diff --git a/js/src/jit-test/tests/debug/Object-apply-02.js b/js/src/jit-test/tests/debug/Object-apply-02.js
new file mode 100644
index 0000000000..18ce4468a2
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-apply-02.js
@@ -0,0 +1,58 @@
+// tests calling native functions via Debugger.Object.prototype.apply/call
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+g.eval("function f() { debugger; }");
+var dbg = new Debugger(g);
+
+function test(usingApply) {
+ dbg.onDebuggerStatement = function (frame) {
+ var max = frame.arguments[0];
+ var cv = usingApply ? max.apply(null, [9, 16]) : max.call(null, 9, 16);
+ assertEq(cv.return, 16);
+
+ cv = usingApply ? max.apply() : max.call();
+ assertEq(cv.return, -1/0);
+
+ cv = usingApply ? max.apply(null, [2, 5, 3, 8, 1, 9, 4, 6, 7])
+ : max.call(null, 2, 5, 3, 8, 1, 9, 4, 6, 7);
+ assertEq(cv.return, 9);
+
+ // second argument to apply must be an array
+ assertThrowsInstanceOf(function () { max.apply(null, 12); }, TypeError);
+ };
+ g.eval("f(Math.max);");
+
+ dbg.onDebuggerStatement = function (frame) {
+ var push = frame.arguments[0];
+ var arr = frame.arguments[1];
+ var cv;
+
+ cv = usingApply ? push.apply(arr, [0, 1, 2]) : push.call(arr, 0, 1, 2);
+ assertEq(cv.return, 3);
+
+ cv = usingApply ? push.apply(arr, [arr]) : push.call(arr, arr);
+ assertEq(cv.return, 4);
+
+ cv = usingApply ? push.apply(arr) : push.call(arr);
+ assertEq(cv.return, 4);
+
+ // You can apply Array.prototype.push to a string; it does ToObject on
+ // it. But as the length property on String objects is non-writable,
+ // attempting to increase the length will throw a TypeError.
+ cv = usingApply
+ ? push.apply("hello", ["world"])
+ : push.call("hello", "world");
+ assertEq("throw" in cv, true);
+ var ex = cv.throw;
+ assertEq(frame.evalWithBindings("ex instanceof TypeError", { ex: ex }).return, true);
+ };
+ g.eval("var a = []; f(Array.prototype.push, a);");
+ assertEq(g.a.length, 4);
+ assertEq(g.a.slice(0, 3).join(","), "0,1,2");
+ assertEq(g.a[3], g.a);
+}
+
+test(true);
+test(false);
diff --git a/js/src/jit-test/tests/debug/Object-apply-03.js b/js/src/jit-test/tests/debug/Object-apply-03.js
new file mode 100644
index 0000000000..f23cc46518
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-apply-03.js
@@ -0,0 +1,21 @@
+// reentering the debugger several times via onDebuggerStatement and apply/call on a single stack
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+
+function test(usingApply) {
+ dbg.onDebuggerStatement = function (frame) {
+ var n = frame.arguments[0];
+ if (n > 1) {
+ var result = usingApply ? frame.callee.apply(null, [n - 1])
+ : frame.callee.call(null, n - 1);
+ result.return *= n;
+ return result;
+ }
+ };
+ g.eval("function fac(n) { debugger; return 1; }");
+ assertEq(g.fac(5), 5 * 4 * 3 * 2 * 1);
+}
+
+test(true);
+test(false);
diff --git a/js/src/jit-test/tests/debug/Object-apply-04.js b/js/src/jit-test/tests/debug/Object-apply-04.js
new file mode 100644
index 0000000000..04115344b2
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-apply-04.js
@@ -0,0 +1,15 @@
+// Debugger.Object.prototype.apply/call works with function proxies
+
+var g = newGlobal({newCompartment: true});
+g.eval("function f() { debugger; }");
+var dbg = Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ var proxy = frame.arguments[0];
+ assertEq(proxy.name, undefined);
+ assertEq(proxy.apply(null, [33]).return, 34);
+ assertEq(proxy.call(null, 33).return, 34);
+ hits++;
+};
+g.eval("f(new Proxy(function (arg) { return arg + 1; }, {}));");
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Object-asEnvironment-01.js b/js/src/jit-test/tests/debug/Object-asEnvironment-01.js
new file mode 100644
index 0000000000..39bfe4dabf
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-asEnvironment-01.js
@@ -0,0 +1,15 @@
+// Tests D.O.asEnvironment() returning the global lexical scope.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+
+g.evaluate(`
+ var x = 42;
+ let y = "foo"
+`);
+
+var globalLexical = gw.asEnvironment();
+assertEq(globalLexical.names().length, 1);
+assertEq(globalLexical.getVariable("y"), "foo");
+assertEq(globalLexical.parent.getVariable("x"), 42);
diff --git a/js/src/jit-test/tests/debug/Object-boundTargetFunction-01.js b/js/src/jit-test/tests/debug/Object-boundTargetFunction-01.js
new file mode 100644
index 0000000000..f87a0abc13
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-boundTargetFunction-01.js
@@ -0,0 +1,26 @@
+// Smoke tests for bound function things.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+var arrw = gw.executeInGlobal("var arr = []; arr;").return;
+var pushw = gw.executeInGlobal("var push = arr.push.bind(arr); push;").return;
+assertEq(pushw.isBoundFunction, true);
+assertEq(pushw.boundThis, arrw);
+assertEq(pushw.boundArguments.length, 0);
+
+var arr2 = gw.executeInGlobal("var arr2 = []; arr2").return;
+assertEq(pushw.call(arr2, "tuesday").return, 1);
+g.eval("assertEq(arr.length, 1);");
+g.eval("assertEq(arr[0], 'tuesday');");
+g.eval("assertEq(arr2.length, 0);");
+
+g.eval("push = arr.push.bind(arr, 1, 'seven', {x: 'q'});");
+pushw = gw.getOwnPropertyDescriptor("push").value;
+assertEq(pushw.isBoundFunction, true);
+var args = pushw.boundArguments;
+assertEq(args.length, 3);
+assertEq(args[0], 1);
+assertEq(args[1], 'seven');
+assertEq(args[2] instanceof Debugger.Object, true);
+assertEq(args[2].getOwnPropertyDescriptor("x").value, "q");
diff --git a/js/src/jit-test/tests/debug/Object-boundTargetFunction-02.js b/js/src/jit-test/tests/debug/Object-boundTargetFunction-02.js
new file mode 100644
index 0000000000..74676c435e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-boundTargetFunction-02.js
@@ -0,0 +1,25 @@
+// Test that bound function accessors work on:
+// - an ordinary non-bound function;
+// - a native function;
+// - and an object that isn't a function at all.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+var fw = gw.executeInGlobal("function f() {}; f").return;
+assertEq(fw.isBoundFunction, false);
+assertEq(fw.boundThis, undefined);
+assertEq(fw.boundArguments, undefined);
+assertEq(fw.boundTargetFunction, undefined);
+
+var nw = gw.executeInGlobal("var n = Math.max; n").return;
+assertEq(nw.isBoundFunction, false);
+assertEq(nw.boundThis, undefined);
+assertEq(fw.boundArguments, undefined);
+assertEq(nw.boundTargetFunction, undefined);
+
+var ow = gw.executeInGlobal("var o = {}; o").return;
+assertEq(ow.isBoundFunction, undefined);
+assertEq(ow.boundThis, undefined);
+assertEq(fw.boundArguments, undefined);
+assertEq(ow.boundTargetFunction, undefined);
diff --git a/js/src/jit-test/tests/debug/Object-boundTargetFunction-03.js b/js/src/jit-test/tests/debug/Object-boundTargetFunction-03.js
new file mode 100644
index 0000000000..2fb45f3822
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-boundTargetFunction-03.js
@@ -0,0 +1,20 @@
+// Test that inspecting a bound function that was bound again does the right
+// thing.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+var expr = "function f() { return this; }; var bf = f.bind(1, 2).bind(3, 4); bf";
+var bfw = gw.executeInGlobal(expr).return;
+
+assertEq(bfw.isBoundFunction, true);
+assertEq(bfw.boundThis, 3);
+var args = bfw.boundArguments;
+assertEq(args.length, 1);
+assertEq(args[0], 4);
+
+assertEq(bfw.boundTargetFunction.isBoundFunction, true);
+assertEq(bfw.boundTargetFunction.boundThis, 1);
+args = bfw.boundTargetFunction.boundArguments;
+assertEq(args.length, 1);
+assertEq(args[0], 2);
diff --git a/js/src/jit-test/tests/debug/Object-callable.js b/js/src/jit-test/tests/debug/Object-callable.js
new file mode 100644
index 0000000000..31130b0175
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-callable.js
@@ -0,0 +1,18 @@
+// Test Debugger.Object.prototype.callable.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ assertEq(frame.arguments[0].callable, frame.arguments[1]);
+ hits++;
+};
+
+g.eval("function f(obj, iscallable) { debugger; }");
+
+g.eval("f({}, false);");
+g.eval("f(Function.prototype, true);");
+g.eval("f(f, true);");
+g.eval("f(new Proxy({}, {}), false);");
+g.eval("f(new Proxy(f, {}), true);");
+assertEq(hits, 5);
diff --git a/js/src/jit-test/tests/debug/Object-class.js b/js/src/jit-test/tests/debug/Object-class.js
new file mode 100644
index 0000000000..ee64998190
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-class.js
@@ -0,0 +1,26 @@
+// Basic tests for Debugger.Object.prototype.class.
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var hits = 0;
+g.eval('function f() { debugger; }');
+
+dbg.onDebuggerStatement = function (frame) {
+ var arr = frame.arguments;
+ assertEq(arr[0].class, "Object");
+ assertEq(arr[1].class, "Array");
+ assertEq(arr[2].class, "Function");
+ assertEq(arr[3].class, "Date");
+ assertEq(arr[4].class, "Object");
+ assertEq(arr[5].class, "Function");
+ assertEq(arr[6].class, "Object");
+ hits++;
+};
+g.f(Object.prototype, [], eval, new Date,
+ new Proxy({}, {}), new Proxy(eval, {}), new Proxy(new Date, {}));
+assertEq(hits, 1);
+
+// Debugger.Object.prototype.class should see through cross-compartment
+// wrappers.
+g.eval('f(Object.prototype, [], eval, new Date,\
+ new Proxy({}, {}), new Proxy(f, {}), new Proxy(new Date, {}));');
+assertEq(hits, 2);
diff --git a/js/src/jit-test/tests/debug/Object-createSource.js b/js/src/jit-test/tests/debug/Object-createSource.js
new file mode 100644
index 0000000000..e71bd702e1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-createSource.js
@@ -0,0 +1,18 @@
+// createSource creates new sources.
+
+let g = newGlobal({newCompartment: true});
+let dbg = new Debugger(g);
+let gdbg = dbg.addDebuggee(g);
+
+let source = gdbg.createSource({
+ text: "x = 3",
+ url: "foo.js",
+ startLine: 3,
+ sourceMapURL: "sourceMapURL",
+ isScriptElement: true,
+});
+assertEq(source.text, "x = 3");
+assertEq(source.url, "foo.js");
+assertEq(source.startLine, 3);
+assertEq(source.sourceMapURL, "sourceMapURL");
+assertEq(source.introductionType, "inlineScript");
diff --git a/js/src/jit-test/tests/debug/Object-defineProperties-01.js b/js/src/jit-test/tests/debug/Object-defineProperties-01.js
new file mode 100644
index 0000000000..36bf55c7a7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-defineProperties-01.js
@@ -0,0 +1,46 @@
+// Debug.Object.prototype.defineProperties.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+
+var descProps = ['configurable', 'enumerable', 'writable', 'value', 'get', 'set'];
+function test(objexpr, descs) {
+ g.eval("obj = (" + objexpr + ");");
+ var gobjw = gw.getOwnPropertyDescriptor("obj").value;
+ gobjw.defineProperties(descs);
+
+ var indirectEval = eval;
+ var obj = indirectEval("(" + objexpr + ");");
+ Object.defineProperties(obj, descs);
+
+ var ids = Object.keys(descs);
+ for (var i = 0; i < ids.length; i++) {
+ var actual = gobjw.getOwnPropertyDescriptor(ids[i]);
+ var expected = Object.getOwnPropertyDescriptor(obj, ids[i]);
+ assertEq(Object.getPrototypeOf(actual), Object.prototype);
+ assertEq(actual.configurable, expected.configurable);
+ assertEq(actual.enumerable, expected.enumerable);
+ for (var j = 0; j < descProps; j++) {
+ var prop = descProps[j];
+ assertEq(prop in actual, prop in expected);
+ assertEq(actual[prop], expected[prop]);
+ }
+ }
+}
+
+test("{}", {});
+test("/abc/", {});
+
+g.eval("var aglobal = newGlobal('same-compartment');");
+var aglobal = newGlobal('same-compartment');
+test("aglobal", {});
+
+var adescs = {a: {enumerable: true, writable: true, value: 0}};
+test("{}", adescs);
+test("{a: 1}", adescs);
+
+var arrdescs = [{value: 'a'}, {value: 'b'}, , {value: 'd'}];
+test("{}", arrdescs);
+test("[]", arrdescs);
+test("[0, 1, 2, 3]", arrdescs);
diff --git a/js/src/jit-test/tests/debug/Object-defineProperties-02.js b/js/src/jit-test/tests/debug/Object-defineProperties-02.js
new file mode 100644
index 0000000000..1383781004
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-defineProperties-02.js
@@ -0,0 +1,33 @@
+// Exceptions thrown by obj.defineProperties are copied into the debugger compartment.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+
+function test(objexpr, descs) {
+ var exca, excb;
+
+ g.eval("obj = (" + objexpr + ");");
+ var gobjw = gw.getOwnPropertyDescriptor("obj").value;
+ try {
+ gobjw.defineProperties(descs);
+ } catch (exc) {
+ exca = exc;
+ }
+
+ var indirectEval = eval;
+ var obj = indirectEval("(" + objexpr + ");");
+ try {
+ Object.defineProperties(obj, descs);
+ } catch (exc) {
+ excb = exc;
+ }
+
+ assertEq(Object.getPrototypeOf(exca), Object.getPrototypeOf(excb));
+ assertEq(exca.message, excb.message);
+ assertEq(typeof exca.fileName, "string");
+ assertEq(typeof exca.stack, "string");
+}
+
+test("Object.create(null, {p: {value: 1}})", {p: {value: 2}});
+test("({})", {x: {get: 'bad'}});
diff --git a/js/src/jit-test/tests/debug/Object-defineProperties-03.js b/js/src/jit-test/tests/debug/Object-defineProperties-03.js
new file mode 100644
index 0000000000..ad5b5c0f3f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-defineProperties-03.js
@@ -0,0 +1,20 @@
+// obj.defineProperties can define accessor properties.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+g.value = undefined;
+g.eval("function gf() { return 12; }\n" +
+ "function sf(v) { value = v; }\n");
+var gfw = gw.getOwnPropertyDescriptor("gf").value;
+var sfw = gw.getOwnPropertyDescriptor("sf").value;
+gw.defineProperties({x: {configurable: true, get: gfw, set: sfw}});
+assertEq(g.x, 12);
+g.x = 'ok';
+assertEq(g.value, 'ok');
+
+var desc = g.Object.getOwnPropertyDescriptor(g, "x");
+assertEq(desc.configurable, true);
+assertEq(desc.enumerable, false);
+assertEq(desc.get, g.gf);
+assertEq(desc.set, g.sf);
diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-01.js b/js/src/jit-test/tests/debug/Object-defineProperty-01.js
new file mode 100644
index 0000000000..91330694b9
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-defineProperty-01.js
@@ -0,0 +1,12 @@
+// obj.defineProperty can define simple data properties.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gobj = dbg.addDebuggee(g);
+gobj.defineProperty("x", {configurable: true, enumerable: true, writable: true, value: 'ok'});
+assertEq(g.x, 'ok');
+
+var desc = g.Object.getOwnPropertyDescriptor(g, "x");
+assertEq(desc.configurable, true);
+assertEq(desc.enumerable, true);
+assertEq(desc.writable, true);
diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-02.js b/js/src/jit-test/tests/debug/Object-defineProperty-02.js
new file mode 100644
index 0000000000..e72fd7f2bb
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-defineProperty-02.js
@@ -0,0 +1,10 @@
+// obj.defineProperty can define a data property with object value.
+
+var g = newGlobal({newCompartment: true});
+g.eval("var a = {};");
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+var desc = gw.getOwnPropertyDescriptor("a");
+assertEq(desc.value instanceof Debugger.Object, true);
+gw.defineProperty("b", desc);
+assertEq(g.a, g.b);
diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-03.js b/js/src/jit-test/tests/debug/Object-defineProperty-03.js
new file mode 100644
index 0000000000..04a2aa8174
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-defineProperty-03.js
@@ -0,0 +1,21 @@
+// defineProperty can set array elements
+
+var g = newGlobal({newCompartment: true});
+g.a = g.Array(0, 1, 2);
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+var aw = gw.getOwnPropertyDescriptor("a").value;
+
+aw.defineProperty(0, {value: 'ok0'}); // by number
+assertEq(g.a[0], 'ok0');
+var desc = g.Object.getOwnPropertyDescriptor(g.a, "0");
+assertEq(desc.configurable, true);
+assertEq(desc.enumerable, true);
+assertEq(desc.writable, true);
+
+aw.defineProperty("1", {value: 'ok1'}); // by string
+assertEq(g.a[1], 'ok1');
+desc = g.Object.getOwnPropertyDescriptor(g.a, "1");
+assertEq(desc.configurable, true);
+assertEq(desc.enumerable, true);
+assertEq(desc.writable, true);
diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-04.js b/js/src/jit-test/tests/debug/Object-defineProperty-04.js
new file mode 100644
index 0000000000..023a0cbf83
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-defineProperty-04.js
@@ -0,0 +1,9 @@
+// defineProperty can add array elements, bumping length
+
+var g = newGlobal({newCompartment: true});
+g.a = g.Array(0, 1, 2);
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+var aw = gw.getOwnPropertyDescriptor("a").value;
+aw.defineProperty(3, {configurable: true, enumerable: true, writable: true, value: 3});
+assertEq(g.a.length, 4);
diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-05.js b/js/src/jit-test/tests/debug/Object-defineProperty-05.js
new file mode 100644
index 0000000000..be494faaf5
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-defineProperty-05.js
@@ -0,0 +1,20 @@
+// defineProperty can define accessor properties.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+g.value = undefined;
+g.eval("function gf() { return 12; }\n" +
+ "function sf(v) { value = v; }\n");
+var gfw = gw.getOwnPropertyDescriptor("gf").value;
+var sfw = gw.getOwnPropertyDescriptor("sf").value;
+gw.defineProperty("x", {configurable: true, get: gfw, set: sfw});
+assertEq(g.x, 12);
+g.x = 'ok';
+assertEq(g.value, 'ok');
+
+var desc = g.Object.getOwnPropertyDescriptor(g, "x");
+assertEq(desc.configurable, true);
+assertEq(desc.enumerable, false);
+assertEq(desc.get, g.gf);
+assertEq(desc.set, g.sf);
diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-06.js b/js/src/jit-test/tests/debug/Object-defineProperty-06.js
new file mode 100644
index 0000000000..0739746bd7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-defineProperty-06.js
@@ -0,0 +1,21 @@
+// obj.defineProperty with vague descriptors works like Object.defineProperty
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+
+gw.defineProperty("p", {configurable: true, enumerable: true});
+assertEq(g.p, undefined);
+var desc = g.Object.getOwnPropertyDescriptor(g, "p");
+assertEq(desc.configurable, true);
+assertEq(desc.enumerable, true);
+assertEq(desc.value, undefined);
+assertEq(desc.writable, false);
+
+gw.defineProperty("q", {});
+assertEq(g.q, undefined);
+var desc = g.Object.getOwnPropertyDescriptor(g, "q");
+assertEq(desc.configurable, false);
+assertEq(desc.enumerable, false);
+assertEq(desc.value, undefined);
+assertEq(desc.writable, false);
diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-07.js b/js/src/jit-test/tests/debug/Object-defineProperty-07.js
new file mode 100644
index 0000000000..8565c47357
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-defineProperty-07.js
@@ -0,0 +1,10 @@
+// obj.defineProperty throws if a value, getter, or setter is not a debuggee value.
+
+load(libdir + "asserts.js");
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gobj = dbg.addDebuggee(g);
+assertThrowsInstanceOf(function () { gobj.defineProperty('x', {value: {}}); }, TypeError);
+assertThrowsInstanceOf(function () { gobj.defineProperty('x', {get: Number}); }, TypeError);
+assertThrowsInstanceOf(function () { gobj.defineProperty('x', {get: gobj, set: Number}) },
+ TypeError);
diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-08.js b/js/src/jit-test/tests/debug/Object-defineProperty-08.js
new file mode 100644
index 0000000000..fb694719e3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-defineProperty-08.js
@@ -0,0 +1,10 @@
+// obj.defineProperty throws if a value, getter, or setter is in a different compartment than obj
+
+load(libdir + "asserts.js");
+var g1 = newGlobal({newCompartment: true});
+var g2 = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var g1w = dbg.addDebuggee(g1);
+var g2w = dbg.addDebuggee(g2);
+assertThrowsInstanceOf(function () { g1w.defineProperty('x', {value: g2w}); }, TypeError);
+assertThrowsInstanceOf(function () { g1w.defineProperty('x', {get: g1w, set: g2w}); }, TypeError);
diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-09.js b/js/src/jit-test/tests/debug/Object-defineProperty-09.js
new file mode 100644
index 0000000000..dc69e4fde1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-defineProperty-09.js
@@ -0,0 +1,24 @@
+// defineProperty can't re-define non-configurable properties.
+// Also: when defineProperty throws, the exception is native to the debugger
+// compartment, not a wrapper.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+gw.defineProperty("p", {value: 1});
+g.p = 4;
+assertEq(g.p, 1);
+
+var threw;
+try {
+ gw.defineProperty("p", {value: 2});
+ threw = false;
+} catch (exc) {
+ threw = true;
+ assertEq(exc instanceof TypeError, true);
+ assertEq(typeof exc.message, "string");
+ assertEq(typeof exc.stack, "string");
+}
+assertEq(threw, true);
+
+assertEq(g.p, 1);
diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-10.js b/js/src/jit-test/tests/debug/Object-defineProperty-10.js
new file mode 100644
index 0000000000..3e4f9930c5
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-defineProperty-10.js
@@ -0,0 +1,10 @@
+// defineProperty can make a non-configurable writable property non-writable
+
+load(libdir + "asserts.js");
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+gw.defineProperty("p", {writable: true, value: 1});
+gw.defineProperty("p", {writable: false});
+g.p = 4;
+assertEq(g.p, 1);
diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-11.js b/js/src/jit-test/tests/debug/Object-defineProperty-11.js
new file mode 100644
index 0000000000..03ed7d651e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-defineProperty-11.js
@@ -0,0 +1,16 @@
+// obj.defineProperty works when obj's referent is a wrapper.
+
+var x = {};
+var g = newGlobal({newCompartment: true});
+g.x = x;
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+var xw = gw.getOwnPropertyDescriptor("x").value;
+xw.defineProperty("p", {configurable: true, enumerable: true, writable: true, value: gw});
+assertEq(x.p, g);
+
+var desc = Object.getOwnPropertyDescriptor(x, "p");
+assertEq(desc.configurable, true);
+assertEq(desc.enumerable, true);
+assertEq(desc.writable, true);
+assertEq(desc.value, g);
diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-12.js b/js/src/jit-test/tests/debug/Object-defineProperty-12.js
new file mode 100644
index 0000000000..821617711d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-defineProperty-12.js
@@ -0,0 +1,18 @@
+// obj.defineProperty redefining an existing property leaves unspecified attributes unchanged.
+
+var g = newGlobal({newCompartment: true});
+g.p = 1;
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+
+gw.defineProperty("p", {value: 2});
+assertEq(g.p, 2);
+
+var desc = Object.getOwnPropertyDescriptor(g, "p");
+assertEq(desc.configurable, true);
+assertEq(desc.enumerable, true);
+assertEq(desc.writable, true);
+assertEq(desc.value, 2);
+
+g.p = 3;
+assertEq(g.p, 3);
diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-13.js b/js/src/jit-test/tests/debug/Object-defineProperty-13.js
new file mode 100644
index 0000000000..3bfd554c14
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-defineProperty-13.js
@@ -0,0 +1,16 @@
+// defineProperty throws if a getter or setter is neither undefined nor callable.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+
+for (let v of [null, false, 'bad', 0, 2.76, {}]) {
+ assertThrowsInstanceOf(function () {
+ gw.defineProperty("p", {configurable: true, get: v});
+ }, TypeError);
+ assertThrowsInstanceOf(function () {
+ gw.defineProperty("p", {configurable: true, set: v});
+ }, TypeError);
+}
diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-14.js b/js/src/jit-test/tests/debug/Object-defineProperty-14.js
new file mode 100644
index 0000000000..5a6deda121
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-defineProperty-14.js
@@ -0,0 +1,15 @@
+// defineProperty accepts undefined for desc.get/set.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+
+gw.defineProperty("p", {get: undefined, set: undefined});
+
+var desc = g.eval("Object.getOwnPropertyDescriptor(this, 'p')");
+assertEq("get" in desc, true);
+assertEq("set" in desc, true);
+assertEq(desc.get, undefined);
+assertEq(desc.set, undefined);
diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-non-primitive-key.js b/js/src/jit-test/tests/debug/Object-defineProperty-non-primitive-key.js
new file mode 100644
index 0000000000..c3c503968f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-defineProperty-non-primitive-key.js
@@ -0,0 +1,41 @@
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var gw = dbg.addDebuggee(g);
+
+g.eval(`
+ var obj = {
+ p: 0,
+ [Symbol.iterator]: 0,
+ };
+`);
+
+// Return |key| as an object.
+function toObject(key) {
+ return {
+ [Symbol.toPrimitive]() {
+ return key;
+ }
+ };
+}
+
+let obj = gw.getProperty("obj").return;
+
+for (let key of obj.getOwnPropertyNames()) {
+ let keyObject = toObject(key);
+
+ obj.defineProperty(key, {value: 1});
+ assertEq(g.obj[key], 1);
+
+ obj.defineProperty(keyObject, {value: 1});
+ assertEq(g.obj[key], 1);
+}
+
+for (let key of obj.getOwnPropertySymbols()) {
+ let keyObject = toObject(key);
+
+ obj.defineProperty(key, {value: 1});
+ assertEq(g.obj[key], 1);
+
+ obj.defineProperty(keyObject, {value: 1});
+ assertEq(g.obj[key], 1);
+}
diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-surfaces-01.js b/js/src/jit-test/tests/debug/Object-defineProperty-surfaces-01.js
new file mode 100644
index 0000000000..8cf9d85200
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-defineProperty-surfaces-01.js
@@ -0,0 +1,8 @@
+// Debugger.Object.prototype.defineProperty with too few arguments throws.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+assertThrowsInstanceOf(function () { gw.defineProperty("x"); }, TypeError);
diff --git a/js/src/jit-test/tests/debug/Object-deleteProperty-01.js b/js/src/jit-test/tests/debug/Object-deleteProperty-01.js
new file mode 100644
index 0000000000..65a465b75e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-deleteProperty-01.js
@@ -0,0 +1,17 @@
+// Basic deleteProperty tests.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+
+assertEq(gw.deleteProperty("no such property"), true);
+
+g.Object.defineProperty(g, "p", {configurable: true, value: 0});
+assertEq(gw.deleteProperty("p"), true);
+
+g[0] = 0;
+assertEq(gw.deleteProperty(0), true);
+assertEq("0" in g, false);
+
+assertEq(gw.deleteProperty(), false); // can't delete g.undefined
+assertEq(g.undefined, undefined);
diff --git a/js/src/jit-test/tests/debug/Object-deleteProperty-error-01.js b/js/src/jit-test/tests/debug/Object-deleteProperty-error-01.js
new file mode 100644
index 0000000000..38827c908d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-deleteProperty-error-01.js
@@ -0,0 +1,16 @@
+// Don't crash when a scripted proxy handler throws Error.prototype.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+ try {
+ frame.arguments[0].deleteProperty("x");
+ } catch (exc) {
+ return;
+ }
+ throw new Error("deleteProperty should throw");
+};
+
+g.eval("function h(obj) { debugger; }");
+g.eval("h(new Proxy({}, { deleteProperty() { throw Error.prototype; }}));");
+
diff --git a/js/src/jit-test/tests/debug/Object-deleteProperty-error-02.js b/js/src/jit-test/tests/debug/Object-deleteProperty-error-02.js
new file mode 100644
index 0000000000..e685b93f46
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-deleteProperty-error-02.js
@@ -0,0 +1,19 @@
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+ try {
+ frame.arguments[0].deleteProperty("x");
+ } catch (exc) {
+ assertEq(exc instanceof Debugger.DebuggeeWouldRun, true);
+ return;
+ }
+ throw new Error("deleteProperty should throw");
+};
+
+g.evaluate("function h(obj) { debugger; } \n" +
+ "h(new Proxy({}, \n" +
+ " { deleteProperty: function () { \n" +
+ " var e = new ReferenceError('diaf', 'fail'); \n" +
+ " throw e; \n" +
+ " } \n" +
+ " }));");
diff --git a/js/src/jit-test/tests/debug/Object-deleteProperty-non-primitive-key.js b/js/src/jit-test/tests/debug/Object-deleteProperty-non-primitive-key.js
new file mode 100644
index 0000000000..83a10b89eb
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-deleteProperty-non-primitive-key.js
@@ -0,0 +1,49 @@
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var gw = dbg.addDebuggee(g);
+
+g.eval(`
+ var obj = {
+ p: 0,
+ [Symbol.iterator]: 0,
+ };
+`);
+
+// Return |key| as an object.
+function toObject(key) {
+ return {
+ [Symbol.toPrimitive]() {
+ return key;
+ }
+ };
+}
+
+let obj = gw.getProperty("obj").return;
+
+for (let key of obj.getOwnPropertyNames()) {
+ let keyObject = toObject(key);
+
+ g.obj[key] = 1;
+ assertEq(g.obj[key], 1);
+ assertEq(obj.deleteProperty(key), true);
+ assertEq(g.obj[key], undefined);
+
+ g.obj[key] = 1;
+ assertEq(g.obj[key], 1);
+ assertEq(obj.deleteProperty(keyObject), true);
+ assertEq(g.obj[key], undefined);
+}
+
+for (let key of obj.getOwnPropertySymbols()) {
+ let keyObject = toObject(key);
+
+ g.obj[key] = 1;
+ assertEq(g.obj[key], 1);
+ assertEq(obj.deleteProperty(key), true);
+ assertEq(g.obj[key], undefined);
+
+ g.obj[key] = 1;
+ assertEq(g.obj[key], 1);
+ assertEq(obj.deleteProperty(keyObject), true);
+ assertEq(g.obj[key], undefined);
+}
diff --git a/js/src/jit-test/tests/debug/Object-displayName-01.js b/js/src/jit-test/tests/debug/Object-displayName-01.js
new file mode 100644
index 0000000000..2a316f9d38
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-displayName-01.js
@@ -0,0 +1,21 @@
+// Debugger.Object.prototype.displayName
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var name;
+dbg.onDebuggerStatement = function (frame) { name = frame.callee.displayName; };
+
+g.eval("(function f() { debugger; })();");
+assertEq(name, "f");
+g.eval("(function () { debugger; })();");
+assertEq(name, undefined);
+g.eval("Function('debugger;')();");
+assertEq(name, "anonymous");
+g.eval("var f = function() { debugger; }; f()");
+assertEq(name, "f");
+g.eval("var a = {}; a.f = function() { debugger; }; a.f()");
+assertEq(name, "a.f");
+g.eval("(async function grondo() { debugger; })();");
+assertEq(name, "grondo");
+g.eval("(async function* estux() { debugger; })().next();");
+assertEq(name, "estux");
diff --git a/js/src/jit-test/tests/debug/Object-environment-01.js b/js/src/jit-test/tests/debug/Object-environment-01.js
new file mode 100644
index 0000000000..ea74761708
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-environment-01.js
@@ -0,0 +1,17 @@
+// obj.environment is undefined when the referent is not a JS function.
+
+var g = newGlobal({newCompartment: true})
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+assertEq(gw.environment, undefined);
+
+g.eval("var r = /x/;");
+var rw = gw.getOwnPropertyDescriptor("r").value;
+assertEq(rw.class, "RegExp");
+assertEq(rw.environment, undefined);
+
+// Native function.
+var fw = gw.getOwnPropertyDescriptor("parseInt").value;
+assertEq(fw.class, "Function");
+assertEq(fw.environment, undefined);
+
diff --git a/js/src/jit-test/tests/debug/Object-environment-02.js b/js/src/jit-test/tests/debug/Object-environment-02.js
new file mode 100644
index 0000000000..d499e1ca2c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-environment-02.js
@@ -0,0 +1,22 @@
+// The .environment of a function Debugger.Object is an Environment object.
+
+var g = newGlobal({newCompartment: true})
+var dbg = new Debugger;
+var gDO = dbg.addDebuggee(g);
+
+function check(expr) {
+ print("checking " + JSON.stringify(expr));
+ let completion = gDO.executeInGlobal(expr);
+ if (completion.throw)
+ throw completion.throw.unsafeDereference();
+ assertEq(completion.return.environment instanceof Debugger.Environment, true);
+}
+
+g.eval('function j(a) { }');
+
+check('j');
+check('(() => { })');
+check('(function f() { })');
+check('(function* g() { })');
+check('(async function m() { })');
+check('(async function* n() { })');
diff --git a/js/src/jit-test/tests/debug/Object-errorLineNumber-errorColumnNumber.js b/js/src/jit-test/tests/debug/Object-errorLineNumber-errorColumnNumber.js
new file mode 100644
index 0000000000..a581f93b6e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-errorLineNumber-errorColumnNumber.js
@@ -0,0 +1,55 @@
+// Debugger.Object.prototype.{errorLineNumber,errorColumnNumber} return the
+// line number and column number associated with some error object.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+
+var syntaxError = gw.executeInGlobal("\nlet a, a;").throw;
+assertEq(syntaxError.errorLineNumber, 2);
+assertEq(syntaxError.errorColumnNumber, 7);
+
+var typeError = gw.executeInGlobal("\n1 + f();").throw;
+assertEq(typeError.errorLineNumber, 2);
+assertEq(typeError.errorColumnNumber, 1);
+
+// Custom errors have no line/column numbers .
+var customError = gw.executeInGlobal("\nthrow 1;").throw;
+assertEq(customError.errorLineNumber, undefined);
+assertEq(customError.errorColumnNumber, undefined);
+
+customError = gw.executeInGlobal("\nthrow { errorLineNumber: 10, errorColumnNumber: 20 };").throw;
+assertEq(customError.errorLineNumber, undefined);
+assertEq(customError.errorColumnNumber, undefined);
+
+customError = gw.executeInGlobal("\nthrow { lineNumber: 10, columnNumber: 20 };").throw;
+assertEq(customError.errorLineNumber, undefined);
+assertEq(customError.errorColumnNumber, undefined);
+
+// Ensure that the method works across globals.
+g.eval(`var g = newGlobal({newCompartment: true});
+ g.eval('var err; \\n' +
+ 'try {\\n' +
+ ' f();\\n' +
+ '} catch (e) {\\n' +
+ ' err = e;\\n' +
+ '}');
+ var err2 = g.err;`);
+var otherGlobalError = gw.executeInGlobal("throw err2").throw;
+assertEq(otherGlobalError.errorLineNumber, 3);
+assertEq(otherGlobalError.errorColumnNumber, 3);
+
+// Ensure that non-error objects return undefined.
+const Args = [
+ "1",
+ "'blah'",
+ "({})",
+ "[]",
+ "() => 1"
+]
+
+for (let arg of Args) {
+ let nonError = gw.executeInGlobal(`${arg}`).return;
+ assertEq(nonError.errorLineNumber, undefined);
+ assertEq(nonError.errorColumnNumber, undefined);
+}
diff --git a/js/src/jit-test/tests/debug/Object-executeInGlobal-01.js b/js/src/jit-test/tests/debug/Object-executeInGlobal-01.js
new file mode 100644
index 0000000000..af2fa3600f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-executeInGlobal-01.js
@@ -0,0 +1,13 @@
+// Debugger.Object.prototype.executeInGlobal basics
+
+var g = newGlobal({newCompartment: true});
+var h = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+var hw = dbg.addDebuggee(h);
+
+g.y = "Bitte Orca";
+h.y = "Visiter";
+var y = "W H O K I L L";
+assertEq(gw.executeInGlobal('y').return, "Bitte Orca");
+assertEq(hw.executeInGlobal('y').return, "Visiter");
diff --git a/js/src/jit-test/tests/debug/Object-executeInGlobal-02.js b/js/src/jit-test/tests/debug/Object-executeInGlobal-02.js
new file mode 100644
index 0000000000..8a2592837f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-executeInGlobal-02.js
@@ -0,0 +1,20 @@
+// Debugger.Object.prototype.executeInGlobal argument validation
+
+load(libdir + 'asserts.js');
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+var gobj = gw.makeDebuggeeValue(g.eval("({})"));
+
+assertThrowsInstanceOf(function () { gw.executeInGlobal(); }, TypeError);
+assertThrowsInstanceOf(function () { gw.executeInGlobal(10); }, TypeError);
+assertThrowsInstanceOf(function () { gobj.executeInGlobal('42'); }, TypeError);
+assertEq(gw.executeInGlobal('42').return, 42);
+
+assertThrowsInstanceOf(function () { gw.executeInGlobalWithBindings(); }, TypeError);
+assertThrowsInstanceOf(function () { gw.executeInGlobalWithBindings('42'); }, TypeError);
+assertThrowsInstanceOf(function () { gw.executeInGlobalWithBindings(10, 1729); }, TypeError);
+assertThrowsInstanceOf(function () { gw.executeInGlobalWithBindings('42', 1729); }, TypeError);
+assertThrowsInstanceOf(function () { gobj.executeInGlobalWithBindings('42', {}); }, TypeError);
+assertEq(gw.executeInGlobalWithBindings('42', {}).return, 42);
diff --git a/js/src/jit-test/tests/debug/Object-executeInGlobal-03.js b/js/src/jit-test/tests/debug/Object-executeInGlobal-03.js
new file mode 100644
index 0000000000..1d2edd4ccc
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-executeInGlobal-03.js
@@ -0,0 +1,19 @@
+// Debugger.Object.prototype.executeInGlobal: closures capturing the global
+
+var g = newGlobal({newCompartment: true});
+var h = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+var hw = dbg.addDebuggee(h);
+
+g.x = "W H O K I L L";
+h.x = "No Color";
+var c1 = gw.executeInGlobal('(function () { return x; })').return;
+var c2 = hw.executeInGlobal('(function () { return x; })').return;
+var c3 = gw.executeInGlobalWithBindings('(function () { return x + y; })', { y:" In Rainbows" }).return;
+var c4 = hw.executeInGlobalWithBindings('(function () { return x + y; })', { y:" In Rainbows" }).return;
+
+assertEq(c1.call(null).return, "W H O K I L L");
+assertEq(c2.call(null).return, "No Color");
+assertEq(c3.call(null).return, "W H O K I L L In Rainbows");
+assertEq(c4.call(null).return, "No Color In Rainbows");
diff --git a/js/src/jit-test/tests/debug/Object-executeInGlobal-04.js b/js/src/jit-test/tests/debug/Object-executeInGlobal-04.js
new file mode 100644
index 0000000000..cf53e8a67f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-executeInGlobal-04.js
@@ -0,0 +1,55 @@
+// Debugger.Object.prototype.executeInGlobal: nested evals
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+
+assertEq(gw.executeInGlobal("eval('\"Awake\"');").return, "Awake");
+
+// Evaluating non-strict-mode code uses the given global as its variable
+// environment.
+g.x = "Swing Lo Magellan";
+g.y = "The Milk-Eyed Mender";
+assertEq(gw.executeInGlobal("eval('var x = \"A Brief History of Love\"');\n"
+ + "var y = 'Merriweather Post Pavilion';"
+ + "x;").return,
+ "A Brief History of Love");
+assertEq(g.x, "A Brief History of Love");
+assertEq(g.y, "Merriweather Post Pavilion");
+
+// As above, but notice that we still create bindings on the global, even
+// when we've interposed a new environment via 'withBindings'.
+g.x = "Swing Lo Magellan";
+g.y = "The Milk-Eyed Mender";
+assertEq(gw.executeInGlobalWithBindings("eval('var x = d1;'); var y = d2; x;",
+ { d1: "A Brief History of Love",
+ d2: "Merriweather Post Pavilion" }).return,
+ "A Brief History of Love");
+assertEq(g.x, "A Brief History of Love");
+assertEq(g.y, "Merriweather Post Pavilion");
+
+
+// Strict mode code variants of the above:
+
+// Strict mode still lets us create bindings on the global as this is
+// equivalent to executing statements at the global level. But note that
+// setting strict mode means that nested evals get their own call objects.
+g.x = "Swing Lo Magellan";
+g.y = "The Milk-Eyed Mender";
+assertEq(gw.executeInGlobal("\'use strict\';\n"
+ + "eval('var x = \"A Brief History of Love\"');\n"
+ + "var y = \"Merriweather Post Pavilion\";"
+ + "x;").return,
+ "Swing Lo Magellan");
+assertEq(g.x, "Swing Lo Magellan");
+assertEq(g.y, "Merriweather Post Pavilion");
+
+// Introducing a bindings object shouldn't change this behavior.
+g.x = "Swing Lo Magellan";
+g.y = "The Milk-Eyed Mender";
+assertEq(gw.executeInGlobalWithBindings("'use strict'; eval('var x = d1;'); var y = d2; x;",
+ { d1: "A Brief History of Love",
+ d2: "Merriweather Post Pavilion" }).return,
+ "Swing Lo Magellan");
+assertEq(g.x, "Swing Lo Magellan");
+assertEq(g.y, "Merriweather Post Pavilion");
diff --git a/js/src/jit-test/tests/debug/Object-executeInGlobal-05.js b/js/src/jit-test/tests/debug/Object-executeInGlobal-05.js
new file mode 100644
index 0000000000..899a6cf1eb
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-executeInGlobal-05.js
@@ -0,0 +1,21 @@
+// Debugger.Object.prototype.executeInGlobal throws when asked to evaluate in a CCW of a global.
+
+load(libdir + 'asserts.js');
+
+var dbg = new Debugger();
+
+var g1 = newGlobal({newCompartment: true});
+var dg1 = dbg.addDebuggee(g1);
+
+var g2 = newGlobal({newCompartment: true});
+var dg2 = dbg.addDebuggee(g2);
+
+// Generate a Debugger.Object viewing g2 from g1's compartment.
+var dg1wg2 = dg1.makeDebuggeeValue(g2);
+assertEq(dg1wg2.unwrap(), dg2.makeDebuggeeValue(g2));
+assertThrowsInstanceOf(function () { dg1wg2.executeInGlobal('1'); }, TypeError);
+assertThrowsInstanceOf(function () { dg1wg2.executeInGlobalWithBindings('x', { x: 1 }); }, TypeError);
+
+// These, however, should not throw.
+assertEq(dg1.executeInGlobal('1729').return, 1729);
+assertEq(dg1.executeInGlobalWithBindings('x', { x: 1729 }).return, 1729);
diff --git a/js/src/jit-test/tests/debug/Object-executeInGlobal-06.js b/js/src/jit-test/tests/debug/Object-executeInGlobal-06.js
new file mode 100644
index 0000000000..37fdf4db5c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-executeInGlobal-06.js
@@ -0,0 +1,8 @@
+// Debugger.Object.prototype.executeInGlobal sets 'this' to the global.
+
+var dbg = new Debugger;
+var g1 = newGlobal({newCompartment: true});
+var g1w = dbg.addDebuggee(g1);
+
+assertEq(g1w.executeInGlobal('this').return, g1w.makeDebuggeeValue(g1));
+assertEq(g1w.executeInGlobalWithBindings('this', { x:42 }).return, g1w.makeDebuggeeValue(g1));
diff --git a/js/src/jit-test/tests/debug/Object-executeInGlobal-07.js b/js/src/jit-test/tests/debug/Object-executeInGlobal-07.js
new file mode 100644
index 0000000000..09f5d7430f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-executeInGlobal-07.js
@@ -0,0 +1,24 @@
+// executeInGlobal correctly handles optional custom url option
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var debuggee = dbg.getDebuggees()[0];
+var count = 0;
+
+function testUrl (options, expected) {
+ count++;
+ dbg.onNewScript = function(script){
+ dbg.onNewScript = undefined;
+ assertEq(script.url, expected);
+ count--;
+ };
+ debuggee.executeInGlobal("", options);
+}
+
+
+testUrl(undefined, "debugger eval code");
+testUrl(null, "debugger eval code");
+testUrl({ url: undefined }, "debugger eval code");
+testUrl({ url: null }, "null");
+testUrl({ url: 5 }, "5");
+testUrl({ url: "test" }, "test");
+assertEq(count, 0);
diff --git a/js/src/jit-test/tests/debug/Object-executeInGlobal-08.js b/js/src/jit-test/tests/debug/Object-executeInGlobal-08.js
new file mode 100644
index 0000000000..3c3819f2d3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-executeInGlobal-08.js
@@ -0,0 +1,22 @@
+// executeInGlobal correctly handles optional lineNumber option
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var debuggee = dbg.getDebuggees()[0];
+var count = 0;
+
+function testLineNumber (options, expected) {
+ count++;
+ dbg.onNewScript = function(script){
+ dbg.onNewScript = undefined;
+ assertEq(script.startLine, expected);
+ count--;
+ };
+ debuggee.executeInGlobal("", options);
+}
+
+
+testLineNumber(undefined, 1);
+testLineNumber({}, 1);
+testLineNumber({ lineNumber: undefined }, 1);
+testLineNumber({ lineNumber: 5 }, 5);
+assertEq(count, 0);
diff --git a/js/src/jit-test/tests/debug/Object-executeInGlobal-09.js b/js/src/jit-test/tests/debug/Object-executeInGlobal-09.js
new file mode 100644
index 0000000000..a4df6d5517
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-executeInGlobal-09.js
@@ -0,0 +1,9 @@
+// The frame created for executeInGlobal is never marked as a 'FUNCTION' frame.
+
+(function () {
+ var g = newGlobal({newCompartment: true});
+ var dbg = new Debugger;
+ var gw = dbg.addDebuggee(g);
+ gw.executeInGlobalWithBindings("eval('Math')",{}).return
+})();
+
diff --git a/js/src/jit-test/tests/debug/Object-executeInGlobal-10.js b/js/src/jit-test/tests/debug/Object-executeInGlobal-10.js
new file mode 100644
index 0000000000..0120bcc1dd
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-executeInGlobal-10.js
@@ -0,0 +1,13 @@
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+
+// executeInGlobal should be able to introduce and persist lexical bindings.
+assertEq(gw.executeInGlobal(`let x = 42; x;`).return, 42);
+assertEq(gw.executeInGlobal(`x;`).return, 42);
+
+// By contrast, Debugger.Frame.eval is like direct eval, and shouldn't be able
+// to introduce new lexical bindings.
+dbg.onDebuggerStatement = function (frame) { frame.eval(`let y = 84;`); };
+g.eval(`debugger;`);
+assertEq(!!gw.executeInGlobal(`y;`).throw, true);
diff --git a/js/src/jit-test/tests/debug/Object-forceLexicalInitializationByName.js b/js/src/jit-test/tests/debug/Object-forceLexicalInitializationByName.js
new file mode 100644
index 0000000000..7550d15ef0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-forceLexicalInitializationByName.js
@@ -0,0 +1,61 @@
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+
+let errorOne, errorTwo;
+
+function evalErrorStr(global, evalString) {
+ try {
+ global.evaluate(evalString);
+ return undefined;
+ } catch (e) {
+ return e.toString();
+ }
+}
+
+
+assertEq(evalErrorStr(g, "let y = IDONTEXIST;"), "ReferenceError: IDONTEXIST is not defined");
+assertEq(evalErrorStr(g, "y = 1;"),
+ "ReferenceError: can't access lexical declaration 'y' before initialization");
+
+// Here we flip the uninitialized binding to undfined.
+assertEq(gw.forceLexicalInitializationByName("y"), true);
+assertEq(g.evaluate("y"), undefined);
+g.evaluate("y = 1;");
+assertEq(g.evaluate("y"), 1);
+
+// Ensure that bogus bindings return false, but otherwise trigger no error or
+// side effect.
+assertEq(gw.forceLexicalInitializationByName("idontexist"), false);
+assertEq(evalErrorStr(g, "idontexist"), "ReferenceError: idontexist is not defined");
+
+// Ensure that ropes (non-atoms) behave properly
+assertEq(gw.forceLexicalInitializationByName(("foo" + "bar" + "bop" + "zopple" + 2 + 3).slice(1)),
+ false);
+assertEq(evalErrorStr(g, "let oobarbopzopple23 = IDONTEXIST;"), "ReferenceError: IDONTEXIST is not defined");
+assertEq(gw.forceLexicalInitializationByName(("foo" + "bar" + "bop" + "zopple" + 2 + 3).slice(1)),
+ true);
+assertEq(g.evaluate("oobarbopzopple23"), undefined);
+
+// Ensure that only strings are accepted by forceLexicalInitializationByName
+const bad_types = [
+ 2112,
+ {geddy: "lee"},
+ () => 1,
+ [],
+ Array,
+ "'1'", // non-identifier
+]
+
+for (var badType of bad_types) {
+ assertThrowsInstanceOf(() => {
+ gw.forceLexicalInitializationByName(badType);
+ }, TypeError);
+}
+
+// Finally, supplying no arguments should throw a type error
+assertThrowsInstanceOf(() => {
+ Debugger.isCompilableUnit();
+}, TypeError);
diff --git a/js/src/jit-test/tests/debug/Object-gc-01.js b/js/src/jit-test/tests/debug/Object-gc-01.js
new file mode 100644
index 0000000000..5dbb01057c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-gc-01.js
@@ -0,0 +1,14 @@
+// Debugger.Objects keep their referents alive.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var arr = [];
+dbg.onDebuggerStatement = function (frame) { arr.push(frame.eval("[]").return); };
+g.eval("for (var i = 0; i < 10; i++) debugger;");
+assertEq(arr.length, 10);
+
+gc();
+
+for (var i = 0; i < arr.length; i++)
+ assertEq(arr[i].class, "Array");
+
diff --git a/js/src/jit-test/tests/debug/Object-getErrorMessageName.js b/js/src/jit-test/tests/debug/Object-getErrorMessageName.js
new file mode 100644
index 0000000000..11c98d93e5
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-getErrorMessageName.js
@@ -0,0 +1,29 @@
+// Debugger.Object.prototype.getErrorMessageName returns the error message name
+// associated with some error object.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+
+assertEq(gw.executeInGlobal("(42).toString(0)").throw.errorMessageName, "JSMSG_BAD_RADIX");
+
+// Custom errors have no error message name.
+assertEq(gw.executeInGlobal("throw new Error()").throw.errorMessageName, undefined);
+
+// Ensure that the method works across globals.
+g.eval(`var g = newGlobal({newCompartment: true});
+ g.eval('var err; try { (42).toString(0); } catch (e) { err = e; }');
+ var err2 = g.err;`);
+assertEq(gw.executeInGlobal("throw err2").throw.errorMessageName, "JSMSG_BAD_RADIX");
+
+// Ensure that non-error objects return undefined.
+const Args = [
+ "1",
+ "'blah'",
+ "({})",
+ "[]",
+ "() => 1"
+]
+
+for (let arg of Args)
+ assertEq(gw.executeInGlobal(`${arg}`).return.errorMessageName, undefined);
diff --git a/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-01.js b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-01.js
new file mode 100644
index 0000000000..a39faeb2f2
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-01.js
@@ -0,0 +1,59 @@
+// getOwnPropertyDescriptor works with simple data properties.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var hits;
+var expected;
+dbg.onDebuggerStatement = function (frame) {
+ var args = frame.arguments;
+ var obj = args[0], id = args[1];
+ var desc = obj.getOwnPropertyDescriptor(id);
+ if (expected === undefined) {
+ assertEq(desc, undefined);
+ } else {
+ assertEq(desc instanceof Object, true);
+ assertEq(desc.enumerable, expected.enumerable);
+ assertEq(desc.configurable, expected.configurable);
+ assertEq(desc.hasOwnProperty("value"), true);
+ assertEq(desc.value, expected.value);
+ assertEq(desc.writable, expected.writable === undefined ? true : expected.writable);
+ assertEq("get" in desc, false);
+ assertEq("set" in desc, false);
+ }
+ hits++;
+};
+
+g.eval("function f(obj, id) { debugger; }");
+
+function test(obj, id, desc) {
+ expected = desc;
+ hits = 0;
+ g.f(obj, id);
+ assertEq(hits, 1);
+}
+
+var obj = g.eval("({a: 1, ' ': undefined, '0': 0})");
+test(obj, "a", {value: 1, enumerable: true, configurable: true});
+test(obj, " ", {value: undefined, enumerable: true, configurable: true});
+test(obj, "b", undefined);
+test(obj, "0", {value: 0, enumerable: true, configurable: true});
+test(obj, 0, {value: 0, enumerable: true, configurable: true});
+
+var arr = g.eval("[7,,]");
+test(arr, 'length', {value: 2, enumerable: false, configurable: false});
+test(arr, 0, {value: 7, enumerable: true, configurable: true});
+test(arr, "0", {value: 7, enumerable: true, configurable: true});
+test(arr, 1, undefined);
+test(arr, "1", undefined);
+test(arr, 2, undefined);
+test(arr, "2", undefined);
+test(arr, "argelfraster", undefined);
+
+var re = g.eval("/erwe/");
+test(re, 'lastIndex', {value: 0, enumerable: false, configurable: false});
+
+// String objects have a well-behaved resolve hook.
+var str = g.eval("new String('hello world')");
+test(str, 'length', {value: 11, enumerable: false, configurable: false, writable: false});
+test(str, 0, {value: 'h', enumerable: true, configurable: false, writable: false});
+test(str, "0", {value: 'h', enumerable: true, configurable: false, writable: false});
diff --git a/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-02.js b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-02.js
new file mode 100644
index 0000000000..7f5368d263
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-02.js
@@ -0,0 +1,8 @@
+// Property values that are objects are reflected as Debugger.Objects.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger();
+var gobj = dbg.addDebuggee(g);
+g.self = g;
+var desc = gobj.getOwnPropertyDescriptor("self");
+assertEq(desc.value, gobj.makeDebuggeeValue(g));
diff --git a/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-03.js b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-03.js
new file mode 100644
index 0000000000..9a30d63d17
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-03.js
@@ -0,0 +1,22 @@
+// obj.getOwnPropertyDescriptor works on global objects.
+
+var g = newGlobal({newCompartment: true});
+g.eval("var v;");
+this.eval("var v;");
+
+var dbg = Debugger();
+var obj = dbg.addDebuggee(g);
+
+function test(name) {
+ var desc = obj.getOwnPropertyDescriptor(name);
+ assertEq(desc instanceof Object, true);
+ var expected = Object.getOwnPropertyDescriptor(this, name);
+ assertEq(Object.prototype.toString.call(desc), Object.prototype.toString.call(expected));
+ assertEq(desc.enumerable, expected.enumerable);
+ assertEq(desc.configurable, expected.configurable);
+ assertEq(desc.writable, expected.writable);
+ assertEq(desc.value, expected.value);
+}
+
+test("Infinity");
+test("v");
diff --git a/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-04.js b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-04.js
new file mode 100644
index 0000000000..a24528bec6
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-04.js
@@ -0,0 +1,18 @@
+// obj.getOwnPropertyDescriptor works on accessor properties.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gdo = dbg.addDebuggee(g);
+
+g.called = false;
+g.eval("var a = {get x() { called = true; }};");
+
+var desc = gdo.getOwnPropertyDescriptor("a").value.getOwnPropertyDescriptor("x");
+assertEq(g.called, false);
+assertEq(desc.enumerable, true);
+assertEq(desc.configurable, true);
+assertEq("value" in desc, false);
+assertEq("writable" in desc, false);
+assertEq(desc.get instanceof Debugger.Object, true);
+assertEq(desc.get.class, "Function");
+assertEq(desc.set, undefined);
diff --git a/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-05.js b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-05.js
new file mode 100644
index 0000000000..b2223076e1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-05.js
@@ -0,0 +1,17 @@
+// obj.getOwnPropertyDescriptor presents getters and setters as Debugger.Object objects.
+
+var g = newGlobal({newCompartment: true});
+g.S = function foreignFunction(v) {};
+g.eval("var a = {};\n" +
+ "function G() {}\n" +
+ "Object.defineProperty(a, 'p', {get: G, set: S})");
+
+var dbg = new Debugger;
+var gdo = dbg.addDebuggee(g);
+var desc = gdo.getOwnPropertyDescriptor("a").value.getOwnPropertyDescriptor("p");
+assertEq(desc.enumerable, false);
+assertEq(desc.configurable, false);
+assertEq("value" in desc, false);
+assertEq("writable" in desc, false);
+assertEq(desc.get, gdo.getOwnPropertyDescriptor("G").value);
+assertEq(desc.set, gdo.getOwnPropertyDescriptor("S").value);
diff --git a/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-06.js b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-06.js
new file mode 100644
index 0000000000..b0d9facb7e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-06.js
@@ -0,0 +1,29 @@
+// obj.getOwnPropertyDescriptor works when obj is a transparent cross-compartment wrapper.
+
+var g1 = newGlobal({newCompartment: true});
+var g2 = newGlobal({newCompartment: true});
+g1.next = g2;
+
+// This test is a little hard to follow, especially the !== assertions.
+//
+// Bottom line: the value of a property of g1 can only be an object in g1's
+// compartment, so any Debugger.Objects obtained by calling
+// g1obj.getOwnPropertyDescriptor should all have referents in g1's
+// compartment.
+
+var dbg = new Debugger;
+var g1obj = dbg.addDebuggee(g1);
+var g2obj = dbg.addDebuggee(g2);
+var wobj = g1obj.getOwnPropertyDescriptor("next").value;
+assertEq(wobj instanceof Debugger.Object, true);
+assertEq(wobj !== g2obj, true); // referents are in two different compartments
+
+g2.x = "ok";
+assertEq(wobj.getOwnPropertyDescriptor("x").value, "ok");
+
+g1.g2min = g2.min = g2.Math.min;
+g2.eval("Object.defineProperty(this, 'y', {get: min});");
+assertEq(g2.y, Infinity);
+var wmin = wobj.getOwnPropertyDescriptor("y").get;
+assertEq(wmin !== g2obj.getOwnPropertyDescriptor("min").value, true); // as above
+assertEq(wmin, g1obj.getOwnPropertyDescriptor("g2min").value);
diff --git a/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-non-primitive-key.js b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-non-primitive-key.js
new file mode 100644
index 0000000000..71b177aa63
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-non-primitive-key.js
@@ -0,0 +1,37 @@
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var gw = dbg.addDebuggee(g);
+
+g.eval(`
+ var obj = {
+ p: 1,
+ [Symbol.iterator]: 2,
+ };
+`);
+
+// Return |key| as an object.
+function toObject(key) {
+ return {
+ [Symbol.toPrimitive]() {
+ return key;
+ }
+ };
+}
+
+let obj = gw.getProperty("obj").return;
+
+for (let key of obj.getOwnPropertyNames()) {
+ let keyObject = toObject(key);
+ let value = g.obj[key];
+
+ assertEq(obj.getOwnPropertyDescriptor(key).value, value);
+ assertEq(obj.getOwnPropertyDescriptor(keyObject).value, value);
+}
+
+for (let key of obj.getOwnPropertySymbols()) {
+ let keyObject = toObject(key);
+ let value = g.obj[key];
+
+ assertEq(obj.getOwnPropertyDescriptor(key).value, value);
+ assertEq(obj.getOwnPropertyDescriptor(keyObject).value, value);
+}
diff --git a/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-surfaces-01.js b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-surfaces-01.js
new file mode 100644
index 0000000000..b0b89f7623
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-surfaces-01.js
@@ -0,0 +1,14 @@
+// The argument to Debugger.Object.prototype.getOwnPropertyDescriptor can be omitted.
+
+var g = newGlobal({newCompartment: true});
+g.eval("var obj = {};");
+
+var dbg = Debugger(g);
+var obj;
+dbg.onDebuggerStatement = function (frame) { obj = frame.eval("obj").return; };
+g.eval("debugger;");
+
+assertEq(obj.getOwnPropertyDescriptor(), undefined);
+g.obj.undefined = 17;
+var desc = obj.getOwnPropertyDescriptor();
+assertEq(desc.value, 17);
diff --git a/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-surfaces-02.js b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-surfaces-02.js
new file mode 100644
index 0000000000..00ff44ea4f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-surfaces-02.js
@@ -0,0 +1,14 @@
+// The argument to Debugger.Object.prototype.getOwnPropertyDescriptor can be an object.
+var g = newGlobal({newCompartment: true});
+g.eval("var obj = {};");
+
+var dbg = Debugger(g);
+var obj;
+dbg.onDebuggerStatement = function (frame) { obj = frame.eval("obj").return; };
+g.eval("debugger;");
+
+var nameobj = {toString: function () { return 'x'; }};
+assertEq(obj.getOwnPropertyDescriptor(nameobj), undefined);
+g.obj.x = 17;
+var desc = obj.getOwnPropertyDescriptor(nameobj);
+assertEq(desc.value, 17);
diff --git a/js/src/jit-test/tests/debug/Object-getOwnPropertyNames-01.js b/js/src/jit-test/tests/debug/Object-getOwnPropertyNames-01.js
new file mode 100644
index 0000000000..b4ceb76f14
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-getOwnPropertyNames-01.js
@@ -0,0 +1,33 @@
+// Basic getOwnPropertyNames tests.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger();
+var gobj = dbg.addDebuggee(g);
+
+function test(code) {
+ code = "(" + code + ");";
+ var expected = Object.getOwnPropertyNames(eval(code));
+ g.eval("obj = " + code);
+ var actual = gobj.getOwnPropertyDescriptor("obj").value.getOwnPropertyNames();
+ assertEq(JSON.stringify(actual.sort()), JSON.stringify(expected.sort()));
+}
+
+test("{}");
+test("{a: 0, b: 1}");
+test("{'name with space': 0}");
+test("{get x() {}, set y(v) {}}");
+test("{get x() { throw 'fit'; }}");
+test("Object.create({a: 1})");
+test("Object.create({get a() {}, set a(v) {}})");
+test("(function () { var x = {a: 0, b: 1}; delete a; return x; })()");
+test("Object.create(null, {x: {value: 0}})");
+test("[]");
+test("[0, 1, 2]");
+test("[,,,,,]");
+test("/a*a/");
+test("function () {}");
+test("(function () {\n" +
+ " var x = {};\n" +
+ " x[Symbol()] = 1; x[Symbol.for('moon')] = 2; x[Symbol.iterator] = 3;\n" +
+ " return x;\n" +
+ "})()");
diff --git a/js/src/jit-test/tests/debug/Object-getOwnPropertyNames-02.js b/js/src/jit-test/tests/debug/Object-getOwnPropertyNames-02.js
new file mode 100644
index 0000000000..df06f55760
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-getOwnPropertyNames-02.js
@@ -0,0 +1,11 @@
+// obj.getOwnPropertyNames() works when obj's referent is itself a cross-compartment wrapper.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger();
+var gobj = dbg.addDebuggee(g);
+g.p = {xyzzy: 8}; // makes a cross-compartment wrapper
+g.p[Symbol.for("plugh")] = 9;
+var wp = gobj.getOwnPropertyDescriptor("p").value;
+var names = wp.getOwnPropertyNames();
+assertEq(names.length, 1);
+assertEq(names[0], "xyzzy");
diff --git a/js/src/jit-test/tests/debug/Object-getOwnPropertySymbols-01.js b/js/src/jit-test/tests/debug/Object-getOwnPropertySymbols-01.js
new file mode 100644
index 0000000000..3e16f3e2e3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-getOwnPropertySymbols-01.js
@@ -0,0 +1,33 @@
+// Basic getOwnPropertSymbols tests.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger();
+var gobj = dbg.addDebuggee(g);
+
+function test(code) {
+ code = "(" + code + ");";
+ var expected = Object.getOwnPropertySymbols(eval(code));
+ g.eval("obj = " + code);
+ var actual = gobj.getOwnPropertyDescriptor("obj").value.getOwnPropertySymbols();
+
+ assertEq(JSON.stringify(actual.map((x) => x.toString()).sort()),
+ JSON.stringify(expected.map((x) => x.toString()).sort()));
+}
+
+test("{}");
+test("Array.prototype"); // Symbol.iterator
+test("Object.create(null)");
+test("(function() {let x = Symbol(); let o = {}; o[x] = 1; return o;})()");
+test("(function() {let x = Symbol('foo'); let o = {}; o[x] = 1; return o;})()");
+test("(function() {let x = Symbol('foo'); let y = Symbol('bar'); \
+ let o = {}; o[x] = 1; o[y] = 2; return o;})()");
+test("(function() {let x = Symbol('foo with spaces'); \
+ let o = {}; o[x] = 1; return o;})()");
+test("(function() {let x = Symbol('foo'); \
+ let o = function(){}; o[x] = 1; return o;})()");
+test("(function() {let x = Symbol('foo'); \
+ let o = Object.create(null); o[x] = 1; return o;})()");
+test("(function() {let x = Symbol('foo'); \
+ let o = new Array(); o[x] = 1; return o;})()");
+test("(function() {let x = Symbol('foo'); \
+ let o = {}; o[x] = 1; delete o[x]; return o;})()");
diff --git a/js/src/jit-test/tests/debug/Object-getOwnPropertySymbols-02.js b/js/src/jit-test/tests/debug/Object-getOwnPropertySymbols-02.js
new file mode 100644
index 0000000000..7f85caa941
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-getOwnPropertySymbols-02.js
@@ -0,0 +1,12 @@
+// obj.getOwnPropertySymbols() works when obj's referent is itself a cross-compartment wrapper.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger();
+var gobj = dbg.addDebuggee(g);
+g.p = {xyzzy: 8}; // makes a cross-compartment wrapper
+var sym = Symbol("plugh");
+g.p[sym] = 9;
+var wp = gobj.getOwnPropertyDescriptor("p").value;
+var names = wp.getOwnPropertySymbols();
+assertEq(names.length, 1);
+assertEq(names[0], sym);
diff --git a/js/src/jit-test/tests/debug/Object-getPromiseReactions-01.js b/js/src/jit-test/tests/debug/Object-getPromiseReactions-01.js
new file mode 100644
index 0000000000..ab56984960
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-getPromiseReactions-01.js
@@ -0,0 +1,25 @@
+// Debugger.Object.prototype.getPromiseReactions throws on non-promises, but
+// works on wrappers of promises.
+
+load(libdir + 'asserts.js');
+load(libdir + 'array-compare.js');
+
+var g = newGlobal({ newCompartment: true });
+var dbg = new Debugger;
+var DOg = dbg.addDebuggee(g);
+
+assertThrowsInstanceOf(() => DOg.getPromiseReactions(), TypeError);
+
+// Try retrieving an empty reaction list from an actual promise.
+g.eval(`var p = Promise.resolve();`);
+var DOgp = DOg.makeDebuggeeValue(g.p);
+assertEq(true, arraysEqual(DOgp.getPromiseReactions(), []));
+
+// Try a Debugger.Object of a cross-compartment wrapper of a promise. This
+// should still work: the promise accessors generally do checked unwraps of
+// their arguments.
+var g2 = newGlobal({ newCompartment: true });
+DOg2 = dbg.addDebuggee(g2);
+var DOg2gp = DOg2.makeDebuggeeValue(g.p);
+assertEq(DOgp, DOg2gp.unwrap());
+assertEq(true, arraysEqual(DOg2gp.getPromiseReactions(), []));
diff --git a/js/src/jit-test/tests/debug/Object-getPromiseReactions-02.js b/js/src/jit-test/tests/debug/Object-getPromiseReactions-02.js
new file mode 100644
index 0000000000..9922dd3389
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-getPromiseReactions-02.js
@@ -0,0 +1,49 @@
+// Debugger.Object.protoype.getPromiseReactions reports directly resolved promises.
+
+load(libdir + 'array-compare.js');
+
+var g = newGlobal({ newCompartment: true });
+var dbg = new Debugger;
+var DOg = dbg.addDebuggee(g);
+
+g.eval(`
+ var pResolve, pReject;
+ var p = new Promise((resolve, reject) => { pResolve = resolve; pReject = reject });
+ var p2 = new Promise((resolve, reject) => { resolve(p); });
+ var p3 = new Promise((resolve, reject) => { resolve(p); });
+ var p4 = new Promise((resolve, reject) => { resolve(p2); });
+`);
+
+var [DOp, DOp2, DOp3, DOp4] = [g.p, g.p2, g.p3, g.p4].map(p => DOg.makeDebuggeeValue(p));
+
+// Since the standard resolving functions enqueue a job to do the `then` call,
+// none of the reactions should be visible yet.
+assertEq(true, arraysEqual(DOp.getPromiseReactions(), []));
+assertEq(true, arraysEqual(DOp2.getPromiseReactions(), []));
+assertEq(true, arraysEqual(DOp3.getPromiseReactions(), []));
+assertEq(true, arraysEqual(DOp4.getPromiseReactions(), []));
+
+// This should let them all appear in place.
+drainJobQueue();
+
+assertEq(true, arraysEqual(DOp.getPromiseReactions(), [DOp2, DOp3]));
+assertEq(true, arraysEqual(DOp2.getPromiseReactions(), [DOp4]));
+assertEq(true, arraysEqual(DOp3.getPromiseReactions(), []));
+assertEq(true, arraysEqual(DOp4.getPromiseReactions(), []));
+
+// Resolving the initial promise should kick off its reactions, but propagating
+// that the rest of the way requires microtasks.
+g.pResolve(42);
+
+assertEq(true, arraysEqual(DOp.getPromiseReactions(), []));
+assertEq(true, arraysEqual(DOp2.getPromiseReactions(), [DOp4]));
+assertEq(true, arraysEqual(DOp3.getPromiseReactions(), []));
+assertEq(true, arraysEqual(DOp4.getPromiseReactions(), []));
+
+// Let the propagation complete.
+drainJobQueue();
+
+assertEq(true, arraysEqual(DOp.getPromiseReactions(), []));
+assertEq(true, arraysEqual(DOp2.getPromiseReactions(), []));
+assertEq(true, arraysEqual(DOp3.getPromiseReactions(), []));
+assertEq(true, arraysEqual(DOp4.getPromiseReactions(), []));
diff --git a/js/src/jit-test/tests/debug/Object-getPromiseReactions-03.js b/js/src/jit-test/tests/debug/Object-getPromiseReactions-03.js
new file mode 100644
index 0000000000..81d639efb7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-getPromiseReactions-03.js
@@ -0,0 +1,41 @@
+// Debugger.Object.prototype.getPromiseReactions reports reaction records
+// created with `then` and `catch`.
+
+load(libdir + 'match.js');
+load(libdir + 'match-debugger.js');
+
+const { Pattern } = Match;
+const { OBJECT_WITH_EXACTLY: EXACT } = Pattern;
+function EQ(v) {
+ return new DebuggerIdentical(v);
+}
+
+var g = newGlobal({ newCompartment: true });
+var dbg = new Debugger;
+var DOg = dbg.addDebuggee(g);
+
+g.eval(`
+ function identity(v) { return v; }
+ function thrower(e) { throw e; }
+ function fib(n) { if (n <= 1) return 1; else return fib(n-1) + fib(n-2); }
+ function triangle(n) { return (n+1) * n / 2; }
+
+ var pResolve, pReject;
+ var p = new Promise((resolve, reject) => { pResolve = resolve; pReject = reject });
+ var p2 = p.then(identity, thrower);
+ var p3 = p.then(fib);
+ var p4 = p.catch(triangle);
+`);
+
+var [DOidentity, DOthrower, DOfib, DOtriangle, DOp, DOp2, DOp3, DOp4] =
+ [g.identity, g.thrower, g.fib, g.triangle, g.p, g.p2, g.p3, g.p4].map(p => DOg.makeDebuggeeValue(p));
+
+Match.Pattern([
+ EXACT({ resolve: EQ(DOidentity), reject: EQ(DOthrower), result: EQ(DOp2) }),
+ EXACT({ resolve: EQ(DOfib), result: EQ(DOp3) }),
+ EXACT({ reject: EQ(DOtriangle), result: EQ(DOp4) })
+]).assert(DOp.getPromiseReactions(),
+ "promiseReactions doesn't return expected reaction list");
+
+g.pResolve(3);
+assertEq(DOp.getPromiseReactions().length, 0);
diff --git a/js/src/jit-test/tests/debug/Object-getPromiseReactions-04.js b/js/src/jit-test/tests/debug/Object-getPromiseReactions-04.js
new file mode 100644
index 0000000000..47278c7d6e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-getPromiseReactions-04.js
@@ -0,0 +1,48 @@
+// Debugger.Object.prototype.getPromiseReactions reports reaction records
+// created by `await` expressions in async functions.
+
+const g = newGlobal({ newCompartment: true });
+const dbg = new Debugger;
+const DOg = dbg.addDebuggee(g);
+
+g.eval(`
+ var pResolve, pReject;
+ var p0 = new Promise((resolve, reject) => { pResolve = resolve; pReject = reject });
+
+ // In this case, promiseReactions encounters a Debugger.Frame we had already
+ // associated with the generator, when we hit the debugger statement.
+ async function f1() { debugger; await p0; }
+
+ // In this case, promiseReactions must construct the Debugger.Frame itself,
+ // since it is the first to encounter the generator.
+ async function f2() { await p0; debugger; }
+`);
+
+let DFf1, DFf2;
+dbg.onDebuggerStatement = (frame) => {
+ DFf1 = frame;
+ dbg.onDebuggerStatement = (frame) => {
+ DFf2 = frame;
+ dbg.onDebuggerStatement = () => { throw "Shouldn't fire twice"; };
+ };
+};
+
+g.eval(`var p1 = f1();`);
+assertEq(DFf1.callee.name, "f1");
+
+g.eval(`var p2 = f2();`);
+assertEq(DFf2, undefined);
+
+const [DOp0, DOp1, DOp2] =
+ [g.p0, g.p1, g.p2].map(p => DOg.makeDebuggeeValue(p));
+
+const reactions = DOp0.getPromiseReactions();
+assertEq(reactions.length, 2);
+assertEq(reactions[0], DFf1);
+assertEq(true, reactions[1] instanceof Debugger.Frame);
+
+// Let f2 run until it hits its debugger statement.
+g.pResolve(42);
+drainJobQueue();
+assertEq(DFf2.terminated, true);
+assertEq(reactions[1], DFf2);
diff --git a/js/src/jit-test/tests/debug/Object-getPromiseReactions-05.js b/js/src/jit-test/tests/debug/Object-getPromiseReactions-05.js
new file mode 100644
index 0000000000..89fc1fe70b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-getPromiseReactions-05.js
@@ -0,0 +1,48 @@
+// Debugger.Object.prototype.getPromiseReactions reports reaction records
+// created by `await` expressions in async generators.
+
+const g = newGlobal({ newCompartment: true });
+const dbg = new Debugger;
+const DOg = dbg.addDebuggee(g);
+
+g.eval(`
+ var pResolve, pReject;
+ var p0 = new Promise((resolve, reject) => { pResolve = resolve; pReject = reject });
+
+ // In this case, promiseReactions encounters a Debugger.Frame we had already
+ // associated with the generator, when we hit the debugger statement.
+ async function* f1() { debugger; await p0; }
+
+ // In this case, promiseReactions must construct the Debugger.Frame itself,
+ // since it is the first to encounter the generator.
+ async function* f2() { await p0; debugger; }
+`);
+
+let DFf1, DFf2;
+dbg.onDebuggerStatement = (frame) => {
+ DFf1 = frame;
+ dbg.onDebuggerStatement = (frame) => {
+ DFf2 = frame;
+ dbg.onDebuggerStatement = () => { throw "Shouldn't fire twice"; };
+ };
+};
+
+g.eval(`var p1 = f1().next();`);
+assertEq(DFf1.callee.name, "f1");
+
+g.eval(`var p2 = f2().next();`);
+assertEq(DFf2, undefined);
+
+const [DOp0, DOp1, DOp2] =
+ [g.p0, g.p1, g.p2].map(p => DOg.makeDebuggeeValue(p));
+
+const reactions = DOp0.getPromiseReactions();
+assertEq(reactions.length, 2);
+assertEq(reactions[0], DFf1);
+assertEq(true, reactions[1] instanceof Debugger.Frame);
+
+// Let f2 run until it hits its debugger statement.
+g.pResolve(42);
+drainJobQueue();
+assertEq(DFf2.terminated, true);
+assertEq(reactions[1], DFf2);
diff --git a/js/src/jit-test/tests/debug/Object-getPromiseReactions-06.js b/js/src/jit-test/tests/debug/Object-getPromiseReactions-06.js
new file mode 100644
index 0000000000..99d0bba872
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-getPromiseReactions-06.js
@@ -0,0 +1,43 @@
+// Debugger.Object.prototype.getPromiseReactions should handle reaction records
+// that are cross-compartment with their promises.
+
+var dbg = new Debugger;
+
+var g1 = newGlobal({ newCompartment: true });
+var DOg1 = dbg.addDebuggee(g1);
+
+var g2 = newGlobal({ newCompartment: true });
+var DOg2 = dbg.addDebuggee(g2);
+
+g1.eval(`
+ var pResolve, pReject;
+ var p = new Promise((resolve, reject) => { pResolve = resolve; pReject = reject });
+`);
+
+g2.p = g1.p;
+g2.eval(`
+ var p2 = new Promise((resolve, reject) => { resolve(p); });
+`);
+
+const DOp = DOg1.makeDebuggeeValue(g1.p);
+const DOp2 = DOg2.makeDebuggeeValue(g2.p2);
+
+// Since the standard resolving functions enqueue a job to do the `then` call,
+// we need to drain the queue before p2 will appear on p1's reaction list.
+drainJobQueue();
+
+const reactions = DOp.getPromiseReactions();
+assertEq(true, Array.isArray(reactions));
+assertEq(reactions.length, 1);
+assertEq(typeof reactions[0], "object");
+assertEq(true, reactions[0].resolve instanceof Debugger.Object);
+assertEq(true, reactions[0].resolve.callable);
+assertEq(true, reactions[0].reject instanceof Debugger.Object);
+assertEq(true, reactions[0].reject.callable);
+
+// Unfortunately, this is not p2; it's the promise returned by the internal call
+// to `then` that attached the reaction record to p. See bug 1603575 for ideas
+// about how to actually retrieve p2.
+assertEq(true, reactions[0].result instanceof Debugger.Object);
+assertEq(reactions[0].result.class, "Promise");
+assertEq(true, reactions[0].result !== DOp2);
diff --git a/js/src/jit-test/tests/debug/Object-getProperty-01.js b/js/src/jit-test/tests/debug/Object-getProperty-01.js
new file mode 100644
index 0000000000..378611b7e2
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-getProperty-01.js
@@ -0,0 +1,122 @@
+// tests calling script functions via Debugger.Object.prototype.getProperty
+"use strict";
+
+var global = newGlobal({newCompartment: true});
+var dbg = new Debugger(global);
+dbg.onDebuggerStatement = onDebuggerStatement;
+
+global.eval(`
+const normalObj = { };
+const abruptObj = { };
+const sym = Symbol("a symbol key");
+
+const arr = [1, 2, 3];
+const obj = {
+ get stringNormal(){
+ return "a value";
+ },
+ get stringAbrupt() {
+ throw "a value";
+ },
+ get objectNormal() {
+ return normalObj;
+ },
+ get objectAbrupt() {
+ throw abruptObj;
+ },
+ get context() {
+ return this;
+ },
+
+ 1234: "number key",
+ [sym]: "symbol key",
+ stringProp: "a value",
+ objectProp: {},
+ method() {
+ return "a value";
+ },
+ undefined: "undefined value",
+};
+const propObj = obj.objectProp;
+const methodObj = obj.method;
+
+const objChild = Object.create(obj);
+
+const proxyChild = new Proxy(obj, {});
+
+debugger;
+`);
+
+function onDebuggerStatement(frame) {
+ const { environment } = frame;
+ const arr = environment.getVariable("arr");
+ const obj = environment.getVariable("obj");
+ const objChild = environment.getVariable("objChild");
+ const proxyChild = environment.getVariable("proxyChild");
+
+ const sym = environment.getVariable("sym");
+ const normalObj = environment.getVariable("normalObj");
+ const abruptObj = environment.getVariable("abruptObj");
+ const propObj = environment.getVariable("propObj");
+ const methodObj = environment.getVariable("methodObj");
+
+ assertEq(arr.getProperty(1).return, 2);
+ assertEq(arr.getProperty("1").return, 2);
+
+ assertEq(obj.getProperty().return, "undefined value");
+
+ assertEq(obj.getProperty("missing").return, undefined);
+
+ assertEq(obj.getProperty("stringNormal").return, "a value");
+ assertEq(obj.getProperty("stringAbrupt").throw, "a value");
+
+ assertEq(obj.getProperty("objectNormal").return, normalObj);
+ assertEq(obj.getProperty("objectAbrupt").throw, abruptObj);
+
+ assertEq(obj.getProperty("context").return, obj);
+
+ assertEq(obj.getProperty(1234).return, "number key");
+ assertEq(obj.getProperty(sym).return, "symbol key");
+ assertEq(obj.getProperty("stringProp").return, "a value");
+ assertEq(obj.getProperty("objectProp").return, propObj);
+
+ assertEq(obj.getProperty("method").return, methodObj);
+
+ assertEq(objChild.getProperty().return, "undefined value");
+
+ assertEq(objChild.getProperty("missing").return, undefined);
+
+ assertEq(objChild.getProperty("stringNormal").return, "a value");
+ assertEq(objChild.getProperty("stringAbrupt").throw, "a value");
+
+ assertEq(objChild.getProperty("objectNormal").return, normalObj);
+ assertEq(objChild.getProperty("objectAbrupt").throw, abruptObj);
+
+ assertEq(objChild.getProperty("context").return, objChild);
+
+ assertEq(objChild.getProperty(1234).return, "number key");
+ assertEq(objChild.getProperty(sym).return, "symbol key");
+ assertEq(objChild.getProperty("stringProp").return, "a value");
+ assertEq(objChild.getProperty("objectProp").return, propObj);
+
+ assertEq(objChild.getProperty("method").return, methodObj);
+
+ assertEq(proxyChild.getProperty().return, "undefined value");
+
+ assertEq(proxyChild.getProperty("missing").return, undefined);
+
+ assertEq(proxyChild.getProperty("stringNormal").return, "a value");
+ assertEq(proxyChild.getProperty("stringAbrupt").throw, "a value");
+
+ assertEq(proxyChild.getProperty("objectNormal").return, normalObj);
+ assertEq(proxyChild.getProperty("objectAbrupt").throw, abruptObj);
+
+ assertEq(proxyChild.getProperty("context").return, proxyChild);
+
+ assertEq(proxyChild.getProperty(1234).return, "number key");
+ assertEq(proxyChild.getProperty(sym).return, "symbol key");
+ assertEq(proxyChild.getProperty("stringProp").return, "a value");
+ assertEq(proxyChild.getProperty("objectProp").return, propObj);
+
+ assertEq(proxyChild.getProperty("method").return, methodObj);
+};
diff --git a/js/src/jit-test/tests/debug/Object-getProperty-02.js b/js/src/jit-test/tests/debug/Object-getProperty-02.js
new file mode 100644
index 0000000000..cb38cb5a26
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-getProperty-02.js
@@ -0,0 +1,35 @@
+// tests calling script functions via Debugger.Object.prototype.getProperty
+// to see if they trigger debugger traps
+"use strict";
+
+var global = newGlobal({newCompartment: true});
+var dbg = new Debugger(global);
+dbg.onDebuggerStatement = onDebuggerStatement;
+
+let obj;
+global.eval(`
+const obj = {
+ get prop() {
+ debugger;
+ return 42;
+ }
+};
+
+debugger;
+`);
+
+
+function onDebuggerStatement(frame) {
+ dbg.onDebuggerStatement = onDebuggerStatementGetter;
+
+ obj = frame.environment.getVariable("obj");
+}
+
+let debuggerRan = false;
+
+assertEq(obj.getProperty("prop").return, 42);
+assertEq(debuggerRan, true);
+
+function onDebuggerStatementGetter(frame) {
+ debuggerRan = true;
+}
diff --git a/js/src/jit-test/tests/debug/Object-getProperty-03.js b/js/src/jit-test/tests/debug/Object-getProperty-03.js
new file mode 100644
index 0000000000..a70b7f6e47
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-getProperty-03.js
@@ -0,0 +1,46 @@
+// tests calling script functions via Debugger.Object.prototype.getProperty
+// with different receiver objects.
+"use strict";
+load(libdir + "/asserts.js");
+
+var global = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var globalDO = dbg.addDebuggee(global);
+var windowProxyDO = globalDO.makeDebuggeeValue(global);
+dbg.onDebuggerStatement = onDebuggerStatement;
+
+global.eval(`
+const sloppy = {
+ get getter() { return this; },
+};
+const strict = {
+ get getter() { "use strict"; return this; },
+};
+debugger;
+`);
+
+function onDebuggerStatement(frame) {
+ const { environment } = frame;
+ const sloppy = environment.getVariable("sloppy");
+ const strict = environment.getVariable("strict");
+
+ assertEq(sloppy.getProperty("getter").return, sloppy);
+ assertEq(sloppy.getProperty("getter", sloppy).return, sloppy);
+ assertEq(sloppy.getProperty("getter", strict).return, strict);
+ assertEq(sloppy.getProperty("getter", 1).return.class, "Number");
+ assertEq(sloppy.getProperty("getter", true).return.class, "Boolean");
+ assertEq(sloppy.getProperty("getter", null).return, windowProxyDO);
+ assertEq(sloppy.getProperty("getter", undefined).return, windowProxyDO);
+ assertErrorMessage(() => sloppy.getProperty("getter", {}), TypeError,
+ "Debugger: expected Debugger.Object, got Object");
+
+ assertEq(strict.getProperty("getter").return, strict);
+ assertEq(strict.getProperty("getter", sloppy).return, sloppy);
+ assertEq(strict.getProperty("getter", strict).return, strict);
+ assertEq(strict.getProperty("getter", 1).return, 1);
+ assertEq(strict.getProperty("getter", true).return, true);
+ assertEq(strict.getProperty("getter", null).return, null);
+ assertEq(strict.getProperty("getter", undefined).return, undefined);
+ assertErrorMessage(() => strict.getProperty("getter", {}), TypeError,
+ "Debugger: expected Debugger.Object, got Object");
+};
diff --git a/js/src/jit-test/tests/debug/Object-getProperty-non-primitive-key.js b/js/src/jit-test/tests/debug/Object-getProperty-non-primitive-key.js
new file mode 100644
index 0000000000..649764322e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-getProperty-non-primitive-key.js
@@ -0,0 +1,37 @@
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var gw = dbg.addDebuggee(g);
+
+g.eval(`
+ var obj = {
+ p: 1,
+ [Symbol.iterator]: 2,
+ };
+`);
+
+// Return |key| as an object.
+function toObject(key) {
+ return {
+ [Symbol.toPrimitive]() {
+ return key;
+ }
+ };
+}
+
+let obj = gw.getProperty("obj").return;
+
+for (let key of obj.getOwnPropertyNames()) {
+ let keyObject = toObject(key);
+ let value = g.obj[key];
+
+ assertEq(obj.getProperty(key).return, value);
+ assertEq(obj.getProperty(keyObject).return, value);
+}
+
+for (let key of obj.getOwnPropertySymbols()) {
+ let keyObject = toObject(key);
+ let value = g.obj[key];
+
+ assertEq(obj.getProperty(key).return, value);
+ assertEq(obj.getProperty(keyObject).return, value);
+}
diff --git a/js/src/jit-test/tests/debug/Object-identity-01.js b/js/src/jit-test/tests/debug/Object-identity-01.js
new file mode 100644
index 0000000000..2eaaa8ab12
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-identity-01.js
@@ -0,0 +1,10 @@
+// Two references to the same object get the same Debugger.Object wrapper.
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ assertEq(frame.arguments[0], frame.arguments[1]);
+ hits++;
+};
+g.eval("var obj = {}; function f(a, b) { debugger; } f(obj, obj);");
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Object-identity-02.js b/js/src/jit-test/tests/debug/Object-identity-02.js
new file mode 100644
index 0000000000..f488c25b3e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-identity-02.js
@@ -0,0 +1,10 @@
+// Different objects get different Debugger.Object wrappers.
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ assertEq(frame.arguments[0] === frame.arguments[1], false);
+ hits++;
+};
+g.eval("function f(a, b) { debugger; } f({}, {});");
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Object-identity-03.js b/js/src/jit-test/tests/debug/Object-identity-03.js
new file mode 100644
index 0000000000..0e2af5220b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-identity-03.js
@@ -0,0 +1,25 @@
+// The same object gets the same Debugger.Object wrapper at different times, if the difference would be observable.
+
+var N = 12;
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var wrappers = [];
+
+dbg.onDebuggerStatement = function (frame) { wrappers.push(frame.arguments[0]); };
+g.eval("var originals = []; function f(x) { originals.push(x); debugger; }");
+for (var i = 0; i < N; i++)
+ g.eval("f({});");
+assertEq(wrappers.length, N);
+
+for (var i = 0; i < N; i++)
+ for (var j = i + 1; j < N; j++)
+ assertEq(wrappers[i] === wrappers[j], false);
+
+gc();
+
+dbg.onDebuggerStatement = function (frame) { assertEq(frame.arguments[0], wrappers.pop()); };
+g.eval("function h(x) { debugger; }");
+for (var i = 0; i < N; i++)
+ g.eval("h(originals.pop());");
+assertEq(wrappers.length, 0);
diff --git a/js/src/jit-test/tests/debug/Object-isArrowFunction.js b/js/src/jit-test/tests/debug/Object-isArrowFunction.js
new file mode 100644
index 0000000000..6d7bd531b6
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-isArrowFunction.js
@@ -0,0 +1,24 @@
+// Debugger.Object.prototype.isArrowFunction recognizes arrow functions.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gDO = dbg.addDebuggee(g);
+var hits = 0;
+
+function checkIsArrow(shouldBe, expr) {
+ print(expr);
+ assertEq(gDO.executeInGlobal(expr).return.isArrowFunction, shouldBe);
+}
+
+checkIsArrow(true, '() => { }');
+checkIsArrow(true, '(a) => { bleh; }');
+checkIsArrow(false, 'Object.getPrototypeOf(() => { })');
+checkIsArrow(false, '(function () { })');
+checkIsArrow(false, 'function f() { } f');
+checkIsArrow((void 0), '({})');
+checkIsArrow(false, 'Math.atan2');
+checkIsArrow(false, 'Function.prototype');
+checkIsArrow(false, 'Function("")');
+checkIsArrow(false, 'new Function("")');
+checkIsArrow(false, '(async function f () {})');
+checkIsArrow(true, '(async () => { })');
diff --git a/js/src/jit-test/tests/debug/Object-isClassConstructor.js b/js/src/jit-test/tests/debug/Object-isClassConstructor.js
new file mode 100644
index 0000000000..342e9d1a52
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-isClassConstructor.js
@@ -0,0 +1,21 @@
+// Debugger.Object.prototype.isClassConstructor recognizes ES6 classes.
+
+var g = newGlobal({ newCompartment: true });
+var dbg = new Debugger();
+var gDO = dbg.addDebuggee(g);
+var hits = 0;
+
+function checkIsClassConstructor(shouldBe, expr) {
+ print(expr);
+ assertEq(gDO.executeInGlobal(expr).return.isClassConstructor, shouldBe);
+}
+
+checkIsClassConstructor(true, "class MyClass{}; MyClass;");
+checkIsClassConstructor(false, "class MyClass2{}; MyClass2.constructor;");
+checkIsClassConstructor(
+ false,
+ "class MyClass3{}; Object.getPrototypeOf(MyClass3)"
+);
+checkIsClassConstructor(false, "(a) => { bleh; }");
+checkIsClassConstructor(false, "(async function f () {})");
+checkIsClassConstructor(void 0, "({})");
diff --git a/js/src/jit-test/tests/debug/Object-isSameNative-01.js b/js/src/jit-test/tests/debug/Object-isSameNative-01.js
new file mode 100644
index 0000000000..9359062cd6
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-isSameNative-01.js
@@ -0,0 +1,9 @@
+// Test that isSameNative will accept a CCW as an argument.
+
+var g = newGlobal({ newCompartment: true });
+var dbg = Debugger();
+var gdbg = dbg.addDebuggee(g);
+
+var g2 = newGlobal({ newCompartment: true });
+
+assertEq(gdbg.getProperty("print").return.isSameNative(g2.print), true);
diff --git a/js/src/jit-test/tests/debug/Object-isSameNative.js b/js/src/jit-test/tests/debug/Object-isSameNative.js
new file mode 100644
index 0000000000..b01b6b4611
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-isSameNative.js
@@ -0,0 +1,46 @@
+// Test that the onNativeCall hook is called when expected.
+
+load(libdir + 'eqArrayHelper.js');
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var gdbg = dbg.addDebuggee(g);
+
+assertEq(gdbg.getProperty("print").return.isSameNative(print), true);
+assertEq(gdbg.getProperty("print").return.isSameNative(newGlobal), false);
+
+g.eval(`
+const x = [];
+Object.defineProperty(x, "a", {
+ get: print,
+ set: print,
+});
+function f() {
+ x.a++;
+ x.length = 0;
+ x.push(4, 5, 6);
+ x.sort(print);
+}
+`);
+
+const comparisons = [
+ print,
+ Array.prototype.push,
+ Array.prototype.sort, // Note: self-hosted
+ newGlobal
+];
+
+const rv = [];
+dbg.onNativeCall = (callee, reason) => {
+ for (const fn of comparisons) {
+ if (callee.isSameNative(fn)) {
+ rv.push(fn.name);
+ }
+ }
+}
+
+for (let i = 0; i < 5; i++) {
+ rv.length = 0;
+ gdbg.executeInGlobal(`f()`);
+ assertEqArray(rv, ["print", "print", "push", "sort"]);
+}
diff --git a/js/src/jit-test/tests/debug/Object-makeDebuggeeNativeFunction-01.js b/js/src/jit-test/tests/debug/Object-makeDebuggeeNativeFunction-01.js
new file mode 100644
index 0000000000..996ad7cffc
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-makeDebuggeeNativeFunction-01.js
@@ -0,0 +1,28 @@
+// Debugger.Object.prototype.makeDebuggeeNativeFunction does what it is
+// supposed to do.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+
+// It would be nice if we could test that this call doesn't produce a CCW,
+// and that calling makeDebuggeeValue instead does, but
+// Debugger.Object.isProxy only returns true for scripted proxies.
+const push = gw.makeDebuggeeNativeFunction(Array.prototype.push);
+
+gw.setProperty("push", push);
+g.eval("var x = []; push.call(x, 2); x.push(3)");
+assertEq(g.x[0], 2);
+assertEq(g.x[1], 3);
+
+// Interpreted functions should throw.
+assertThrowsInstanceOf(() => {
+ gw.makeDebuggeeNativeFunction(() => {});
+}, Error);
+
+// Native functions which have extended slots should throw.
+let f;
+new Promise(resolve => { f = resolve; })
+assertThrowsInstanceOf(() => gw.makeDebuggeeNativeFunction(f), Error);
diff --git a/js/src/jit-test/tests/debug/Object-makeDebuggeeValue-01.js b/js/src/jit-test/tests/debug/Object-makeDebuggeeValue-01.js
new file mode 100644
index 0000000000..4b281421d4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-makeDebuggeeValue-01.js
@@ -0,0 +1,42 @@
+// Debugger.Object.prototype.makeDebuggeeValue creates only one
+// Debugger.Object instance for each debuggee object.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+
+g.eval("var x = { 'now playing': 'Joy Division' };");
+g.eval("var y = { 'mood': 'bleak' };");
+
+wx = gw.makeDebuggeeValue(g.x);
+assertEq(wx, gw.makeDebuggeeValue(g.x));
+assertEq(wx === g.x, false);
+assertEq("now playing" in wx, false);
+assertEq(wx.getOwnPropertyNames().indexOf("now playing"), 0);
+wx.commentary = "deconstruction";
+assertEq("deconstruction" in g.x, false);
+
+wy = gw.makeDebuggeeValue(g.y);
+assertEq(wy === wx, false);
+wy.commentary = "reconstruction";
+assertEq(wx.commentary, "deconstruction");
+
+// Separate debuggers get separate Debugger.Object instances, but both
+// instances' referents are the same underlying object.
+var dbg2 = new Debugger();
+var gw2 = dbg2.addDebuggee(g);
+w2x = gw2.makeDebuggeeValue(g.x);
+assertEq(wx === w2x, false);
+w2x.defineProperty("breadcrumb", { value: "pumpernickel" });
+assertEq(wx.getOwnPropertyDescriptor("breadcrumb").value, "pumpernickel");
+
+// Non-objects are already debuggee values.
+assertEq(gw.makeDebuggeeValue("foonting turlingdromes"), "foonting turlingdromes");
+assertEq(gw.makeDebuggeeValue(true), true);
+assertEq(gw.makeDebuggeeValue(false), false);
+assertEq(gw.makeDebuggeeValue(null), null);
+assertEq(gw.makeDebuggeeValue(1729), 1729);
+assertEq(gw.makeDebuggeeValue(Math.PI), Math.PI);
+assertEq(gw.makeDebuggeeValue(undefined), undefined);
+var s = g.eval("Symbol('Stavromula Beta')");
+assertEq(gw.makeDebuggeeValue(s), s);
diff --git a/js/src/jit-test/tests/debug/Object-makeDebuggeeValue-02.js b/js/src/jit-test/tests/debug/Object-makeDebuggeeValue-02.js
new file mode 100644
index 0000000000..23c493ea43
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-makeDebuggeeValue-02.js
@@ -0,0 +1,12 @@
+// Debugger.Object.prototype.makeDebuggeeValue returns the object wrapped
+// the same way as Debugger.Frame.prototype.eval, etc.
+var g = newGlobal({newCompartment: true});
+g.eval("function f() { debugger; }");
+var dbg = Debugger();
+var gw = dbg.addDebuggee(g);
+var jsonw;
+dbg.onDebuggerStatement = function (frame) {
+ jsonw = frame.eval("JSON").return;
+};
+g.eval("debugger;");
+assertEq(gw.makeDebuggeeValue(g.JSON), jsonw);
diff --git a/js/src/jit-test/tests/debug/Object-name-01.js b/js/src/jit-test/tests/debug/Object-name-01.js
new file mode 100644
index 0000000000..5159720de9
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-name-01.js
@@ -0,0 +1,17 @@
+// Debugger.Object.prototype.name
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var name;
+dbg.onDebuggerStatement = function (frame) { name = frame.callee.name; };
+
+g.eval("(function f() { debugger; })();");
+assertEq(name, "f");
+g.eval("(function () { debugger; })();");
+assertEq(name, undefined);
+g.eval("Function('debugger;')();");
+assertEq(name, "anonymous");
+g.eval("(async function grondo() { debugger; })();");
+assertEq(name, "grondo");
+g.eval("(async function* estux() { debugger; })().next();");
+assertEq(name, "estux");
diff --git a/js/src/jit-test/tests/debug/Object-name-02.js b/js/src/jit-test/tests/debug/Object-name-02.js
new file mode 100644
index 0000000000..b9ae25ca88
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-name-02.js
@@ -0,0 +1,16 @@
+// The .name of a non-function object is undefined.
+
+var g = newGlobal({newCompartment: true});
+var hits = 0;
+var dbg = new Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+ assertEq(frame.arguments[0].name, undefined);
+ hits++;
+};
+g.eval("function f(nonfunction) { debugger; }");
+
+g.eval("f({});");
+g.eval("f(/a*/);");
+g.eval("f({name: 'bad'});");
+g.eval("f(new Proxy({}, {}));");
+assertEq(hits, 4);
diff --git a/js/src/jit-test/tests/debug/Object-parameterNames.js b/js/src/jit-test/tests/debug/Object-parameterNames.js
new file mode 100644
index 0000000000..3e3c504714
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-parameterNames.js
@@ -0,0 +1,32 @@
+load(libdir + 'array-compare.js');
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gDO = dbg.addDebuggee(g);
+var hits = 0;
+
+function check(expr, expected) {
+ print("checking " + JSON.stringify(expr));
+
+ let completion = gDO.executeInGlobal(expr);
+ if (completion.throw)
+ throw completion.throw.unsafeDereference();
+
+ let fn = completion.return;
+ if (expected === undefined)
+ assertEq(fn.parameterNames, undefined);
+ else
+ assertEq(arraysEqual(fn.parameterNames, expected), true);
+}
+
+check('(function () {})', []);
+check('(function (x) {})', ["x"]);
+check('(function (a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z) {})',
+ ["a","b","c","d","e","f","g","h","i","j","k","l","m",
+ "n","o","p","q","r","s","t","u","v","w","x","y","z"]);
+check('(function (a, [b, c], {d, e:f}) { })',
+ ["a", undefined, undefined]);
+check('({a:1})', undefined);
+check('Math.atan2', [undefined, undefined]);
+check('(async function (a, b, c) {})', ["a", "b", "c"]);
+check('(async function* (d, e, f) {})', ["d", "e", "f"]);
diff --git a/js/src/jit-test/tests/debug/Object-preventExtensions-01.js b/js/src/jit-test/tests/debug/Object-preventExtensions-01.js
new file mode 100644
index 0000000000..d5339e2d3d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-preventExtensions-01.js
@@ -0,0 +1,17 @@
+// Basic preventExtensions test.
+
+var g = newGlobal({newCompartment: true});
+var obj = g.eval("({x: 1})");
+assertEq(g.Object.isExtensible(obj), true);
+
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+var objw = gw.makeDebuggeeValue(obj);
+assertEq(objw.isExtensible(), true);
+
+assertEq(objw.preventExtensions(), undefined);
+assertEq(g.Object.isExtensible(obj), false);
+assertEq(objw.isExtensible(), false);
+
+// Calling preventExtensions again has no effect.
+assertEq(objw.preventExtensions(), undefined);
diff --git a/js/src/jit-test/tests/debug/Object-promiseDependentPromises-realms.js b/js/src/jit-test/tests/debug/Object-promiseDependentPromises-realms.js
new file mode 100644
index 0000000000..537421f6a0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-promiseDependentPromises-realms.js
@@ -0,0 +1,17 @@
+// Bug 1475669 - Cross-compartment dependent promises.
+
+// Create a promise in realm 1.
+let g1 = newGlobal({newCompartment: true});
+let p1 = new g1.Promise((_resolve, _reject) => {});
+
+// Add a dependent promise in realm 2.
+let g2 = newGlobal({newCompartment: true});
+let p2 = g2.Promise.prototype.then.call(p1, g2.eval(`value => {}`));
+
+// Use debugger to find p2 from p1.
+let dbg = new Debugger;
+let g1w = dbg.addDebuggee(g1);
+let g2w = dbg.addDebuggee(g2);
+let dependents = g1w.makeDebuggeeValue(p1).promiseDependentPromises;
+assertEq(dependents.length, 1);
+assertEq(dependents[0], g2w.makeDebuggeeValue(p2));
diff --git a/js/src/jit-test/tests/debug/Object-proto.js b/js/src/jit-test/tests/debug/Object-proto.js
new file mode 100644
index 0000000000..065810a0b4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-proto.js
@@ -0,0 +1,23 @@
+// Debugger.Object.prototype.proto
+var g = newGlobal({newCompartment: true});
+var dbgeval = function () {
+ var dbg = new Debugger(g);
+ var hits = 0;
+ g.eval("function f() { debugger; }");
+ var lastval;
+ dbg.onDebuggerStatement = function (frame) { lastval = frame.arguments[0]; };
+ return function dbgeval(s) {
+ g.eval("f(" + s + ");");
+ return lastval;
+ };
+ }();
+
+var Op = dbgeval("Object.prototype");
+assertEq(Op.proto, null);
+assertEq(dbgeval("({})").proto, Op);
+
+var Ap = dbgeval("[]").proto;
+assertEq(Ap, dbgeval("Array.prototype"));
+assertEq(Ap.proto, Op);
+
+assertEq(dbgeval("Object").proto, dbgeval("Function.prototype"));
diff --git a/js/src/jit-test/tests/debug/Object-proxy.js b/js/src/jit-test/tests/debug/Object-proxy.js
new file mode 100644
index 0000000000..c32233c9c7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-proxy.js
@@ -0,0 +1,44 @@
+// Debugger.Object.prototype.isProxy recognizes (scripted) proxies.
+// Debugger.Object.prototype.proxyTarget exposes the [[Proxytarget]] of a proxy.
+// Debugger.Object.prototype.proxyHandler exposes the [[ProxyHandler]] of a proxy.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gDO = dbg.addDebuggee(g);
+
+g.eval('var target = [1,2,3];');
+g.eval('var handler = {has: ()=>false};');
+g.eval('var proxy = new Proxy(target, handler);');
+g.eval('var proxyProxy = new Proxy(proxy, proxy);');
+g.eval('var revoker = Proxy.revocable(target, handler);');
+g.eval('var revocable = revoker.proxy;');
+
+var target = gDO.getOwnPropertyDescriptor('target').value;
+var handler = gDO.getOwnPropertyDescriptor('handler').value;
+var proxy = gDO.getOwnPropertyDescriptor('proxy').value;
+var proxyProxy = gDO.getOwnPropertyDescriptor('proxyProxy').value;
+var revocable = gDO.getOwnPropertyDescriptor('revocable').value;
+
+assertEq(target.isProxy, false);
+assertEq(target.proxyTarget, undefined);
+assertEq(target.proxyHandler, undefined);
+
+assertEq(handler.isProxy, false);
+assertEq(handler.proxyTarget, undefined);
+assertEq(handler.proxyHandler, undefined);
+
+assertEq(proxy.isProxy, true);
+assertEq(proxy.proxyTarget, target);
+assertEq(proxy.proxyHandler, handler);
+
+assertEq(proxyProxy.isProxy, true);
+assertEq(proxyProxy.proxyTarget, proxy);
+assertEq(proxyProxy.proxyHandler, proxy);
+
+assertEq(revocable.isProxy, true);
+assertEq(revocable.proxyTarget, target);
+assertEq(revocable.proxyHandler, handler);
+g.eval('revoker.revoke();');
+assertEq(revocable.isProxy, true);
+assertEq(revocable.proxyTarget, null);
+assertEq(revocable.proxyHandler, null);
diff --git a/js/src/jit-test/tests/debug/Object-script-AsmJSNative.js b/js/src/jit-test/tests/debug/Object-script-AsmJSNative.js
new file mode 100644
index 0000000000..3854f7f03d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-script-AsmJSNative.js
@@ -0,0 +1,15 @@
+function test(stdlib, foreign) {
+ "use asm"
+ function f(y) {
+ y = +y;
+ }
+ return f;
+};
+var g = newGlobal({newCompartment: true});
+g.parent = this;
+g.eval(`
+ var dbg = new Debugger();
+ var parentw = dbg.addDebuggee(parent);
+ var testw = parentw.makeDebuggeeValue(parent.test);
+ var scriptw = testw.script;
+`);
diff --git a/js/src/jit-test/tests/debug/Object-script-environment-nondebuggee.js b/js/src/jit-test/tests/debug/Object-script-environment-nondebuggee.js
new file mode 100644
index 0000000000..3e77bc89d5
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-script-environment-nondebuggee.js
@@ -0,0 +1,24 @@
+// The script and environment of a non-debuggee function are null.
+
+var g = newGlobal({newCompartment: true});
+g.eval('function f() { return "from f"; }');
+
+var dbg = new Debugger;
+var gw = dbg.makeGlobalObjectReference(g);
+var fw = gw.getOwnPropertyDescriptor('f').value;
+
+// g is not a debuggee, so we can't fetch f's script or environment.
+assertEq(fw.script, null);
+assertEq(fw.environment, null);
+
+// If we add g as a debuggee, we can fetch f's script and environment.
+dbg.addDebuggee(g);
+var fscript = fw.script;
+var fenv = fw.environment;
+assertEq(fscript instanceof Debugger.Script, true);
+assertEq(fenv instanceof Debugger.Environment, true);
+
+// Removing g as a debuggee makes the script and environment inaccessible again.
+dbg.removeDebuggee(g);
+assertEq(fw.script, null);
+assertEq(fw.environment, null);
diff --git a/js/src/jit-test/tests/debug/Object-script-lazy.js b/js/src/jit-test/tests/debug/Object-script-lazy.js
new file mode 100644
index 0000000000..a0fd9ef7ff
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-script-lazy.js
@@ -0,0 +1,57 @@
+// Asking for the script of a Debugger.Object should work, even when lazy
+// functions are involved.
+
+// Note that we never hand out the scripts of non-debuggee functions, and
+// putting a compartment in debug mode automatically de-lazifies all its
+// functions. (This ensures that findScripts works, too.)
+//
+// So here we create a reference to a non-debuggee function, verify that we
+// can't access its interesting properties, make it a debuggee, and verify
+// that everything becomes available.
+
+// Create functions f, g in a non-debuggee compartment.
+var g1 = newGlobal({newCompartment: true});
+g1.eval('function f() { return "from f"; }');
+g1.eval('function g() { return "from g"; }');
+g1.eval('this.h = f.bind(g, 42, "foo");');
+
+// Create a debuggee compartment with CCWs referring to f and g.
+var g2 = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var g2w = dbg.addDebuggee(g2);
+g2.f = g1.f;
+g2.g = g1.g;
+g2.h = g1.h;
+
+// At this point, g1.f should still be a lazy function. Unwrapping a D.O
+// referring to g2's CCW of f should yield a D.O referring to f directly.
+// Asking for that second D.O's script should yield null, because it's not
+// a debuggee.
+var fDO = g2w.getOwnPropertyDescriptor('f').value;
+assertEq(fDO.unwrap().class, "Function");
+assertEq(fDO.unwrap().script, null);
+
+// Similarly for g1.g, and asking for its parameter names.
+var gDO = g2w.getOwnPropertyDescriptor('g').value;
+assertEq(gDO.unwrap().parameterNames, undefined);
+
+// Similarly for g1.h, and asking for its bound function properties.
+var hDO = g2w.getOwnPropertyDescriptor('h').value;
+assertEq(hDO.unwrap().class, "Function");
+assertEq(hDO.unwrap().isBoundFunction, undefined);
+assertEq(hDO.unwrap().isArrowFunction, undefined);
+assertEq(hDO.unwrap().boundTargetFunction, undefined);
+assertEq(hDO.unwrap().boundThis, undefined);
+assertEq(hDO.unwrap().boundArguments, undefined);
+
+// Add g1 as a debuggee, and verify that we can get everything.
+dbg.addDebuggee(g1);
+assertEq(fDO.unwrap().script instanceof Debugger.Script, true);
+assertEq(gDO.unwrap().parameterNames instanceof Array, true);
+assertEq(hDO.unwrap().isBoundFunction, true);
+assertEq(hDO.unwrap().isArrowFunction, false);
+assertEq(hDO.unwrap().boundTargetFunction, fDO.unwrap());
+assertEq(hDO.unwrap().boundThis, gDO.unwrap());
+assertEq(hDO.unwrap().boundArguments.length, 2);
+assertEq(hDO.unwrap().boundArguments[0], 42);
+assertEq(hDO.unwrap().boundArguments[1], "foo");
diff --git a/js/src/jit-test/tests/debug/Object-script.js b/js/src/jit-test/tests/debug/Object-script.js
new file mode 100644
index 0000000000..fbaa0232fa
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-script.js
@@ -0,0 +1,25 @@
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gDO = dbg.addDebuggee(g);
+
+function check(expr, expected) {
+ print("checking " + JSON.stringify(expr) + ", expecting " +
+ (expected ? "script" : "no script"));
+
+ let completion = gDO.executeInGlobal(expr);
+ if (completion.throw)
+ throw completion.throw.unsafeDereference();
+
+ let val = completion.return;
+ if (expected)
+ assertEq(val.script instanceof Debugger.Script, true);
+ else
+ assertEq(val.script, undefined);
+}
+
+check('(function g(){})', true);
+check('(function* h() {})', true);
+check('(async function j() {})', true);
+check('(async function* k() {})', true);
+check('({})', false);
+check('Math.atan2', false);
diff --git a/js/src/jit-test/tests/debug/Object-seal-01.js b/js/src/jit-test/tests/debug/Object-seal-01.js
new file mode 100644
index 0000000000..f8190f4ecc
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-seal-01.js
@@ -0,0 +1,63 @@
+// Basic tests for obj.{seal,freeze,preventExtensions,isSealed,isFrozen,isExtensible}.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+
+g.eval("function compareObjects() {\n" +
+ " assertEq(Object.isExtensible(x), Object.isExtensible(y));\n" +
+ " var xnames = Object.getOwnPropertyNames(x).sort();\n" +
+ " var ynames = Object.getOwnPropertyNames(y).sort();\n" +
+ " assertEq(xnames.length, ynames.length);\n" +
+ " for (var i = 0; i < xnames.length; i++) {\n" +
+ " assertEq(xnames[i], ynames[i]);\n" +
+ " var name = xnames[i];\n" +
+ " var xd = Object.getOwnPropertyDescriptor(x, name);\n" +
+ " var yd = Object.getOwnPropertyDescriptor(y, name);\n" +
+ " assertEq(xd.configurable, yd.configurable, code + '.' + name + ' .configurable');\n" +
+ " assertEq(xd.enumerable, yd.enumerable, code + '.' + name + ' .enumerable');\n" +
+ " assertEq(xd.writable, yd.writable, code + '.' + name + ' .writable');\n" +
+ " }\n" +
+ "}\n");
+
+function test(code) {
+ g.code = code;
+ g.eval("x = (" + code + ");");
+ g.eval("y = (" + code + ");");
+ var xw = gw.getOwnPropertyDescriptor("x").value;
+
+ function check() {
+ // The Debugger.Object seal/freeze/preventExtensions methods
+ // had the same effect as the corresponding ES5 Object methods.
+ g.compareObjects();
+
+ // The Debugger.Object introspection methods agree with the ES5 Object methods.
+ assertEq(xw.isExtensible(), g.Object.isExtensible(g.x), code + ' isExtensible');
+ assertEq(xw.isSealed(), g.Object.isSealed(g.x), code + ' isSealed');
+ assertEq(xw.isFrozen(), g.Object.isFrozen(g.x), code + ' isFrozen');
+ }
+
+ check();
+
+ xw.preventExtensions();
+ assertEq(g.Object.isExtensible(g.x), false, code + ' preventExtensions');
+ g.Object.preventExtensions(g.y);
+ check();
+
+ xw.seal();
+ assertEq(g.Object.isSealed(g.x), true, code + ' seal');
+ g.Object.seal(g.y);
+ check();
+
+ xw.freeze();
+ assertEq(g.Object.isFrozen(g.x), true, code + ' freeze');
+ g.Object.freeze(g.y);
+ check();
+}
+
+test("{}");
+test("{a: [1], get b() { return -1; }}");
+test("Object.create(null, {x: {value: 3}, y: {get: Math.min}})");
+test("[]");
+test("[,,,,,]");
+test("[0, 1, 2]");
diff --git a/js/src/jit-test/tests/debug/Object-setProperty-01.js b/js/src/jit-test/tests/debug/Object-setProperty-01.js
new file mode 100644
index 0000000000..cde5f68e54
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-setProperty-01.js
@@ -0,0 +1,185 @@
+// tests calling script functions via Debugger.Object.prototype.setProperty
+"use strict";
+
+var global = newGlobal({newCompartment: true});
+var dbg = new Debugger(global);
+dbg.onDebuggerStatement = onDebuggerStatement;
+
+global.eval(`
+const sym = Symbol("a symbol key");
+
+const arr = [];
+const obj = {
+ set stringNormal(value) {
+ this._stringNormal = value;
+ },
+ set stringAbrupt(value) {
+ throw value;
+ },
+
+ set objectNormal(value) {
+ this._objectNormal = value;
+ },
+ set objectAbrupt(value) {
+ throw value;
+ },
+
+ set context(value) {
+ this._context = this;
+ },
+
+ set 1234(value) {
+ this._1234 = value;
+ },
+
+ set [sym](value) {
+ this._sym = value;
+ },
+
+ get getterOnly() {},
+ readOnly: "",
+
+ stringProp: "",
+ objProp: {},
+};
+Object.defineProperty(obj, "readOnly", { writable: false });
+
+const objChild = Object.create(obj);
+const proxyChild = new Proxy(obj, {});
+
+const testObj = { };
+const testChildObj = { };
+const testProxyObj = { };
+
+debugger;
+`);
+
+function onDebuggerStatement(frame) {
+ const { environment } = frame;
+ const sym = environment.getVariable("sym");
+ const arr = environment.getVariable("arr");
+ const obj = environment.getVariable("obj");
+ const objChild = environment.getVariable("objChild");
+ const proxyChild = environment.getVariable("proxyChild");
+
+ assertEq(arr.setProperty(1, "index 1").return, true);
+ assertEq(arr.getProperty(1).return, "index 1");
+ assertEq(arr.getProperty("1").return, "index 1");
+ assertEq(arr.getProperty(0).return, undefined);
+ assertEq(arr.getProperty("length").return, 2);
+
+ assertEq(arr.setProperty("2", "index 2").return, true);
+ assertEq(arr.getProperty(2).return, "index 2");
+ assertEq(arr.getProperty("2").return, "index 2");
+ assertEq(arr.getProperty(0).return, undefined);
+ assertEq(arr.getProperty("length").return, 3);
+
+ const testObj = environment.getVariable("testObj");
+ assertEq(obj.setProperty("undefined", 123).return, true);
+ assertEq(obj.getProperty("undefined").return, 123);
+ assertEq(obj.setProperty().return, true);
+ assertEq(obj.getProperty("undefined").return, undefined);
+
+ assertEq(obj.setProperty("missing", 42).return, true);
+ assertEq(obj.getProperty("missing").return, 42);
+
+ assertEq(obj.setProperty("stringNormal", "a normal value").return, true);
+ assertEq(obj.getProperty("_stringNormal").return, "a normal value");
+ assertEq(obj.setProperty("stringAbrupt", "an abrupt value").throw, "an abrupt value");
+
+ assertEq(obj.setProperty("objectNormal", testObj).return, true);
+ assertEq(obj.getProperty("_objectNormal").return, testObj);
+ assertEq(obj.setProperty("objectAbrupt", testObj).throw, testObj);
+
+ assertEq(obj.setProperty("context", "a value").return, true);
+ assertEq(obj.getProperty("_context").return, obj);
+
+ assertEq(obj.setProperty(1234, "number value").return, true);
+ assertEq(obj.getProperty("_1234").return, "number value");
+
+ assertEq(obj.setProperty(sym, "symbol value").return, true);
+ assertEq(obj.getProperty("_sym").return, "symbol value");
+
+ assertEq(obj.setProperty("getterOnly", "a value").return, false);
+ assertEq(obj.setProperty("readOnly", "a value").return, false);
+
+ assertEq(obj.setProperty("stringProp", "a normal value").return, true);
+ assertEq(obj.getProperty("stringProp").return, "a normal value");
+
+ assertEq(obj.setProperty("objectProp", testObj).return, true);
+ assertEq(obj.getProperty("objectProp").return, testObj);
+
+ const testChildObj = environment.getVariable("testChildObj");
+ assertEq(objChild.setProperty("undefined", 123).return, true);
+ assertEq(objChild.getProperty("undefined").return, 123);
+ assertEq(objChild.setProperty().return, true);
+ assertEq(objChild.getProperty("undefined").return, undefined);
+
+ assertEq(objChild.setProperty("missing", 42).return, true);
+ assertEq(objChild.getProperty("missing").return, 42);
+
+ assertEq(objChild.setProperty("stringNormal", "a normal child value").return, true);
+ assertEq(objChild.getProperty("_stringNormal").return, "a normal child value");
+
+ assertEq(objChild.setProperty("stringAbrupt", "an abrupt child value").throw, "an abrupt child value");
+
+ assertEq(objChild.setProperty("objectNormal", testChildObj).return, true);
+ assertEq(objChild.getProperty("_objectNormal").return, testChildObj);
+
+ assertEq(objChild.setProperty("objectAbrupt", testChildObj).throw, testChildObj);
+
+ assertEq(objChild.setProperty("context", "a value").return, true);
+ assertEq(objChild.getProperty("_context").return, objChild);
+
+ assertEq(objChild.setProperty(1234, "number value").return, true);
+ assertEq(objChild.getProperty("_1234").return, "number value");
+
+ assertEq(objChild.setProperty(sym, "symbol value").return, true);
+ assertEq(objChild.getProperty("_sym").return, "symbol value");
+
+ assertEq(objChild.setProperty("getterOnly", "a value").return, false);
+ assertEq(objChild.setProperty("readOnly", "a value").return, false);
+
+ assertEq(objChild.setProperty("stringProp", "a normal child value").return, true);
+ assertEq(objChild.getProperty("stringProp").return, "a normal child value");
+
+ assertEq(objChild.setProperty("objectProp", testChildObj).return, true);
+ assertEq(objChild.getProperty("objectProp").return, testChildObj);
+
+ const testProxyObj = environment.getVariable("testProxyObj");
+ assertEq(proxyChild.setProperty("undefined", 123).return, true);
+ assertEq(proxyChild.getProperty("undefined").return, 123);
+ assertEq(proxyChild.setProperty().return, true);
+ assertEq(proxyChild.getProperty("undefined").return, undefined);
+
+ assertEq(proxyChild.setProperty("missing", 42).return, true);
+ assertEq(proxyChild.getProperty("missing").return, 42);
+
+ assertEq(proxyChild.setProperty("stringNormal", "a normal child value").return, true);
+ assertEq(proxyChild.getProperty("_stringNormal").return, "a normal child value");
+
+ assertEq(proxyChild.setProperty("stringAbrupt", "an abrupt child value").throw, "an abrupt child value");
+
+ assertEq(proxyChild.setProperty("objectNormal", testProxyObj).return, true);
+ assertEq(proxyChild.getProperty("_objectNormal").return, testProxyObj);
+
+ assertEq(proxyChild.setProperty("objectAbrupt", testProxyObj).throw, testProxyObj);
+
+ assertEq(proxyChild.setProperty("context", "a value").return, true);
+ assertEq(proxyChild.getProperty("_context").return, proxyChild);
+
+ assertEq(proxyChild.setProperty(1234, "number value").return, true);
+ assertEq(proxyChild.getProperty("_1234").return, "number value");
+
+ assertEq(proxyChild.setProperty(sym, "symbol value").return, true);
+ assertEq(proxyChild.getProperty("_sym").return, "symbol value");
+
+ assertEq(proxyChild.setProperty("getterOnly", "a value").return, false);
+ assertEq(proxyChild.setProperty("readOnly", "a value").return, false);
+
+ assertEq(proxyChild.setProperty("stringProp", "a normal child value").return, true);
+ assertEq(proxyChild.getProperty("stringProp").return, "a normal child value");
+
+ assertEq(proxyChild.setProperty("objectProp", testProxyObj).return, true);
+ assertEq(proxyChild.getProperty("objectProp").return, testProxyObj);
+};
diff --git a/js/src/jit-test/tests/debug/Object-setProperty-02.js b/js/src/jit-test/tests/debug/Object-setProperty-02.js
new file mode 100644
index 0000000000..42b61f8190
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-setProperty-02.js
@@ -0,0 +1,36 @@
+// tests calling script functions via Debugger.Object.prototype.setProperty
+// to see if they trigger debugger traps
+"use strict";
+
+var global = newGlobal({newCompartment: true});
+var dbg = new Debugger(global);
+dbg.onDebuggerStatement = onDebuggerStatement;
+
+let obj;
+global.eval(`
+const obj = {
+ set prop(val) {
+ debugger;
+ this._prop = val;
+ }
+};
+
+debugger;
+`);
+
+
+function onDebuggerStatement(frame) {
+ dbg.onDebuggerStatement = onDebuggerStatementGetter;
+
+ obj = frame.environment.getVariable("obj");
+}
+
+let debuggerRan = false;
+
+assertEq(obj.setProperty("prop", 42).return, true);
+assertEq(obj.getProperty("_prop").return, 42);
+assertEq(debuggerRan, true);
+
+function onDebuggerStatementGetter(frame) {
+ debuggerRan = true;
+}
diff --git a/js/src/jit-test/tests/debug/Object-setProperty-03.js b/js/src/jit-test/tests/debug/Object-setProperty-03.js
new file mode 100644
index 0000000000..002553c477
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-setProperty-03.js
@@ -0,0 +1,67 @@
+// tests calling script functions via Debugger.Object.prototype.setProperty
+// with different receiver objects.
+"use strict";
+load(libdir + "/asserts.js");
+
+var global = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var globalDO = dbg.addDebuggee(global);
+var windowProxyDO = globalDO.makeDebuggeeValue(global);
+dbg.onDebuggerStatement = onDebuggerStatement;
+
+global.eval(`
+let receiver;
+function check(value, thisVal) {
+ receiver = thisVal;
+ if (value !== "value") throw "Unexpected value";
+}
+const sloppy = {
+ set setter(value) { check(value, this); },
+};
+const strict = {
+ set setter(value) { "use strict"; check(value, this); },
+};
+debugger;
+`);
+
+function onDebuggerStatement(frame) {
+ const { environment } = frame;
+ const sloppy = environment.getVariable("sloppy");
+ const strict = environment.getVariable("strict");
+ const receiver = () => environment.getVariable("receiver");
+ const value = "value";
+
+ assertEq(sloppy.setProperty("setter", value).return, true);
+ assertEq(receiver(), sloppy);
+ assertEq(sloppy.setProperty("setter", value, sloppy).return, true);
+ assertEq(receiver(), sloppy);
+ assertEq(sloppy.setProperty("setter", value, strict).return, true);
+ assertEq(receiver(), strict);
+ assertEq(sloppy.setProperty("setter", value, 1).return, true);
+ assertEq(receiver().class, "Number");
+ assertEq(sloppy.setProperty("setter", value, true).return, true);
+ assertEq(receiver().class, "Boolean");
+ assertEq(sloppy.setProperty("setter", value, null).return, true);
+ assertEq(receiver(), windowProxyDO);
+ assertEq(sloppy.setProperty("setter", value, undefined).return, true);
+ assertEq(receiver(), windowProxyDO);
+ assertErrorMessage(() => sloppy.setProperty("setter", value, {}), TypeError,
+ "Debugger: expected Debugger.Object, got Object");
+
+ assertEq(strict.setProperty("setter", value).return, true);
+ assertEq(receiver(), strict);
+ assertEq(strict.setProperty("setter", value, sloppy).return, true);
+ assertEq(receiver(), sloppy);
+ assertEq(strict.setProperty("setter", value, strict).return, true);
+ assertEq(receiver(), strict);
+ assertEq(strict.setProperty("setter", value, 1).return, true);
+ assertEq(receiver(), 1);
+ assertEq(strict.setProperty("setter", value, true).return, true);
+ assertEq(receiver(), true);
+ assertEq(strict.setProperty("setter", value, null).return, true);
+ assertEq(receiver(), null);
+ assertEq(strict.setProperty("setter", value, undefined).return, true);
+ assertEq(receiver(), undefined);
+ assertErrorMessage(() => strict.setProperty("setter", value, {}), TypeError,
+ "Debugger: expected Debugger.Object, got Object");
+};
diff --git a/js/src/jit-test/tests/debug/Object-setProperty-non-primitive-key.js b/js/src/jit-test/tests/debug/Object-setProperty-non-primitive-key.js
new file mode 100644
index 0000000000..e6aaa138a4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-setProperty-non-primitive-key.js
@@ -0,0 +1,49 @@
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var gw = dbg.addDebuggee(g);
+
+g.eval(`
+ var obj = {
+ p: 0,
+ [Symbol.iterator]: 0,
+ };
+`);
+
+// Return |key| as an object.
+function toObject(key) {
+ return {
+ [Symbol.toPrimitive]() {
+ return key;
+ }
+ };
+}
+
+let obj = gw.getProperty("obj").return;
+
+for (let key of obj.getOwnPropertyNames()) {
+ let keyObject = toObject(key);
+
+ g.obj[key] = 1;
+ assertEq(g.obj[key], 1);
+ assertEq(obj.setProperty(key, 2).return, true);
+ assertEq(g.obj[key], 2);
+
+ g.obj[key] = 1;
+ assertEq(g.obj[key], 1);
+ assertEq(obj.setProperty(keyObject, 2).return, true);
+ assertEq(g.obj[key], 2);
+}
+
+for (let key of obj.getOwnPropertySymbols()) {
+ let keyObject = toObject(key);
+
+ g.obj[key] = 1;
+ assertEq(g.obj[key], 1);
+ assertEq(obj.setProperty(key, 2).return, true);
+ assertEq(g.obj[key], 2);
+
+ g.obj[key] = 1;
+ assertEq(g.obj[key], 1);
+ assertEq(obj.setProperty(keyObject, 2).return, true);
+ assertEq(g.obj[key], 2);
+}
diff --git a/js/src/jit-test/tests/debug/Object-unsafeDereference-01.js b/js/src/jit-test/tests/debug/Object-unsafeDereference-01.js
new file mode 100644
index 0000000000..daac3555e2
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-unsafeDereference-01.js
@@ -0,0 +1,10 @@
+// Debugger.Object.prototype.unsafeDereference returns the referent directly.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+
+assertEq(gw.getOwnPropertyDescriptor('Math').value.unsafeDereference(), g.Math);
+
+g.eval('var obj = {}');
+assertEq(gw.getOwnPropertyDescriptor('obj').value.unsafeDereference(), g.obj);
diff --git a/js/src/jit-test/tests/debug/Object-unwrap-01.js b/js/src/jit-test/tests/debug/Object-unwrap-01.js
new file mode 100644
index 0000000000..345723e0e8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-unwrap-01.js
@@ -0,0 +1,23 @@
+// Check Debugger.Object.prototype.unwrap surfaces.
+
+load(libdir + 'asserts.js');
+
+var dbg = new Debugger();
+var g = newGlobal({newCompartment: true});
+var gw = dbg.addDebuggee(g);
+
+assertEq(Object.getOwnPropertyDescriptor(gw, 'unwrap'), undefined);
+var d = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(gw), 'unwrap');
+assertEq(d.enumerable, false);
+assertEq(d.configurable, true);
+assertEq(d.writable, true);
+
+assertEq(typeof gw.unwrap, "function");
+assertEq(gw.unwrap.length, 0);
+assertEq(gw.unwrap.name, "unwrap");
+
+// It can be called.
+gw.unwrap();
+
+// You shouldn't be able to apply the accessor to the prototype.
+assertThrowsInstanceOf(function () { Debugger.Object.prototype.unwrap(); }, TypeError);
diff --git a/js/src/jit-test/tests/debug/Object-unwrap-02.js b/js/src/jit-test/tests/debug/Object-unwrap-02.js
new file mode 100644
index 0000000000..3d65db2fe7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-unwrap-02.js
@@ -0,0 +1,21 @@
+// Debugger.Object.prototype.unwrap unwraps Debugger.Objects referring to
+// cross-compartment wrappers.
+
+var dbg = new Debugger();
+
+var g1 = newGlobal({newCompartment: true});
+var dg1 = dbg.addDebuggee(g1);
+assertEq(dg1.unwrap(), dg1);
+
+var g2 = newGlobal({newCompartment: true});
+var dg2 = dbg.addDebuggee(g2);
+
+var dg1g2 = dg1.makeDebuggeeValue(g2);
+assertEq(dg1g2.unwrap(), dg2.makeDebuggeeValue(g2));
+
+// Try an ordinary object, not a global.
+var g2o = g2.Object();
+var dg2o = dg2.makeDebuggeeValue(g2o);
+var dg1g2o = dg1.makeDebuggeeValue(g2o);
+assertEq(dg1g2o.unwrap(), dg2o);
+assertEq(dg1g2o.unwrap().unwrap(), dg2o);
diff --git a/js/src/jit-test/tests/debug/Object-unwrap-03.js b/js/src/jit-test/tests/debug/Object-unwrap-03.js
new file mode 100644
index 0000000000..d6c9180be6
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-unwrap-03.js
@@ -0,0 +1,15 @@
+// Debugger.Object.prototype.unwrap should not let us see things in
+// invisible-to-Debugger compartments.
+
+load(libdir + 'asserts.js');
+
+var g = newGlobal({ newCompartment: true, invisibleToDebugger: true });
+
+var dbg = new Debugger;
+
+// Create a wrapper in our compartment for the global.
+// Note that makeGlobalObjectReference won't do: it tries to dereference as far
+// as it can go.
+var /* yo */ DOwg = dbg.makeGlobalObjectReference(this).makeDebuggeeValue(g);
+
+assertThrowsInstanceOf(() => DOwg.unwrap(), Error);
diff --git a/js/src/jit-test/tests/debug/Promise-race-dependent-promises.js b/js/src/jit-test/tests/debug/Promise-race-dependent-promises.js
new file mode 100644
index 0000000000..196d29aeae
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Promise-race-dependent-promises.js
@@ -0,0 +1,46 @@
+// Promise.race(...) may add a dummy PromiseReaction which is only used for the
+// debugger.
+//
+// See BlockOnPromise when called from PerformPromiseRace for when this dummy
+// reaction is created.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+
+function test(withFastPath) {
+ g.eval(`
+ function newPromiseCapability() {
+ var resolve, reject, promise = new Promise(function(r1, r2) {
+ resolve = r1;
+ reject = r2;
+ });
+ return {promise, resolve, reject};
+ }
+
+ var {promise: alwaysPending} = newPromiseCapability();
+
+ if (!${withFastPath}) {
+ // Disable the BlockOnPromise fast path by giving |alwaysPending| a
+ // non-default "then" function property. This will ensure the dummy
+ // reaction is created.
+ alwaysPending.then = function() {};
+ }
+
+ var result = Promise.race([alwaysPending]);
+ `);
+
+ var alwaysPending = gw.makeDebuggeeValue(g.alwaysPending);
+ var result = gw.makeDebuggeeValue(g.result);
+
+ assertEq(alwaysPending.promiseDependentPromises.length, 1);
+ assertEq(alwaysPending.promiseDependentPromises[0], result);
+
+ assertEq(result.promiseDependentPromises.length, 0);
+}
+
+// No dummy reaction created when the fast path is taken.
+test(true);
+
+// Dummy reaction is created when we can't take the fast path.
+test(false);
diff --git a/js/src/jit-test/tests/debug/RematerializedFrame-retval.js b/js/src/jit-test/tests/debug/RematerializedFrame-retval.js
new file mode 100644
index 0000000000..18e7778f27
--- /dev/null
+++ b/js/src/jit-test/tests/debug/RematerializedFrame-retval.js
@@ -0,0 +1,39 @@
+// |jit-test| error: InternalError; --baseline-eager; --ion-eager
+// Make sure that return values we store in RematerializedFrames via resumption
+// values get propagated to the BaselineFrames we build from them.
+//
+// Test case from bug 1285939; there's another in bug 1282518, but this one
+// takes less time to run, when it doesn't crash.
+
+var lfLogBuffer = `
+function testResumptionVal(resumptionVal, turnOffDebugMode) {
+ var g = newGlobal({newCompartment: true});
+ var dbg = new Debugger;
+ setInterruptCallback(function () {
+ dbg.addDebuggee(g);
+ var frame = dbg.getNewestFrame();
+ frame.onStep = function () {
+ frame.onStep = undefined;
+ return resumptionVal;
+ };
+ return true;
+ });
+ try {
+ return g.eval("(" + function f() {
+ invokeInterruptCallback(function (interruptRv) {
+ f({ valueOf: function () { dbg.g(dbg); }});
+ });
+ } + ")();");
+ } finally { }
+}
+assertEq(testResumptionVal({ return: "not 42" }), "not 42");
+`;
+loadFile(lfLogBuffer);
+function loadFile(lfVarx) {
+ try {
+ let m = parseModule(lfVarx);
+ m.declarationInstantiation();
+ m.evaluation();
+ } catch (lfVare) {}
+}
+
diff --git a/js/src/jit-test/tests/debug/Script-01.js b/js/src/jit-test/tests/debug/Script-01.js
new file mode 100644
index 0000000000..c88d6bc3e2
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-01.js
@@ -0,0 +1,70 @@
+// We get the same Debugger.Script object instance each time we ask.
+
+var global = newGlobal({newCompartment: true});
+global.eval('function f() { debugger; }');
+global.eval('function g() { debugger; }');
+
+var debug = new Debugger(global);
+
+function evalAndNoteScripts(prog) {
+ var scripts = {};
+ debug.onDebuggerStatement = function(frame) {
+ if (frame.type == "call")
+ assertEq(frame.script, frame.callee.script);
+ scripts.frame = frame.script;
+ if (frame.arguments[0])
+ scripts.argument = frame.arguments[0].script;
+ };
+ global.eval(prog);
+ return scripts;
+}
+
+// If we create a frame for a function and pass it as a value, those should
+// both yield the same Debugger.Script instance.
+var scripts = evalAndNoteScripts('f(f)');
+assertEq(scripts.frame, scripts.argument);
+var fScript = scripts.argument;
+
+// If we call a second time, we should still get the same instance.
+scripts = evalAndNoteScripts('f(f)');
+assertEq(scripts.frame, fScript);
+assertEq(scripts.argument, fScript);
+
+// If we call with a different argument, we should get a different Debugger.Script.
+scripts = evalAndNoteScripts('f(g)');
+assertEq(scripts.frame !== scripts.argument, true);
+assertEq(scripts.frame, fScript);
+var gScript = scripts.argument;
+
+// See if we can get g via the frame.
+scripts = evalAndNoteScripts('g(f)');
+assertEq(scripts.frame !== scripts.argument, true);
+assertEq(scripts.frame, gScript);
+assertEq(scripts.argument, fScript);
+
+// Different closures made from the same 'function' expression should yield
+// the same script.
+global.eval('function gen1(x) { return function clo(y) { return x+y; }; }');
+global.eval('var clo1 = gen1(42);');
+global.eval('var clo2 = gen1("smoot");');
+var scripts1 = evalAndNoteScripts('f(clo1)');
+var scripts2 = evalAndNoteScripts('f(clo2)');
+assertEq(scripts1.argument, scripts2.argument);
+
+// Different closures made from the same 'function' declaration should yield
+// the same script.
+global.eval('function gen2(x) { function clo(y) { return x+y; }; return clo; }');
+global.eval('var clo1 = gen2(42);');
+global.eval('var clo2 = gen2("smoot");');
+var scripts1 = evalAndNoteScripts('f(clo1)');
+var scripts2 = evalAndNoteScripts('f(clo2)');
+assertEq(scripts1.argument, scripts2.argument);
+
+// Different closures made from the same 'function' statement should yield
+// the same script.
+global.eval('function gen3(x) { if (true) { function clo(y) { return x+y; }; return clo; } }');
+global.eval('var clo1 = gen3(42);');
+global.eval('var clo2 = gen3("smoot");');
+var scripts1 = evalAndNoteScripts('f(clo1)');
+var scripts2 = evalAndNoteScripts('f(clo2)');
+assertEq(scripts1.argument, scripts2.argument);
diff --git a/js/src/jit-test/tests/debug/Script-02.js b/js/src/jit-test/tests/debug/Script-02.js
new file mode 100644
index 0000000000..071dc465ae
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-02.js
@@ -0,0 +1,6 @@
+// Debugger.Script throws when applied as a constructor.
+
+load(libdir + 'asserts.js');
+
+assertThrowsInstanceOf(function() { Debugger.Script(); }, TypeError);
+assertThrowsInstanceOf(function() { new Debugger.Script(); }, TypeError);
diff --git a/js/src/jit-test/tests/debug/Script-clearBreakpoint-01.js b/js/src/jit-test/tests/debug/Script-clearBreakpoint-01.js
new file mode 100644
index 0000000000..631e9b0a29
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-clearBreakpoint-01.js
@@ -0,0 +1,19 @@
+// A breakpoint handler may clear itself.
+
+var g = newGlobal({newCompartment: true});
+var bphits = 0;
+var handler = {hit: function (frame) { frame.script.clearBreakpoint(this); bphits++; }};
+var dbg = Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ var offs = frame.script.getLineOffsets(g.line0 + 3);
+ for (var i = 0; i < offs.length; i++)
+ frame.script.setBreakpoint(offs[i], handler);
+ hits++;
+};
+g.eval("var line0 = Error().lineNumber;\n" +
+ "debugger;\n" + // line0 + 1
+ "for (var i = 0; i < 4; i++)\n" + // line0 + 2
+ " result = 'ok';\n"); // line0 + 3
+assertEq(hits, 1);
+assertEq(bphits, 1);
diff --git a/js/src/jit-test/tests/debug/Script-clearBreakpoint-02.js b/js/src/jit-test/tests/debug/Script-clearBreakpoint-02.js
new file mode 100644
index 0000000000..2de4eb210e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-clearBreakpoint-02.js
@@ -0,0 +1,26 @@
+// A breakpoint cleared during dispatch does not fire.
+// (Breakpoint dispatch is well-behaved even when breakpoint handlers clear other breakpoints.)
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var log = '';
+dbg.onDebuggerStatement = function (frame) {
+ var s = frame.script;
+ function handler(i) {
+ if (i === 1)
+ return function () { log += i; s.clearBreakpoint(h[1]); s.clearBreakpoint(h[2]); };
+ return function () { log += i; };
+ }
+ var offs = s.getLineOffsets(g.line0 + 2);
+ var h = [];
+ for (var i = 0; i < 4; i++) {
+ h[i] = {hit: handler(i)};
+ for (var j = 0; j < offs.length; j++)
+ s.setBreakpoint(offs[j], h[i]);
+ }
+};
+
+g.eval("var line0 = Error().lineNumber;\n" +
+ "debugger;\n" + // line0 + 1
+ "result = 'ok';\n"); // line0 + 2
+assertEq(log, '013');
diff --git a/js/src/jit-test/tests/debug/Script-clearBreakpoint-03.js b/js/src/jit-test/tests/debug/Script-clearBreakpoint-03.js
new file mode 100644
index 0000000000..c80939786e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-clearBreakpoint-03.js
@@ -0,0 +1,25 @@
+// Clearing a breakpoint by handler can clear multiple breakpoints.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var s;
+dbg.onDebuggerStatement = function (frame) {
+ s = frame.eval("f").return.script;
+};
+g.eval("var line0 = Error().lineNumber;\n" +
+ "function f(a, b) {\n" + // line0 + 1
+ " return a + b;\n" + // line0 + 2
+ "}\n" +
+ "debugger;\n");
+
+var hits = 0;
+var handler = {hit: function (frame) { hits++; s.clearBreakpoint(handler); }};
+var offs = s.getLineOffsets(g.line0 + 2);
+for (var i = 0; i < 4; i++) {
+ for (var j = 0; j < offs.length; j++)
+ s.setBreakpoint(offs[j], handler);
+}
+
+assertEq(g.f(2, 2), 4);
+assertEq(hits, 1);
+assertEq(s.getBreakpoints().length, 0);
diff --git a/js/src/jit-test/tests/debug/Script-clearBreakpoint-04.js b/js/src/jit-test/tests/debug/Script-clearBreakpoint-04.js
new file mode 100644
index 0000000000..b1da7b4095
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-clearBreakpoint-04.js
@@ -0,0 +1,28 @@
+// clearBreakpoint clears breakpoints for the current Debugger object only.
+
+var g = newGlobal({newCompartment: true});
+
+var hits = 0;
+var handler = {
+ hit: function (frame) {
+ hits++;
+ frame.script.clearBreakpoint(handler);
+ }
+};
+
+function attach(i) {
+ var dbg = Debugger(g);
+ dbg.onDebuggerStatement = function (frame) {
+ var s = frame.script;
+ var offs = s.getLineOffsets(g.line0 + 2);
+ for (var i = 0; i < offs.length; i++)
+ s.setBreakpoint(offs[i], handler);
+ };
+}
+for (var i = 0; i < 4; i++)
+ attach(i);
+
+g.eval("var line0 = Error().lineNumber;\n" +
+ "debugger;\n" + // line0 + 1
+ "Math.sin(0);\n"); // line0 + 2
+assertEq(hits, 4);
diff --git a/js/src/jit-test/tests/debug/Script-displayName-01.js b/js/src/jit-test/tests/debug/Script-displayName-01.js
new file mode 100644
index 0000000000..02056f7542
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-displayName-01.js
@@ -0,0 +1,17 @@
+// Debugger.Script.prototype.displayName
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var name;
+dbg.onDebuggerStatement = f => { name = f.callee.script.displayName; };
+
+g.eval("(function f() { debugger; })();");
+assertEq(name, "f");
+g.eval("(function () { debugger; })();");
+assertEq(name, undefined);
+g.eval("Function('debugger;')();");
+assertEq(name, "anonymous");
+g.eval("var f = function() { debugger; }; f()");
+assertEq(name, "f");
+g.eval("var a = {}; a.f = function() { debugger; }; a.f()");
+assertEq(name, "a.f");
diff --git a/js/src/jit-test/tests/debug/Script-format-01.js b/js/src/jit-test/tests/debug/Script-format-01.js
new file mode 100644
index 0000000000..24db07dc72
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-format-01.js
@@ -0,0 +1,18 @@
+// |jit-test| skip-if: !wasmDebuggingEnabled()
+
+// Tests that JavaScript scripts have a "js" format and wasm scripts have a
+// "wasm" format.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+var gotScript;
+dbg.onNewScript = (script) => {
+ gotScript = script;
+};
+
+g.eval(`(() => {})()`);
+assertEq(gotScript.format, "js");
+
+g.eval(`o = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (func) (export "" (func 0)))')));`);
+assertEq(gotScript.format, "wasm");
diff --git a/js/src/jit-test/tests/debug/Script-gc-01.js b/js/src/jit-test/tests/debug/Script-gc-01.js
new file mode 100644
index 0000000000..f328008b15
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-gc-01.js
@@ -0,0 +1,26 @@
+// Debugger.Script instances with live referents stay alive.
+
+var N = 4;
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var i;
+dbg.onDebuggerStatement = function (frame) {
+ assertEq(frame.script instanceof Debugger.Script, true);
+ frame.script.id = i;
+};
+
+g.eval('var arr = [];')
+for (i = 0; i < N; i++) // loop to defeat conservative GC
+ g.eval("arr.push(function () { debugger }); arr[arr.length - 1]();");
+
+gc();
+
+var hits;
+dbg.onDebuggerStatement = function (frame) {
+ hits++;
+ assertEq(frame.script.id, i);
+};
+hits = 0;
+for (i = 0; i < N; i++)
+ g.arr[i]();
+assertEq(hits, N);
diff --git a/js/src/jit-test/tests/debug/Script-gc-02.js b/js/src/jit-test/tests/debug/Script-gc-02.js
new file mode 100644
index 0000000000..c805e2c66e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-gc-02.js
@@ -0,0 +1,14 @@
+// Debugger.Scripts keep their referents alive.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var arr = [];
+dbg.onDebuggerStatement = function (frame) { arr.push(frame.script); };
+g.eval("for (var i = 0; i < 10; i++) Function('debugger;')();");
+assertEq(arr.length, 10);
+
+gc();
+
+for (var i = 0; i < arr.length; i++)
+ assertEq(arr[i].lineCount, 4);
+
diff --git a/js/src/jit-test/tests/debug/Script-gc-03.js b/js/src/jit-test/tests/debug/Script-gc-03.js
new file mode 100644
index 0000000000..ab53203239
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-gc-03.js
@@ -0,0 +1,15 @@
+// Referents of Debugger.Scripts in other compartments always survive per-compartment GC.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var arr = [];
+dbg.onDebuggerStatement = function (frame) { arr.push(frame.script); };
+g.eval("for (var i = 0; i < 100; i++) Function('debugger;')();");
+assertEq(arr.length, 100);
+
+gc(g);
+
+for (var i = 0; i < arr.length; i++)
+ assertEq(arr[i].lineCount, 4);
+
+gc();
diff --git a/js/src/jit-test/tests/debug/Script-getAllColumnOffsets.js b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets.js
new file mode 100644
index 0000000000..1661ca2ad2
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets.js
@@ -0,0 +1,147 @@
+load(libdir + "assert-offset-columns.js");
+
+// getColumnOffsets correctly places the various parts of a ForStatement.
+assertOffsetColumns(
+ "function f(n) { for (var i = 0; i < n; ++i) hits.push('.'); hits.push('!'); }",
+ " ^ ^ ^ ^ ^ ^ ^ ^",
+ "0 1 3 4 . 2 1 3 4 . 2 1 3 4 . 2 1 5 6 ! 7"
+);
+
+// getColumnOffsets correctly places multiple variable declarations.
+assertOffsetColumns(
+ "function f(n){var w0,x1=3,y2=4,z3=9}",
+ " ^ ^ ^^"
+);
+
+// getColumnOffsets correctly places comma separated expressions.
+assertOffsetColumns(
+ "function f(n){print(n),print(n),print(n)}",
+ " ^ ^ ^ ^ ^"
+);
+
+// getColumnOffsets correctly places object properties.
+assertOffsetColumns(
+ // Should hit each property in the object.
+ "function f(n){var o={a:1,b:2,c:3}}",
+ " ^^ ^ ^ ^"
+);
+
+// getColumnOffsets correctly places array properties.
+assertOffsetColumns(
+ // Should hit each item in the array.
+ "function f(n){var a=[1,2,n]}",
+ " ^^ ^ ^ ^"
+);
+
+// getColumnOffsets correctly places function calls.
+assertOffsetColumns(
+ "function ppppp() { return 1; }\n" +
+ "function f(){ 1 && ppppp(ppppp()) && new Error() }",
+ " ^ ^ ^ ^ ^",
+ "0 2 1 3 4"
+);
+
+// getColumnOffsets correctly places the various parts of a SwitchStatement.
+assertOffsetColumns(
+ "function f(n) { switch(n) { default: print(n); } }",
+ " ^ ^ ^ ^"
+);
+
+// getColumnOffsets correctly places the various parts of a BreakStatement.
+assertOffsetColumns(
+ "function f(n) { do { print(n); if (n === 3) { break; } } while(false); }",
+ " ^ ^ ^ ^ ^ ^ ^",
+ "0 1 2 3 4 6"
+);
+
+// If the loop condition is unreachable, we currently don't report its offset.
+assertOffsetColumns(
+ "function f(n) { do { print(n); break; } while(false); }",
+ " ^ ^ ^ ^ ^",
+);
+
+// getColumnOffsets correctly places the various parts of a ContinueStatement.
+assertOffsetColumns(
+ "function f(n) { do { print(n); continue; } while(false); }",
+ " ^ ^ ^ ^ ^ ^"
+);
+
+// getColumnOffsets correctly places the various parts of a WithStatement.
+assertOffsetColumns(
+ "function f(n) { with({}) { print(n); } }",
+ " ^ ^ ^ ^"
+);
+
+// getColumnOffsets correctly places the various parts of a IfStatement.
+assertOffsetColumns(
+ "function f(n) { if (n == 3) print(n); }",
+ " ^ ^ ^ ^"
+);
+
+// getColumnOffsets correctly places the various parts of a IfStatement
+// with an if/else
+assertOffsetColumns(
+ "function f(n) { if (n == 2); else if (n === 3) print(n); }",
+ " ^ ^ ^ ^ ^"
+);
+
+// getColumnOffsets correctly places the various parts of a DoWhileStatement.
+assertOffsetColumns(
+ "function f(n) { do { print(n); } while(false); }",
+ " ^ ^ ^ ^ ^"
+);
+
+// getColumnOffsets correctly places the part of normal ::Dot node with identifier root.
+assertOffsetColumns(
+ "var args = [];\n" +
+ "var obj = { base: { a(){ return { b(){} }; } } };\n" +
+ "function f(n) { obj.base.a().b(...args); }",
+ " ^ ^ ^ ^ ^",
+ "0 1 3 2 4"
+);
+
+// getColumnOffsets correctly places the part of normal ::Dot node with "this" root.
+assertOffsetColumns(
+ "var args = [];\n" +
+ "var obj = { base: { a(){ return { b(){} }; } } };\n" +
+ "var f = function() { this.base.a().b(...args); }.bind(obj);",
+ " ^ ^ ^ ^ ^",
+ "0 1 3 2 4"
+);
+
+// getColumnOffsets correctly places the part of normal ::Dot node with "super" base.
+assertOffsetColumns(
+ "var args = [];\n" +
+ "var obj = { base: { a(){ return { b(){} }; } } };\n" +
+ "var f = { __proto__: obj, f(n) { super.base.a().b(...args); } }.f;",
+ " ^ ^ ^ ^ ^",
+ "0 1 3 2 4"
+);
+
+// getColumnOffsets correctly places the part of normal ::Dot node with other base.
+assertOffsetColumns(
+ "var args = [];\n" +
+ "var obj = { base: { a(){ return { b(){} }; } } };\n" +
+ "function f(n) { (0, obj).base.a().b(...args); }",
+ " ^ ^ ^ ^ ^ ^",
+ "0 1 2 4 3 5"
+);
+
+// getColumnOffsets correctly places the part of folded ::Elem node.
+assertOffsetColumns(
+ "var args = [];\n" +
+ "var obj = { base: { a(){ return { b(){} }; } } };\n" +
+ // Constant folding makes the static string behave like a dot access.
+ "function f(n) { obj.base['a']()['b'](...args); }",
+ " ^ ^ ^ ^ ^",
+ "0 1 3 2 4"
+);
+
+// getColumnOffsets correctly places the part of computed ::Elem node.
+assertOffsetColumns(
+ "var args = [], a = 'a', b = 'b';\n" +
+ "var obj = { base: { a(){ return { b(){} }; } } };\n" +
+ "function f(n) { obj.base[a]()[b](...args); }",
+ " ^ ^ ^^ ^",
+ "0 1 3 2 4"
+);
diff --git a/js/src/jit-test/tests/debug/Script-getBreakpoints-01.js b/js/src/jit-test/tests/debug/Script-getBreakpoints-01.js
new file mode 100644
index 0000000000..ac143588fb
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getBreakpoints-01.js
@@ -0,0 +1,40 @@
+// Basic Script.prototype.getBreakpoints tests.
+
+var g = newGlobal({newCompartment: true});
+g.eval("var line0 = Error().lineNumber;\n" +
+ "function f(x) {\n" + // line0 + 1
+ " if (x < 0)\n" + // line0 + 2
+ " return -x;\n" + // line0 + 3
+ " return x;\n" +
+ "}");
+
+var s;
+var offsets = [];
+var handlers = [];
+var dbg = Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+ s = frame.eval("f").return.script;
+ var off;
+
+ for (var i = 0; i < 3; i++) {
+ var off = s.getLineOffsets(g.line0 + 2 + i)[0];
+ assertEq(typeof off, 'number');
+ handlers[i] = {};
+ s.setBreakpoint(off, handlers[i]);
+ offsets[i] = off;
+ }
+};
+g.eval("debugger;");
+
+// getBreakpoints without an offset gets all breakpoints in the script.
+var bps = s.getBreakpoints();
+assertEq(bps.length, handlers.length);
+for (var i = 0; i < bps.length; i++)
+ assertEq(bps.indexOf(handlers[i]) !== -1, true);
+
+// getBreakpoints with an offset finds only breakpoints at that offset.
+for (var i = 0; i < offsets.length; i++) {
+ var bps = s.getBreakpoints(offsets[i]);
+ assertEq(bps.length, 1);
+ assertEq(bps[0], handlers[i]);
+}
diff --git a/js/src/jit-test/tests/debug/Script-getBreakpoints-02.js b/js/src/jit-test/tests/debug/Script-getBreakpoints-02.js
new file mode 100644
index 0000000000..ead429889e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getBreakpoints-02.js
@@ -0,0 +1,42 @@
+// Script.prototype.getBreakpoints returns breakpoints for the current Debugger object only.
+
+var g = newGlobal({newCompartment: true});
+
+var debuggers = [];
+var hits = 0;
+function attach(g, i) {
+ var dbg = Debugger(g);
+ dbg.onDebuggerStatement = function (frame) {
+ var s = frame.script;
+ var offs = s.getLineOffsets(g.line0 + 2);
+ var hitAny = false;
+ var handler = {
+ hit: function (frame) {
+ assertEq(this, handler);
+ assertEq(frame.script, s);
+ var bps = s.getBreakpoints();
+ assertEq(bps.length, offs.length);
+ for (var i = 0; i < bps.length; i++)
+ assertEq(bps[i], handler);
+
+ if (!hitAny) {
+ hitAny = true;
+ hits++;
+ }
+ }
+ };
+ for (var i = 0; i < offs.length; i++)
+ s.setBreakpoint(offs[i], handler);
+ hits++;
+ };
+ debuggers[i] = dbg;
+}
+
+for (var i = 0; i < 3; i++)
+ attach(g, i);
+g.done = false;
+g.eval("var line0 = Error().lineNumber;\n" +
+ "debugger;\n" + // line0 + 1
+ "done = true;\n"); // line0 + 2
+assertEq(hits, 6);
+assertEq(g.done, true);
diff --git a/js/src/jit-test/tests/debug/Script-getChildScripts-01.js b/js/src/jit-test/tests/debug/Script-getChildScripts-01.js
new file mode 100644
index 0000000000..99f9cf5b36
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getChildScripts-01.js
@@ -0,0 +1,40 @@
+// Basic getChildScripts tests.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var log;
+function note(s) {
+ assertEq(s instanceof Debugger.Script, true);
+ log += 'S';
+ var c = s.getChildScripts();
+ if (c.length > 0) {
+ log += '[';
+ for (var i = 0; i < c.length; i++)
+ note(c[i]);
+ log += ']';
+ }
+}
+dbg.onDebuggerStatement = function (frame) { note(frame.script); };
+
+function test(code, expected) {
+ log = '';
+ g.eval(code);
+ assertEq(log, expected);
+}
+
+test("debugger;",
+ "S");
+test("function f() {} debugger;",
+ "S[S]");
+test("function g() { function h() { function k() {} return k; } return h; } debugger;",
+ "S[S[S[S]]]");
+test("function q() {} function qq() {} debugger;",
+ "S[SS]");
+test("[0].map(function id(a) { return a; }); debugger;",
+ "S[S]");
+test("Function('return 2+2;')(); debugger;",
+ "S");
+test("var obj = {get x() { return 0; }, set x(v) {}}; debugger;",
+ "S[SS]");
+test("function* qux(n) { for (var i = 0; i < n; i++) yield i; } debugger;",
+ "S[S]");
diff --git a/js/src/jit-test/tests/debug/Script-getChildScripts-02.js b/js/src/jit-test/tests/debug/Script-getChildScripts-02.js
new file mode 100644
index 0000000000..4a8982aeea
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getChildScripts-02.js
@@ -0,0 +1,20 @@
+// getChildScripts returns scripts in source order.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var scripts = [];
+var cs;
+dbg.onDebuggerStatement = function (frame) {
+ scripts.push(frame.script);
+ if (scripts.length === 1)
+ cs = frame.script.getChildScripts();
+};
+
+g.eval("function f() { debugger; }\n" +
+ "var g = function () { debugger; }\n" +
+ "debugger; f(); g();");
+
+assertEq(scripts.length, 3);
+assertEq(cs.length, 2);
+assertEq(cs[0], scripts[1]);
+assertEq(cs[1], scripts[2]);
diff --git a/js/src/jit-test/tests/debug/Script-getChildScripts-03.js b/js/src/jit-test/tests/debug/Script-getChildScripts-03.js
new file mode 100644
index 0000000000..09e5cba32c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getChildScripts-03.js
@@ -0,0 +1,16 @@
+// getChildScripts on a direct eval script returns the right scripts.
+// (A bug had it also returning the script for the calling function.)
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ var arr = frame.script.getChildScripts();
+ assertEq(arr.length, 1);
+ assertEq(arr[0], frame.eval("h").return.script);
+ hits++;
+};
+
+g.eval("function f(s) { eval(s); }");
+g.f("debugger; function h(a) { return a + 1; }");
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Script-getChildScripts-04.js b/js/src/jit-test/tests/debug/Script-getChildScripts-04.js
new file mode 100644
index 0000000000..fc47247137
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getChildScripts-04.js
@@ -0,0 +1,15 @@
+// script.getChildScripts() works correctly during the newScript hook.
+// (A bug had it including the script for the calling function.)
+
+var g = newGlobal({newCompartment: true});
+g.eval("function h(a) { eval(a); }");
+
+var dbg = Debugger(g);
+var arr, kscript;
+dbg.onNewScript = function (script) { arr = script.getChildScripts(); };
+dbg.onDebuggerStatement = function (frame) { kscript = frame.callee.script; };
+
+g.h("function k(a) { debugger; return a + 1; } k(-1);");
+assertEq(kscript instanceof Debugger.Script, true);
+assertEq(arr.length, 1);
+assertEq(arr[0], kscript);
diff --git a/js/src/jit-test/tests/debug/Script-getChildScripts-05.js b/js/src/jit-test/tests/debug/Script-getChildScripts-05.js
new file mode 100644
index 0000000000..594e8db435
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getChildScripts-05.js
@@ -0,0 +1,16 @@
+// Test that lazy inner functions inside eval are tagged properly so we don't
+// incorrectly do NAME -> GNAME optimization.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+dbg.onNewScript = function delazify(script, global) {
+ // Force delazification of inner functions.
+ script.getChildScripts();
+};
+
+g.eval("" + function f() {
+ var $;
+ eval('var obj={foo:1}; $=function() { assertEq(obj.foo, 1); }');
+ return $;
+});
+g.eval("f()();");
diff --git a/js/src/jit-test/tests/debug/Script-getEffectfulOffsets.js b/js/src/jit-test/tests/debug/Script-getEffectfulOffsets.js
new file mode 100644
index 0000000000..49289a946f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getEffectfulOffsets.js
@@ -0,0 +1,33 @@
+// Check that Script.getEffectfulOffsets behaves sensibly.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var numEffectfulOperations;
+
+function onNewScript(script) {
+ script.getChildScripts().forEach(onNewScript);
+ numEffectfulOperations += script.getEffectfulOffsets().length;
+}
+dbg.onNewScript = onNewScript;
+
+function test(code, expected) {
+ numEffectfulOperations = 0;
+ g.eval(`
+function f(a, b, c) {
+${code}
+}
+`);
+ assertEq(numEffectfulOperations, expected);
+}
+
+test("a.f = 0", 2);
+test("a.f++", 2);
+test("return a.f", 1);
+test("a[b] = 0", 2);
+test("a[b]++", 2);
+test("return a[b]", 1);
+test("a = 0", 1);
+test("d = 0", 2);
+test("with (a) { b = 0; }", 4);
+test("let o = {}; ({x: o.x} = { x: 10 })", 2);
+test("var x", 1);
diff --git a/js/src/jit-test/tests/debug/Script-getLineOffsets-01.js b/js/src/jit-test/tests/debug/Script-getLineOffsets-01.js
new file mode 100644
index 0000000000..4bd1c47eaa
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getLineOffsets-01.js
@@ -0,0 +1,13 @@
+// getLineOffsets on a line that is definitely outside a script returns an empty array.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ var offs = frame.script.getLineOffsets(g.line0 + 2);
+ assertEq(Array.isArray(offs), true);
+ assertEq(offs.length, 0);
+ hits++;
+};
+g.eval("var line0 = Error().lineNumber; debugger;");
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Script-getLineOffsets-02.js b/js/src/jit-test/tests/debug/Script-getLineOffsets-02.js
new file mode 100644
index 0000000000..7fab0c9fb8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getLineOffsets-02.js
@@ -0,0 +1,33 @@
+// getLineOffsets correctly places the various parts of a ForStatement.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+ function handler(line) {
+ return {hit: function (frame) { g.log += "" + line; }};
+ }
+
+ var s = frame.eval("f").return.script;
+ for (var line = 2; line <= 6; line++) {
+ var offs = s.getLineOffsets(g.line0 + line);
+ var h = handler(line);
+ for (var i = 0; i < offs.length; i++) {
+ assertEq(s.getOffsetLocation(offs[i]).lineNumber, g.line0 + line);
+ s.setBreakpoint(offs[i], h);
+ }
+ }
+};
+
+g.log = '';
+g.eval("var line0 = Error().lineNumber;\n" +
+ "function f(n) {\n" + // line0 + 1
+ " for (var i = 0;\n" + // line0 + 2
+ " i < n;\n" + // line0 + 3
+ " i++)\n" + // line0 + 4
+ " log += '.';\n" + // line0 + 5
+ " log += '!';\n" + // line0 + 6
+ "}\n" +
+ "debugger;\n");
+assertEq(g.log, "");
+g.f(3);
+assertEq(g.log, "235.435.435.436!");
diff --git a/js/src/jit-test/tests/debug/Script-getLineOffsets-03.js b/js/src/jit-test/tests/debug/Script-getLineOffsets-03.js
new file mode 100644
index 0000000000..62302f5a56
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getLineOffsets-03.js
@@ -0,0 +1,36 @@
+// getLineOffsets treats one-line compound statements as having only one entry-point.
+// (A breakpoint on a line that only executes once will only hit once.)
+
+var g = newGlobal({newCompartment: true});
+g.line0 = null;
+var dbg = Debugger(g);
+var log;
+dbg.onDebuggerStatement = function (frame) {
+ var s = frame.script;
+ var lineno = g.line0 + 2;
+ var offs = s.getLineOffsets(lineno);
+ for (var i = 0; i < offs.length; i++) {
+ assertEq(s.getOffsetLocation(offs[i]).lineNumber, lineno);
+ s.setBreakpoint(offs[i], {hit: function () { log += 'B'; }});
+ }
+ log += 'A';
+};
+
+function test(s) {
+ log = '';
+ g.eval("line0 = Error().lineNumber;\n" +
+ "debugger;\n" + // line0 + 1
+ s); // line0 + 2
+ assertEq(log, 'AB');
+}
+
+g.i = 0;
+g.j = 0;
+test("{i++; i--; i++; i--; }");
+test("if (i === 0) i++; else i--;");
+test("while (i < 5) i++;");
+test("do --i; while (i > 0);");
+test("for (i = 0; i < 5; i++) j++;");
+test("for (var x in [0, 1, 2]) j++;");
+test("switch (i) { case 0: j = 0; case 1: j = 1; case 2: j = 2; default: j = i; }");
+test("switch (i) { case 'A': j = 0; case 'B': j = 1; case 'C': j = 2; default: j = i; }");
diff --git a/js/src/jit-test/tests/debug/Script-getLineOffsets-04.js b/js/src/jit-test/tests/debug/Script-getLineOffsets-04.js
new file mode 100644
index 0000000000..9e828b8af6
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getLineOffsets-04.js
@@ -0,0 +1,53 @@
+// getLineOffsets works with instructions reachable only by breaking out of a loop or switch.
+
+var g = newGlobal({newCompartment: true});
+g.line0 = null;
+var dbg = Debugger(g);
+var where;
+dbg.onDebuggerStatement = function (frame) {
+ var s = frame.eval("f").return.script;
+ var lineno = g.line0 + where;
+ var offs = s.getLineOffsets(lineno);
+ for (var i = 0; i < offs.length; i++) {
+ assertEq(s.getOffsetLocation(offs[i]).lineNumber, lineno);
+ s.setBreakpoint(offs[i], {hit: function () { g.log += 'B'; }});
+ }
+ g.log += 'A';
+};
+
+function test(s) {
+ var count = (s.split(/\n/).length - 1); // number of newlines in s
+ g.log = '';
+ where = 3 + count + 1;
+ g.eval("line0 = Error().lineNumber;\n" +
+ "debugger;\n" + // line0 + 1
+ "function f(i) {\n" + // line0 + 2
+ s + // line0 + 3 ... line0 + where - 2
+ " log += '?';\n" + // line0 + where - 1
+ " log += '!';\n" + // line0 + where
+ "}\n");
+ g.f(0);
+ assertEq(g.log, 'A?B!');
+}
+
+test("i = 128;\n" +
+ "for (;;) {\n" +
+ " var x = i - 10;;\n" +
+ " if (x < 0)\n" +
+ " break;\n" +
+ " i >>= 2;\n" +
+ "}\n");
+
+test("while (true)\n" +
+ " if (++i === 2) break;\n");
+
+test("do {\n" +
+ " if (++i === 2) break;\n" +
+ "} while (true);\n");
+
+test("switch (i) {\n" +
+ " case 2: return 7;\n" +
+ " case 1: return 8;\n" +
+ " case 0: break;\n" +
+ " default: return -i;\n" +
+ "}\n");
diff --git a/js/src/jit-test/tests/debug/Script-getLineOffsets-05.js b/js/src/jit-test/tests/debug/Script-getLineOffsets-05.js
new file mode 100644
index 0000000000..0a030ad87e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getLineOffsets-05.js
@@ -0,0 +1,65 @@
+// getLineOffsets identifies multiple ways to land on a line.
+
+var g = newGlobal({newCompartment: true});
+g.line0 = null;
+var dbg = Debugger(g);
+var where;
+dbg.onDebuggerStatement = function (frame) {
+ var s = frame.script, lineno, offs;
+
+ lineno = g.line0 + where;
+ offs = s.getLineOffsets(lineno);
+ for (var i = 0; i < offs.length; i++) {
+ assertEq(s.getOffsetLocation(offs[i]).lineNumber, lineno);
+ s.setBreakpoint(offs[i], {hit: function () { g.log += 'B'; }});
+ }
+
+ lineno++;
+ offs = s.getLineOffsets(lineno);
+ for (var i = 0; i < offs.length; i++) {
+ assertEq(s.getOffsetLocation(offs[i]).lineNumber, lineno);
+ s.setBreakpoint(offs[i], {hit: function () { g.log += 'C'; }});
+ }
+
+ g.log += 'A';
+};
+
+function test(s) {
+ assertEq(s.charAt(s.length - 1), '\n');
+ var count = (s.split(/\n/).length - 1); // number of lines in s
+ g.log = '';
+ where = 1 + count;
+ g.eval("line0 = Error().lineNumber;\n" +
+ "debugger;\n" + // line0 + 1
+ s + // line0 + 2 ... line0 + where
+ "log += 'D';\n");
+ assertEq(g.log, 'AB!CD');
+}
+
+// if-statement with yes and no paths on a single line
+g.i = 0;
+test("if (i === 0)\n" +
+ " log += '!'; else log += 'X';\n");
+test("if (i === 2)\n" +
+ " log += 'X'; else log += '!';\n");
+
+// break to a line that has code inside and outside the loop
+g.i = 2;
+test("while (1) {\n" +
+ " if (i === 2) break;\n" +
+ " log += 'X'; } log += '!';\n");
+
+// leaving a while loop by failing the test, when the last line has stuff both inside and outside the loop
+g.i = 0;
+test("while (i > 0) {\n" +
+ " if (i === 70) log += 'X';\n" +
+ " --i; } log += '!';\n");
+
+// multiple case-labels on the same line
+g.i = 0;
+test("switch (i) {\n" +
+ " case 0: case 1: log += '!'; break; }\n");
+test("switch ('' + i) {\n" +
+ " case '0': case '1': log += '!'; break; }\n");
+test("switch (i) {\n" +
+ " case 'ok' + i: case i - i: log += '!'; break; }\n");
diff --git a/js/src/jit-test/tests/debug/Script-getLineOffsets-06.js b/js/src/jit-test/tests/debug/Script-getLineOffsets-06.js
new file mode 100644
index 0000000000..f5fccaed56
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getLineOffsets-06.js
@@ -0,0 +1,99 @@
+// getLineOffsets works with extended instructions, such as JSOP_GOTOX.
+
+var g = newGlobal({newCompartment: true});
+g.line0 = null;
+var dbg = Debugger(g);
+var where;
+dbg.onDebuggerStatement = function (frame) {
+ var s = frame.script;
+ var offs;
+ var lineno = g.line0 + where;
+ offs = s.getLineOffsets(lineno);
+ for (var i = 0; i < offs.length; i++) {
+ assertEq(s.getOffsetLocation(offs[i]).lineNumber, lineno);
+ s.setBreakpoint(offs[i], {hit: function (frame) { g.log += 'B'; }});
+ }
+ g.log += 'A';
+};
+
+function test(s) {
+ assertEq(s.charAt(s.length - 1) !== '\n', true);
+ var count = s.split(/\n/).length; // number of lines in s
+ g.i = 0;
+ g.log = '';
+ where = 1 + count;
+ g.eval("line0 = Error().lineNumber;\n" +
+ "debugger;\n" + // line0 + 1
+ s + // line0 + 2 ... line0 + where
+ " log += 'C';\n");
+ assertEq(g.log, 'ABC');
+}
+
+function repeat(s) {
+ return Array((1 << 14) + 1).join(s); // 16K copies of s
+}
+var long_expr = "i" + repeat(" + i");
+var long_throw_stmt = "throw " + long_expr + ";\n";
+
+// long break (JSOP_GOTOX)
+test("for (;;) {\n" +
+ " if (i === 0)\n" +
+ " break;\n" +
+ " " + long_throw_stmt +
+ "}");
+
+// long continue (JSOP_GOTOX)
+test("do {\n" +
+ " if (i === 0)\n" +
+ " continue;\n" +
+ " " + long_throw_stmt +
+ "} while (i !== 0);");
+
+// long if consequent (JSOP_IFEQX)
+test("if (i === 2) {\n" +
+ " " + long_throw_stmt +
+ "}");
+
+// long catch-block with finally (JSOP_GOSUBX)
+test("try {\n" +
+ " i = 0;\n" +
+ "} catch (exc) {\n" +
+ " throw " + long_expr + ";\n" +
+ "} finally {\n" +
+ " i = 1;\n" +
+ "}");
+
+// long case (JSOP_TABLESWITCHX)
+test("switch (i) {\n" +
+ " default:\n" +
+ " case 1: " + long_throw_stmt +
+ " case 0: i++; }");
+
+test("switch (i) {\n" +
+ " case 1: case 2: case 3: " + long_throw_stmt +
+ " default: i++; }");
+
+// long case (JSOP_LOOKUPSWITCHX)
+test("switch ('' + i) {\n" +
+ " default:\n" +
+ " case '1': " + long_throw_stmt +
+ " case '0': i++; }");
+
+test("switch (i) {\n" +
+ " case '1': case '2': case '3': " + long_throw_stmt +
+ " default: i++; }");
+
+// long case or case-expression (JSOP_CASEX)
+test("switch (i) {\n" +
+ " case i + 1 - i:\n" +
+ " default:\n" +
+ " " + long_throw_stmt +
+ " case i + i:\n" +
+ " i++; break; }");
+
+// long case when JSOP_CASE is used (JSOP_DEFAULTX)
+test("switch (i) {\n" +
+ " case i + 1 - i:\n" +
+ " " + long_throw_stmt +
+ " default:\n" +
+ " i++; break; }");
diff --git a/js/src/jit-test/tests/debug/Script-getLineOffsets-07.js b/js/src/jit-test/tests/debug/Script-getLineOffsets-07.js
new file mode 100644
index 0000000000..5ecf311615
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getLineOffsets-07.js
@@ -0,0 +1,19 @@
+// Lazy scripts should correctly report line offsets
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+
+g.eval("// Header comment\n" + // <- line 6 in this file
+ "\n" +
+ "\n" +
+ "function f(n) {\n" + // <- line 9 in this file
+ " var foo = '!';\n" +
+ "}");
+
+dbg.addDebuggee(g);
+var scripts = dbg.findScripts();
+for (var i = 0; i < scripts.length; i++) {
+ // Nothing should have offsets for the deffun on line 9 if lazy scripts
+ // correctly update the position.
+ assertEq(scripts[i].getLineOffsets(9).length, 0);
+}
diff --git a/js/src/jit-test/tests/debug/Script-getLineOffsets-08.js b/js/src/jit-test/tests/debug/Script-getLineOffsets-08.js
new file mode 100644
index 0000000000..b51b0e09f8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getLineOffsets-08.js
@@ -0,0 +1,25 @@
+// A "while" or a "for" loop should have a single entry point.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+dbg.onDebuggerStatement = function(frame) {
+ var s = frame.eval('f').return.script;
+
+ // There should be just a single entry point for the first line of
+ // the function. See below to understand the "+2".
+ assertEq(s.getLineOffsets(g.line0 + 2).length, 1);
+};
+
+
+function test(code) {
+ g.eval('var line0 = Error().lineNumber;\n' +
+ 'function f() {\n' + // line0 + 1
+ code + '\n' + // line0 + 2 -- see above
+ '}\n' +
+ 'debugger;');
+}
+
+test('while (false)\n;');
+test('for (;false;)\n;');
+test('for (;;) break;\n;');
diff --git a/js/src/jit-test/tests/debug/Script-getOffsetLine-01.js b/js/src/jit-test/tests/debug/Script-getOffsetLine-01.js
new file mode 100644
index 0000000000..65a1f50c53
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getOffsetLine-01.js
@@ -0,0 +1,25 @@
+// Basic getOffsetLocation test, using Error.lineNumber as the gold standard.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var hits;
+dbg.onDebuggerStatement = function (frame) {
+ var knownLine = frame.eval("line").return;
+ assertEq(frame.script.getOffsetLocation(frame.offset).lineNumber, knownLine);
+ hits++;
+};
+
+hits = 0;
+g.eval("var line = new Error().lineNumber; debugger;");
+assertEq(hits, 1);
+
+hits = 0;
+g.eval("var s = 2 + 2;\n" +
+ "s += 2;\n" +
+ "line = new Error().lineNumber; debugger;\n" +
+ "s += 2;\n" +
+ "s += 2;\n" +
+ "line = new Error().lineNumber; debugger;\n" +
+ "s += 2;\n" +
+ "assertEq(s, 12);\n");
+assertEq(hits, 2);
diff --git a/js/src/jit-test/tests/debug/Script-getOffsetLine-02.js b/js/src/jit-test/tests/debug/Script-getOffsetLine-02.js
new file mode 100644
index 0000000000..d118e0e2db
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getOffsetLine-02.js
@@ -0,0 +1,19 @@
+// Frame.script/offset and Script.getOffsetLocation work in non-top frames.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ var a = [];
+ for (; frame.type == "call"; frame = frame.older)
+ a.push(frame.script.getOffsetLocation(frame.offset).lineNumber - g.line0);
+ assertEq(a.join(","), "1,2,3,4");
+ hits++;
+};
+g.eval("var line0 = Error().lineNumber;\n" +
+ "function f0() { debugger; }\n" +
+ "function f1() { f0(); }\n" +
+ "function f2() { f1(); }\n" +
+ "function f3() { f2(); }\n" +
+ "f3();\n");
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Script-getOffsetLocation.js b/js/src/jit-test/tests/debug/Script-getOffsetLocation.js
new file mode 100644
index 0000000000..3df02f6fc1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getOffsetLocation.js
@@ -0,0 +1,37 @@
+// getOffsetLocation agrees with getAllColumnOffsets
+
+var global = newGlobal({newCompartment: true});
+Debugger(global).onDebuggerStatement = function (frame) {
+ var script = frame.script;
+ var byOffset = [];
+ script.getAllColumnOffsets().forEach(function (entry) {
+ var {lineNumber, columnNumber, offset} = entry;
+ byOffset[offset] = {lineNumber, columnNumber};
+ });
+
+ frame.onStep = function() {
+ var offset = frame.offset;
+ var location = script.getOffsetLocation(offset);
+ if (location.isEntryPoint) {
+ assertEq(location.lineNumber, byOffset[offset].lineNumber);
+ assertEq(location.columnNumber, byOffset[offset].columnNumber);
+ } else {
+ assertEq(byOffset[offset], undefined);
+ }
+ };
+};
+
+function test(body) {
+ print("Test: " + body);
+ global.eval(`function f(n) { debugger; ${body} }`);
+ global.f(3);
+}
+
+test("for (var i = 0; i < n; ++i) ;");
+test("var w0,x1=3,y2=4,z3=9");
+test("print(n),print(n),print(n)");
+test("var o={a:1,b:2,c:3}");
+test("var a=[1,2,n]");
+
+global.eval("function ppppp() { return 1; }");
+test("1 && ppppp(ppppp()) && new Error()");
diff --git a/js/src/jit-test/tests/debug/Script-getOffsetMetadata.js b/js/src/jit-test/tests/debug/Script-getOffsetMetadata.js
new file mode 100644
index 0000000000..0f265ff0dc
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getOffsetMetadata.js
@@ -0,0 +1,36 @@
+var global = newGlobal({newCompartment: true});
+var dbg = Debugger(global);
+dbg.onDebuggerStatement = function(frame) {
+ const bps = frame.script.getPossibleBreakpoints();
+
+ const stepBps = [];
+ frame.onStep = function() {
+ assertOffset(this);
+ };
+
+ assertOffset(frame);
+
+ function assertOffset(frame) {
+ const meta = frame.script.getOffsetMetadata(frame.offset);
+
+ if (meta.isBreakpoint) {
+ assertEq(frame.offset, bps[0].offset);
+ const expectedData = bps.shift();
+
+ assertEq(meta.lineNumber, expectedData.lineNumber);
+ assertEq(meta.columnNumber, expectedData.columnNumber);
+ assertEq(meta.isStepStart, expectedData.isStepStart);
+ } else {
+ assertEq(meta.isStepStart, false);
+ }
+ };
+};
+
+global.eval(`
+ function a() { return "str"; }
+ debugger;
+
+ console.log("42" + a());
+ a();
+ a() + a();
+`);
diff --git a/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-01.js b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-01.js
new file mode 100644
index 0000000000..7757d4fbf4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-01.js
@@ -0,0 +1,501 @@
+// Currently the Jit integration has a few issues, let's keep this test
+// case deterministic.
+//
+// - Baseline OSR increments the loop header twice.
+// - Ion is not updating any counter yet.
+//
+if (getJitCompilerOptions()["ion.warmup.trigger"] != 30)
+ setJitCompilerOption("ion.warmup.trigger", 30);
+if (getJitCompilerOptions()["baseline.warmup.trigger"] != 10)
+ setJitCompilerOption("baseline.warmup.trigger", 10);
+
+/*
+ * These test cases are annotated with the output produced by LCOV [1]. Comment
+ * starting with //<key> without any spaces are used as a reference for the code
+ * coverage output. Any "$" in these line comments are replaced by the current
+ * line number, and any "%" are replaced with the current function name (defined
+ * by the FN key).
+ *
+ * [1] http://ltp.sourceforge.net/coverage/lcov/geninfo.1.php
+ */
+function checkGetOffsetsCoverage(fun) {
+ var keys = [ "TN", "SF", "FN", "FNDA", "FNF", "FNH", "BRDA", "BRF", "BRH", "DA", "LF", "LH" ];
+ function startsWithKey(s) {
+ for (k of keys) {
+ if (s.startsWith(k))
+ return true;
+ }
+ return false;
+ };
+
+ // Extract the body of the function, as the code to be executed.
+ var source = fun.toString();
+ source = source.slice(source.indexOf('{') + 1, source.lastIndexOf('}'));
+
+ // Extract comment starting with the previous keys, as a reference.
+ var lcovRef = [];
+ var currLine = 0;
+ var currFun = [{name: "top-level", braces: 1}];
+ for (var line of source.split('\n')) {
+ currLine++;
+
+ for (var comment of line.split("//").slice(1)) {
+ if (!startsWithKey(comment))
+ continue;
+ comment = comment.trim();
+ if (comment.startsWith("FN:"))
+ currFun.push({ name: comment.split(',')[1], braces: 0 });
+ var name = currFun[currFun.length - 1].name;
+ if (!comment.startsWith("DA:"))
+ continue;
+ comment = {
+ offset: null,
+ lineNumber: currLine,
+ columnNumber: null,
+ count: comment.split(",")[1] | 0,
+ script: (name == "top-level" ? undefined : name)
+ };
+ lcovRef.push(comment);
+ }
+
+ var deltaBraces = line.split('{').length - line.split('}').length;
+ currFun[currFun.length - 1].braces += deltaBraces;
+ if (currFun[currFun.length - 1].braces == 0)
+ currFun.pop();
+ }
+
+ // Create a new global and instrument it with a debugger, to find all scripts,
+ // created in the current global.
+ var g = newGlobal({newCompartment: true});
+ var dbg = Debugger(g);
+ dbg.collectCoverageInfo = true;
+
+ var topLevel = null;
+ dbg.onNewScript = function (s) {
+ topLevel = s;
+ dbg.onNewScript = function () {};
+ };
+
+ // Evaluate the code, and collect the hit counts for each scripts / lines.
+ g.eval(source);
+
+ var coverageRes = [];
+ function collectCoverage(s) {
+ var res = s.getOffsetsCoverage();
+ if (res == null)
+ res = [{
+ offset: null,
+ lineNumber: null,
+ columnNumber: null,
+ script: s.displayName,
+ count: 0
+ }];
+ else {
+ res.map(function (e) {
+ e.script = s.displayName;
+ return e;
+ });
+ }
+ coverageRes.push(res);
+ s.getChildScripts().forEach(collectCoverage);
+ };
+ collectCoverage(topLevel);
+ coverageRes = [].concat(...coverageRes);
+
+ // Check that all the lines are present the result.
+ function match(ref) {
+ return function (entry) {
+ return ref.lineNumber == entry.lineNumber && ref.script == entry.script;
+ }
+ }
+ function ppObj(entry) {
+ var str = "{";
+ for (var k in entry) {
+ if (entry[k] != null)
+ str += " '" + k + "': " + entry[k] + ",";
+ }
+ str += "}";
+ return str;
+ }
+ for (ref of lcovRef) {
+ var res = coverageRes.find(match(ref));
+ if (!res) {
+ // getOffsetsCoverage returns null if we have no result for the
+ // script. We added a fake entry with an undefined lineNumber, which is
+ // used to match against the modified reference.
+ var missRef = Object.create(ref);
+ missRef.lineNumber = null;
+ res = coverageRes.find(match(missRef));
+ }
+
+ if (!res || res.count != ref.count) {
+ print("Cannot find `" + ppObj(ref) + "` in the following results:\n", coverageRes.map(ppObj).join("\n"));
+ print("In the following source:\n", source);
+ assertEq(true, false);
+ }
+ }
+}
+
+checkGetOffsetsCoverage(function () { //FN:$,top-level
+ ",".split(','); //DA:$,1
+});
+
+checkGetOffsetsCoverage(function () { //FN:$,top-level
+ function f() { //FN:$,f
+ ",".split(','); //DA:$,0
+ }
+ ",".split(','); //DA:$,1
+});
+
+checkGetOffsetsCoverage(function () { //FN:$,top-level
+ function f() { //FN:$,f
+ ",".split(','); //DA:$,1
+ }
+ f(); //DA:$,1
+});
+
+checkGetOffsetsCoverage(function () { ','.split(','); //FN:$,top-level //DA:$,1
+});
+
+checkGetOffsetsCoverage(function () { function f() { ','.split(','); } //FN:$,top-level //FN:$,f //DA:$,1
+ f(); //DA:$,1
+});
+
+checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,%
+ var l = ",".split(','); //DA:$,1
+ if (l.length == 3) //DA:$,1
+ l.push(''); //DA:$,0
+ l.pop(); //DA:$,1
+});
+
+checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,%
+ var l = ",".split(','); //DA:$,1
+ if (l.length == 2) //DA:$,1
+ l.push(''); //DA:$,1
+ l.pop(); //DA:$,1
+});
+
+checkGetOffsetsCoverage(function () { //FN:$,top-level
+ var l = ",".split(','); //DA:$,1
+ if (l.length == 3) //DA:$,1
+ l.push(''); //DA:$,0
+ else
+ l.pop(); //DA:$,1
+});
+
+checkGetOffsetsCoverage(function () { //FN:$,top-level
+ var l = ",".split(','); //DA:$,1
+ if (l.length == 2) //DA:$,1
+ l.push(''); //DA:$,1
+ else
+ l.pop(); //DA:$,0
+});
+
+checkGetOffsetsCoverage(function () { //FN:$,top-level
+ var l = ",".split(','); //DA:$,1
+ if (l.length == 2) //DA:$,1
+ l.push(''); //DA:$,1
+ else {
+ if (l.length == 1) //DA:$,0
+ l.pop(); //DA:$,0
+ }
+});
+
+checkGetOffsetsCoverage(function () { //FN:$,top-level
+ function f(i) { //FN:$,f
+ var x = 0; //DA:$,2
+ while (i--) { // Currently OSR wrongly count the loop header twice.
+ // So instead of DA:$,12 , we have DA:$,13 .
+ x += i; //DA:$,10
+ x = x / 2; //DA:$,10
+ }
+ return x; //DA:$,2
+ }
+
+ f(5); //DA:$,1
+ f(5); //DA:$,1
+});
+
+checkGetOffsetsCoverage(function () { //FN:$,top-level
+ try { //DA:$,1
+ var l = ",".split(','); //DA:$,1
+ if (l.length == 2) { //DA:$,1
+ l.push(''); //DA:$,1
+ throw l; //DA:$,1
+ }
+ l.pop(); //DA:$,0
+ } catch (x) { //DA:$,1
+ x.pop(); //DA:$,1
+ }
+});
+
+checkGetOffsetsCoverage(function () { //FN:$,top-level
+ var l = ",".split(','); //DA:$,1
+ try { //DA:$,1
+ try { //DA:$,1
+ if (l.length == 2) { //DA:$,1
+ l.push(''); //DA:$,1
+ throw l; //DA:$,1
+ }
+ l.pop(); //DA:$,0
+ } finally { //DA:$,1
+ l.pop(); //DA:$,1
+ }
+ } catch (x) { //DA:$,1
+ }
+});
+
+checkGetOffsetsCoverage(function () { //FN:$,top-level
+ function f() { //FN:$,f
+ throw 1; //DA:$,1
+ f(); //DA:$,0
+ }
+ var l = ",".split(','); //DA:$,1
+ try { //DA:$,1
+ f(); //DA:$,1
+ f(); //DA:$,0
+ } catch (x) { //DA:$,1
+ }
+});
+
+
+// Test TableSwitch opcode
+checkGetOffsetsCoverage(function () { //FN:$,top-level
+ var l = ",".split(','); //DA:$,1
+ switch (l.length) { //DA:$,1
+ case 0:
+ l.push('0'); //DA:$,0
+ break;
+ case 1:
+ l.push('1'); //DA:$,0
+ break;
+ case 2:
+ l.push('2'); //DA:$,1
+ break;
+ case 3:
+ l.push('3'); //DA:$,0
+ break;
+ }
+ l.pop(); //DA:$,1
+});
+
+checkGetOffsetsCoverage(function () { //FN:$,top-level
+ var l = ",".split(','); //DA:$,1
+ switch (l.length) { //DA:$,1
+ case 0:
+ l.push('0'); //DA:$,0
+ case 1:
+ l.push('1'); //DA:$,0
+ case 2:
+ l.push('2'); //DA:$,1
+ case 3:
+ l.push('3'); //DA:$,1
+ }
+ l.pop(); //DA:$,1
+});
+
+checkGetOffsetsCoverage(function () { //FN:$,top-level
+ var l = ",".split(','); //DA:$,1
+ switch (l.length) { //DA:$,1
+ case 5:
+ l.push('5'); //DA:$,0
+ case 4:
+ l.push('4'); //DA:$,0
+ case 3:
+ l.push('3'); //DA:$,0
+ case 2:
+ l.push('2'); //DA:$,1
+ }
+ l.pop(); //DA:$,1
+});
+
+checkGetOffsetsCoverage(function () { //FN:$,top-level
+ var l = ",".split(','); //DA:$,1
+ switch (l.length) { //DA:$,1
+ case 2:
+ l.push('2'); //DA:$,1
+ case 5:
+ l.push('5'); //DA:$,1
+ }
+ l.pop(); //DA:$,1
+});
+
+checkGetOffsetsCoverage(function () { //FN:$,top-level
+ var l = ",".split(','); //DA:$,1
+ switch (l.length) { //DA:$,1
+ case 3:
+ l.push('1'); //DA:$,0
+ case 5:
+ l.push('5'); //DA:$,0
+ }
+ l.pop(); //DA:$,1
+});
+
+// Unfortunately the differences between switch implementations leaks in the
+// code coverage reports.
+checkGetOffsetsCoverage(function () { //FN:$,top-level
+ function f(a) { //FN:$,f
+ return a; //DA:$,2
+ }
+ var l = ",".split(','); //DA:$,1
+ switch (l.length) { //DA:$,1
+ case f(-42): //DA:$,1
+ l.push('1'); //DA:$,0
+ case f(51): //DA:$,1
+ l.push('5'); //DA:$,0
+ }
+ l.pop(); //DA:$,1
+});
+
+
+checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,%
+ var l = ",".split(','); //DA:$,1
+ switch (l.length) { //DA:$,1 //BRDA:$,0,0,0 //BRDA:$,0,1,1 //BRDA:$,0,2,0 //BRDA:$,0,3,0
+ case 0:
+ case 1:
+ l.push('0'); //DA:$,0
+ l.push('1'); //DA:$,0
+ case 2:
+ l.push('2'); //DA:$,1
+ case 3:
+ l.push('3'); //DA:$,1
+ }
+ l.pop(); //DA:$,1
+ //FNF:1
+ //FNH:1
+ //LF:7
+ //LH:5
+ //BRF:4
+ //BRH:1
+});
+
+checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,%
+ var l = ",".split(','); //DA:$,1
+ switch (l.length) { //DA:$,1 //BRDA:$,0,0,0 //BRDA:$,0,1,0 //BRDA:$,0,2,1 //BRDA:$,0,3,0
+ case 0:
+ l.push('0'); //DA:$,0
+ case 1:
+ l.push('1'); //DA:$,0
+ case 2:
+ case 3:
+ l.push('2'); //DA:$,1
+ l.push('3'); //DA:$,1
+ }
+ l.pop(); //DA:$,1
+ //FNF:1
+ //FNH:1
+ //LF:7
+ //LH:5
+ //BRF:4
+ //BRH:1
+});
+
+checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,%
+ var l = ",".split(','); //DA:$,1
+ switch (l.length) { //DA:$,1 //BRDA:$,0,0,0 //BRDA:$,0,1,0 //BRDA:$,0,2,1 //BRDA:$,0,3,0
+ case 0:
+ l.push('0'); //DA:$,0
+ case 1:
+ default:
+ l.push('1'); //DA:$,0
+ case 2:
+ l.push('2'); //DA:$,1
+ case 3:
+ l.push('3'); //DA:$,1
+ }
+ l.pop(); //DA:$,1
+ //FNF:1
+ //FNH:1
+ //LF:7
+ //LH:5
+ //BRF:4
+ //BRH:1
+});
+
+checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,%
+ var l = ",".split(','); //DA:$,1
+ switch (l.length) { //DA:$,1 //BRDA:$,0,0,0 //BRDA:$,0,1,0 //BRDA:$,0,2,1 //BRDA:$,0,3,0
+ case 0:
+ l.push('0'); //DA:$,0
+ case 1:
+ l.push('1'); //DA:$,0
+ default:
+ case 2:
+ l.push('2'); //DA:$,1
+ case 3:
+ l.push('3'); //DA:$,1
+ }
+ l.pop(); //DA:$,1
+ //FNF:1
+ //FNH:1
+ //LF:7
+ //LH:5
+ //BRF:4
+ //BRH:1
+});
+
+checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,%
+ var l = ",".split(','); //DA:$,1
+ switch (l.length) { //DA:$,1 //BRDA:$,0,0,0 //BRDA:$,0,1,0 //BRDA:$,0,2,1 //BRDA:$,0,3,0 //BRDA:$,0,4,0
+ case 0:
+ l.push('0'); //DA:$,0
+ case 1:
+ l.push('1'); //DA:$,0
+ default:
+ l.push('default'); //DA:$,0
+ case 2:
+ l.push('2'); //DA:$,1
+ case 3:
+ l.push('3'); //DA:$,1
+ }
+ l.pop(); //DA:$,1
+ //FNF:1
+ //FNH:1
+ //LF:8
+ //LH:5
+ //BRF:5
+ //BRH:1
+});
+
+checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,%
+ var l = ",".split(','); //DA:$,1
+ switch (l.length) { //DA:$,1 //BRDA:$,0,0,0 //BRDA:$,0,1,0 //BRDA:$,0,2,0 //BRDA:$,0,3,1
+ case 0:
+ l.push('0'); //DA:$,0
+ case 1:
+ l.push('1'); //DA:$,0
+ default:
+ l.push('2'); //DA:$,1
+ case 3:
+ l.push('3'); //DA:$,1
+ }
+ l.pop(); //DA:$,1
+ //FNF:1
+ //FNH:1
+ //LF:7
+ //LH:5
+ //BRF:4
+ //BRH:1
+});
+
+checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,%
+ var l = ','.split(','); //DA:$,1
+ if (l.length === 45) { //DA:$,1 //BRDA:$,0,0,1 //BRDA:$,0,1,0
+ switch (l[0]) { //DA:$,0 //BRDA:$,1,0,- //BRDA:$,1,1,-
+ case ',':
+ l.push('0'); //DA:$,0
+ default:
+ l.push('1'); //DA:$,0
+ }
+ }
+ l.pop(); //DA:$,1
+ //FNF:1
+ //FNH:1
+ //LF:6
+ //LH:3
+ //BRF:4
+ //BRH:1
+});
+
+// If you add a test case here, do the same in
+// jit-test/tests/coverage/simple.js
diff --git a/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-02.js b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-02.js
new file mode 100644
index 0000000000..dc1420e026
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-02.js
@@ -0,0 +1,41 @@
+// |jit-test| --ion-pgo=off; skip-if: isLcovEnabled()
+
+// This script check that when we enable / disable the code coverage collection,
+// then we have different results for the getOffsetsCoverage methods.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var coverageInfo = [];
+var num = 20;
+function loop(i) {
+ var n = 0;
+ for (n = 0; n < i; n++)
+ debugger;
+}
+g.eval(loop.toString());
+
+dbg.onDebuggerStatement = function (f) {
+ // Collect coverage info each time we hit a debugger statement.
+ coverageInfo.push(f.callee.script.getOffsetsCoverage());
+};
+
+coverageInfo = [];
+dbg.collectCoverageInfo = false;
+g.eval("loop(" + num + ");");
+assertEq(coverageInfo.length, num);
+assertEq(coverageInfo[0], null);
+assertEq(coverageInfo[num - 1], null);
+
+coverageInfo = [];
+dbg.collectCoverageInfo = true;
+g.eval("loop(" + num + ");");
+assertEq(coverageInfo.length, num);
+assertEq(!coverageInfo[0], false);
+assertEq(!coverageInfo[num - 1], false);
+
+coverageInfo = [];
+dbg.collectCoverageInfo = false;
+g.eval("loop(" + num + ");");
+assertEq(coverageInfo.length, num);
+assertEq(coverageInfo[0], null);
+assertEq(coverageInfo[num - 1], null);
diff --git a/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-03.js b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-03.js
new file mode 100644
index 0000000000..8bbafb9838
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-03.js
@@ -0,0 +1,21 @@
+// |jit-test| error: Error: can't start debugging: a debuggee script is on the stack
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+function loop(i) {
+ var n = 0;
+ for (n = 0; n < i; n++)
+ debugger;
+}
+g.eval(loop.toString());
+
+var countDown = 20;
+dbg.onDebuggerStatement = function (f) {
+ // Should throw an error.
+ if (countDown > 0 && --countDown == 0) {
+ dbg.collectCoverageInfo = !dbg.collectCoverageInfo;
+ }
+};
+
+dbg.collectCoverageInfo = false;
+g.eval("loop("+ (2 * countDown) +");");
diff --git a/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-04.js b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-04.js
new file mode 100644
index 0000000000..22bff85eab
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-04.js
@@ -0,0 +1,22 @@
+// |jit-test| error: Error: can't start debugging: a debuggee script is on the stack
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+
+function loop(i) {
+ var n = 0;
+ for (n = 0; n < i; n++)
+ debugger;
+}
+g.eval(loop.toString());
+
+var countDown = 20;
+dbg.onDebuggerStatement = function (f) {
+ // Should throw an error.
+ if (countDown > 0 && --countDown == 0) {
+ dbg.collectCoverageInfo = !dbg.collectCoverageInfo;
+ }
+};
+
+dbg.collectCoverageInfo = true;
+g.eval("loop("+ (2 * countDown) +");");
diff --git a/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-05.js b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-05.js
new file mode 100644
index 0000000000..3072fbc00e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-05.js
@@ -0,0 +1,24 @@
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+function f(x) {
+ while (x) {
+ interruptIf(true);
+ x -= 1;
+ }
+}
+g.eval(f.toString());
+
+// Toogle the debugger while the function f is running.
+setInterruptCallback(toogleDebugger);
+function toogleDebugger() {
+ dbg.enabled = !dbg.enabled;
+ return true;
+}
+
+dbg.collectCoverageInfo = false;
+dbg.enabled = false;
+g.eval("f(10);");
+
+dbg.collectCoverageInfo = true;
+dbg.enabled = false;
+g.eval("f(10);");
diff --git a/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-bug1233178.js b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-bug1233178.js
new file mode 100644
index 0000000000..70753c8533
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-bug1233178.js
@@ -0,0 +1,13 @@
+gczeal(2);
+g = newGlobal({newCompartment: true});
+dbg = Debugger(g);
+function loop() {
+ for (var i = 0; i < 10; i++)
+ debugger;
+}
+g.eval(loop.toString());
+dbg.onDebuggerStatement = function(f) {
+ f.script.getOffsetsCoverage();
+}
+dbg.collectCoverageInfo = true;
+g.eval("loop")();
diff --git a/js/src/jit-test/tests/debug/Script-getPossibleBreakpoints-02.js b/js/src/jit-test/tests/debug/Script-getPossibleBreakpoints-02.js
new file mode 100644
index 0000000000..56ea6620d3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getPossibleBreakpoints-02.js
@@ -0,0 +1,83 @@
+
+var global = newGlobal({newCompartment: true});
+var dbg = Debugger(global);
+dbg.onDebuggerStatement = onDebuggerStatement;
+
+global.eval(`
+ debugger;
+ function f() {
+ var o = {}; // 4
+
+ o.a; o.a; o.a; o.a; // 6
+ o.a; o.a; // 7
+ o.a; o.a; o.a; // 8
+ o.a; // 9
+ } // 10
+`);
+
+function onDebuggerStatement(frame) {
+ const fScript = frame.script.getChildScripts()[0];
+
+ const allBreakpoints = fScript.getPossibleBreakpoints();
+ assertEq(allBreakpoints.length, 12);
+
+ assertBPCount({ line: 4 }, 1);
+ assertBPCount({ line: 5 }, 0);
+ assertBPCount({ line: 6 }, 4);
+ assertBPCount({ line: 7 }, 2);
+ assertBPCount({ line: 8 }, 3);
+ assertBPCount({ line: 9 }, 1);
+ assertBPCount({ line: 10 }, 1);
+
+ assertBPCount({ line: 6, minColumn: 7 }, 3);
+ assertBPCount({ line: 6, maxColumn: 16 }, 3);
+ assertBPCount({ line: 6, minColumn: 7, maxColumn: 16 }, 2);
+ assertBPError({ line: 1, minLine: 1 }, "line", "not allowed alongside 'minLine'/'maxLine'");
+ assertBPError({ line: 1, maxLine: 1 }, "line", "not allowed alongside 'minLine'/'maxLine'");
+ assertBPError({ line: "1" }, "line", "not an integer");
+
+ assertBPCount({ minLine: 9 }, 2);
+ assertBPCount({ minLine: 9, minColumn: 0 }, 2);
+ assertBPCount({ minLine: 9, minColumn: 8 }, 1);
+ assertBPError({ minLine: "1" }, "minLine", "not an integer");
+ assertBPError({ minColumn: 1 }, "minColumn", "not allowed without 'line' or 'minLine'");
+ assertBPError({ minLine: 1, minColumn: "1" }, "minColumn", "not an integer");
+
+ assertBPCount({ maxLine: 7 }, 5);
+ assertBPCount({ maxLine: 7, maxColumn: 0 }, 5);
+ assertBPCount({ maxLine: 7, maxColumn: 8 }, 6);
+ assertBPError({ maxLine: "1" }, "maxLine", "not an integer");
+ assertBPError({ maxColumn: 1 }, "maxColumn", "not allowed without 'line' or 'maxLine'");
+ assertBPError({ maxLine: 1, maxColumn: "1" }, "maxColumn", "not an integer");
+
+ assertBPCount({ minLine: 6, maxLine: 8 }, 6);
+ assertBPCount({ minLine: 6, minColumn: 8, maxLine: 8 }, 5);
+ assertBPCount({ minLine: 6, maxLine: 8, maxColumn: 8 }, 7);
+ assertBPCount({ minLine: 6, minColumn: 8, maxLine: 8, maxColumn: 8 }, 6);
+
+ assertBPCount({
+ minOffset: fScript.getPossibleBreakpoints({ line: 6 })[3].offset,
+ }, 8);
+ assertBPError({ minOffset: "1" }, "minOffset", "not an integer");
+ assertBPCount({
+ maxOffset: fScript.getPossibleBreakpoints({ line: 6 })[3].offset,
+ }, 4);
+ assertBPError({ maxOffset: "1" }, "maxOffset", "not an integer");
+ assertBPCount({
+ minOffset: fScript.getPossibleBreakpoints({ line: 6 })[2].offset,
+ maxOffset: fScript.getPossibleBreakpoints({ line: 7 })[1].offset,
+ }, 3);
+
+ function assertBPError(query, field, message) {
+ try {
+ fScript.getPossibleBreakpoints(query);
+ assertEq(false, true);
+ } catch (err) {
+ assertEq(err.message, `getPossibleBreakpoints' '${field}' is ${message}`);
+ }
+ }
+
+ function assertBPCount(query, count) {
+ assertEq(fScript.getPossibleBreakpoints(query).length, count);
+ }
+};
diff --git a/js/src/jit-test/tests/debug/Script-getPossibleBreakpoints.js b/js/src/jit-test/tests/debug/Script-getPossibleBreakpoints.js
new file mode 100644
index 0000000000..5c52f52de3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getPossibleBreakpoints.js
@@ -0,0 +1,384 @@
+// simple ExpressionStatement
+assertBreakpoints(`
+ /*S*/a;
+ /*S*/obj.prop;
+`);
+
+// ExpressionStatement with calls
+assertBreakpoints(`
+ /*S*/a();
+ /*S*/obj./*B*/prop();
+`);
+
+// calls with many args
+assertBreakpoints(`
+ /*S*/a/*B*/(1);
+ /*S*/a/*B*/(1,2);
+ /*S*/a/*B*/(1,2,3);
+`);
+
+
+// ExpressionStatement with nested expression calls.
+assertBreakpoints(`
+ "45";
+ /*S*/"45" + /*B*/a();
+ /*S*/b() + "45";
+
+ /*S*/"45" + o./*B*/a();
+ /*S*/o./*B*/b() + "45";
+ /*S*/"45" + o./*B*/a() + o./*B*/b();
+ /*S*/o./*B*/b() + "45" + o./*B*/a();
+ /*S*/o./*B*/b() + o./*B*/a() + "45";
+`);
+
+// var VariableStatement initializers
+assertBreakpoints(`
+ var foo1 = /*S*/"" + o.a + "" + /*B*/b(),
+ foo2 = /*S*/"45",
+ foo3 = /*S*/"45" + /*B*/a(),
+ foo4 = /*S*/b() + "45",
+ foo5 = /*S*/"45" + /*B*/a() + /*B*/b(),
+ foo6 = /*S*/b() + "45" + /*B*/a(),
+ foo7 = /*S*/b() + /*B*/a() + "45",
+ foo8 = /*S*/"45" + o./*B*/a(),
+ foo9 = /*S*/o./*B*/b() + "45",
+ foo10 = /*S*/"45" + o./*B*/a() + o./*B*/b(),
+ foo11 = /*S*/o./*B*/b() + "45" + o./*B*/a(),
+ foo12 = /*S*/o./*B*/b() + o./*B*/a() + "45";
+`);
+
+// let VariableStatement initializers
+assertBreakpoints(`
+ let foo1 = /*S*/"" + o.a + "" + /*B*/b(),
+ foo2 = /*S*/"45",
+ foo3 = /*S*/"45" + /*B*/a(),
+ foo4 = /*S*/b() + "45",
+ foo5 = /*S*/"45" + /*B*/a() + /*B*/b(),
+ foo6 = /*S*/b() + "45" + /*B*/a(),
+ foo7 = /*S*/b() + /*B*/a() + "45",
+ foo8 = /*S*/"45" + o./*B*/a(),
+ foo9 = /*S*/o./*B*/b() + "45",
+ foo10 = /*S*/"45" + o./*B*/a() + o./*B*/b(),
+ foo11 = /*S*/o./*B*/b() + "45" + o./*B*/a(),
+ foo12 = /*S*/o./*B*/b() + o./*B*/a() + "45";
+`);
+
+// const VariableStatement initializers
+assertBreakpoints(`
+ const foo1 = /*S*/"" + o.a + "" + /*B*/b(),
+ foo2 = /*S*/"45",
+ foo3 = /*S*/"45" + /*B*/a(),
+ foo4 = /*S*/b() + "45",
+ foo5 = /*S*/"45" + /*B*/a() + /*B*/b(),
+ foo6 = /*S*/b() + "45" + /*B*/a(),
+ foo7 = /*S*/b() + /*B*/a() + "45",
+ foo8 = /*S*/"45" + o./*B*/a(),
+ foo9 = /*S*/o./*B*/b() + "45",
+ foo10 = /*S*/"45" + o./*B*/a() + o./*B*/b(),
+ foo11 = /*S*/o./*B*/b() + "45" + o./*B*/a(),
+ foo12 = /*S*/o./*B*/b() + o./*B*/a() + "45";
+`);
+
+// EmptyStatement
+assertBreakpoints(`
+ ;
+ ;
+ ;
+ /*S*/a();
+`);
+
+// IfStatement
+assertBreakpoints(`
+ if (/*S*/a) {}
+ if (/*S*/a()) {}
+ if (/*S*/obj.prop) {}
+ if (/*S*/obj./*B*/prop()) {}
+ if (/*S*/"42" + a) {}
+ if (/*S*/"42" + /*B*/a()) {}
+ if (/*S*/"42" + obj.prop) {}
+ if (/*S*/"42" + obj./*B*/prop()) {}
+`);
+
+// DoWhile
+assertBreakpoints(`
+ do {
+ /*S*/fn();
+ } while(/*S*/a)
+ do {
+ /*S*/fn();
+ } while(/*S*/"42" + /*B*/a());
+`);
+
+// While
+assertBreakpoints(`
+ while(/*S*/a) {
+ /*S*/fn();
+ }
+ while(/*S*/"42" + /*B*/a()) {
+ /*S*/fn();
+ }
+`);
+
+// ForExpr
+assertBreakpoints(`
+ for (/*S*/b = 42; /*S*/c; /*S*/d) /*S*/fn();
+ for (var b = /*S*/42; /*S*/c; /*S*/d) /*S*/fn();
+ for (let b = /*S*/42; /*S*/c; /*S*/d) /*S*/fn();
+ for (const b = /*S*/42; /*S*/c; /*S*/d) /*S*/fn();
+ for (b in /*S*/d) /*S*/fn();
+ for (var b in /*S*/d) /*S*/fn();
+ for (let b in /*S*/d) /*S*/fn();
+ for (const b in /*S*/d) /*S*/fn();
+ for (b of /*S*/d) /*S*/fn();
+ for (var b of /*S*/d) /*S*/fn();
+ for (let b of /*S*/d) /*S*/fn();
+ for (const b of /*S*/d) /*S*/fn();
+`);
+
+// SwitchStatement
+assertBreakpoints(`
+ switch (/*S*/d) {
+ case 42:
+ /*S*/fn();
+ }
+`);
+
+// ContinueStatement
+assertBreakpoints(`
+ while (/*S*/a) {
+ /*S*/continue;
+ }
+`);
+
+// BreakStatement
+assertBreakpoints(`
+ while (/*S*/a) {
+ /*S*/break;
+ }
+`);
+
+// ReturnStatement
+assertBreakpoints(`
+ /*S*/return a + /*B*/b();
+`);
+
+// WithStatement
+assertBreakpoints(`
+ with (/*S*/a) {
+ /*S*/fn();
+ }
+`);
+
+// ThrowStatement
+assertBreakpoints(`
+ /*S*/throw /*B*/fn();
+ /*S*/throw "42" + /*B*/fn();
+`);
+
+// DebuggerStatement
+assertBreakpoints(`
+ /*S*/debugger;
+ /*S*/debugger;
+`);
+
+// BlockStatent wrapper
+assertBreakpoints(`
+ {
+ /*S*/a();
+ }
+`);
+
+// ClassDeclaration
+assertBreakpoints(`
+ class Foo2 {}
+ /*S*/class Foo extends ("" + o.a + /*B*/a() + /*B*/b()) { }
+`);
+
+// Misc examples
+assertBreakpoints(`
+ /*S*/void /*B*/a();
+`);
+assertBreakpoints(`
+ /*S*/a() + /*B*/b();
+`);
+assertBreakpoints(`
+ for (
+ var i = /*S*/0;
+ /*S*/i < n; // 4
+ /*S*/++i
+ ) {
+ /*S*/console./*B*/log("omg");
+ }
+`);
+assertBreakpoints(`
+ function * gen(){
+ var foo = (
+ (/*S*/console./*B*/log('before', /*B*/a())),
+ (yield console./*B*/log('mid', /*B*/b())),
+ (console./*B*/log('after', /*B*/a()))
+ );
+ var foo2 = /*S*/a() + /*B*/b();
+ /*S*/console./*B*/log(foo);
+ /*B*/}
+ var i = /*S*/0;
+ for (var foo of /*S*/gen()) {
+ /*S*/console./*B*/log(i++);
+ }
+`);
+assertBreakpoints(`
+ var fn = /*S*/() => {
+ /*S*/console./*B*/log("fn");
+ /*S*/return /*B*/new Proxy({ prop: 42 }, {
+ deleteProperty() {
+ /*S*/console./*B*/log("delete");
+ /*B*/}
+ });
+ /*B*/};
+`);
+assertBreakpoints(`
+ var fn = /*S*/async (arg) => {
+ /*S*/console./*B*/log("fn");
+ /*B*/};
+`);
+assertBreakpoints(`
+ var fn = /*S*/arg => {
+ /*S*/console./*B*/log("fn");
+ /*B*/};
+`);
+assertBreakpoints(`
+ var fn = /*S*/async arg => {
+ /*S*/console./*B*/log("fn");
+ /*B*/};
+`);
+assertBreakpoints(`
+ var fn = /*S*/(arg) => /*S*/console./*B*/log("fn");
+ var fn = /*S*/async (arg) => /*S*/console./*B*/log("fn");
+ var fn = /*S*/arg => /*S*/console./*B*/log("fn");
+ var fn = /*S*/async arg => /*S*/console./*B*/log("fn");
+`);
+assertBreakpoints(`
+ if ((/*S*/delete /*B*/fn().prop) + /*B*/b()) {
+ /*S*/console./*B*/log("foo");
+ }
+`);
+assertBreakpoints(`
+ for (var j = /*S*/0; (/*S*/o.a) < 3; (/*S*/j++, /*B*/a(), /*B*/b())) {
+ /*S*/console./*B*/log(i);
+ }
+`);
+assertBreakpoints(`
+ function fn2(
+ [a, b] = (/*B*/a(), /*B*/b())
+ ) {
+ /*S*/a();
+ /*S*/b();
+ /*B*/}
+
+ ({ a, b } = (/*S*/a(), /*B*/b()));
+`);
+assertBreakpoints(`
+ /*S*/o.a + "42" + /*B*/a() + /*B*/b();
+`);
+assertBreakpoints(`
+ /*S*/a();
+ /*S*/o./*B*/a(/*B*/b());
+`);
+assertBreakpoints(`
+ (/*S*/{}[obj.a] = 42 + /*B*/a());
+`);
+assertBreakpoints(`
+ var {
+ foo = o.a
+ } = /*S*/{};
+`);
+assertBreakpoints(`
+ var ack = /*S*/[
+ o.a,
+ o.b,
+ /*B*/a(),
+ /*B*/a(),
+ /*B*/a(),
+ /*B*/a(),
+ /*B*/a(),
+ /*B*/a(),
+ /*B*/a(),
+ ];
+`);
+
+function assertBreakpoints(expected) {
+ const input = expected.replace(/\/\*[BS]\*\//g, "");
+
+ var global = newGlobal({ newCompartment: true });
+ var dbg = Debugger(global);
+ dbg.onDebuggerStatement = function(frame) {
+ const fScript = frame.environment.parent.getVariable("f").script;
+
+ let positions = [];
+ (function recurse(script) {
+ const bps = script.getPossibleBreakpoints();
+ const offsets = script.getPossibleBreakpointOffsets();
+
+ assertEq(offsets.length, bps.length);
+ for (let i = 0; i < bps.length; i++) {
+ assertEq(offsets[i], bps[i].offset);
+ }
+
+ positions = positions.concat(bps);
+ script.getChildScripts().forEach(recurse);
+ })(fScript);
+
+ const result = annotateOffsets(input, positions);
+ assertEq(result, expected + "/*B*/");
+ };
+
+ global.eval(`function f(){${input}} debugger;`);
+}
+
+function annotateOffsets(code, positions) {
+ const offsetLookup = createOffsetLookup(code);
+
+ positions = positions.slice();
+ positions.sort((a, b) => {
+ const lineDiff = a.lineNumber - b.lineNumber;
+ return lineDiff === 0 ? a.columnNumber - b.columnNumber : lineDiff;
+ });
+ positions.reverse();
+
+ let output = "";
+ let last = code.length;
+ for (const { lineNumber, columnNumber, isStepStart } of positions) {
+ const offset = offsetLookup(lineNumber, columnNumber);
+
+ output =
+ "/*" +
+ (isStepStart ? "S" : "B") +
+ "*/" +
+ code.slice(offset, last) +
+ output;
+ last = offset;
+ }
+ return code.slice(0, last) + output;
+}
+
+function createOffsetLookup(code) {
+ const lines = code.split(/(\r?\n|\r|\u2028|\u2029)/g);
+ const lineOffsets = [];
+
+ let count = 0;
+ for (const [i, str] of lines.entries()) {
+ if (i % 2 === 0) {
+ lineOffsets[i / 2] = count;
+ }
+ count += str.length;
+ }
+
+ return function(line, column) {
+ // Lines from getAllColumnOffsets are 1-based.
+ line = line - 1;
+
+ if (!lineOffsets.hasOwnProperty(line)) {
+ throw new Error("Unknown line " + line + " column " + column);
+ }
+ return lineOffsets[line] + column;
+ };
+}
diff --git a/js/src/jit-test/tests/debug/Script-global-01.js b/js/src/jit-test/tests/debug/Script-global-01.js
new file mode 100644
index 0000000000..fb2d131e35
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-global-01.js
@@ -0,0 +1,18 @@
+// Debugger.Script.prototype.script returns the global the script runs in.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+
+var log = '';
+dbg.onDebuggerStatement = function (frame) {
+ log += 'd';
+ assertEq(frame.script.global, gw);
+}
+
+g.eval('debugger;');
+assertEq(log, 'd');
+
+g.eval('function f() { debugger; }');
+g.f();
+assertEq(log, 'dd');
diff --git a/js/src/jit-test/tests/debug/Script-global-02.js b/js/src/jit-test/tests/debug/Script-global-02.js
new file mode 100644
index 0000000000..9d5e1fda32
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-global-02.js
@@ -0,0 +1,36 @@
+// Debugger.Script.prototype.script returns the global the script runs in.
+// Multi-global version.
+
+var dbg = new Debugger;
+
+var g1 = newGlobal({newCompartment: true});
+var g1w = dbg.addDebuggee(g1);
+
+var g2 = newGlobal({newCompartment: true});
+var g2w = dbg.addDebuggee(g2);
+
+var g3 = newGlobal({newCompartment: true});
+var g3w = dbg.addDebuggee(g3);
+
+var log = '';
+dbg.onDebuggerStatement = function (frame) {
+ log += 'd';
+ assertEq(frame.script.global, g1w);
+ assertEq(frame.older.script.global, g2w);
+ assertEq(frame.older.older.script.global, g3w);
+ assertEq(frame.older.older.older.script.global, g1w);
+}
+
+g1.eval('function f() { debugger; }');
+
+g2.g1 = g1;
+g2.eval('function g() { g1.f(); }');
+
+g3.g2 = g2;
+g3.eval('function h() { g2.g(); }');
+
+g1.g3 = g3;
+g1.eval('function i() { g3.h(); }');
+
+g1.i();
+assertEq(log, 'd');
diff --git a/js/src/jit-test/tests/debug/Script-isFunction.js b/js/src/jit-test/tests/debug/Script-isFunction.js
new file mode 100644
index 0000000000..5f68d54b19
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-isFunction.js
@@ -0,0 +1,25 @@
+// Script.isFunction should do what it is supposed to do.
+
+const g = newGlobal({newCompartment: true});
+const dbg = new Debugger;
+dbg.addDebuggee(g);
+
+let gNumFunctionScripts = 0;
+function countFunctionScripts(script) {
+ if (script.isFunction) {
+ gNumFunctionScripts++;
+ }
+ return script.getChildScripts().forEach(countFunctionScripts);
+}
+
+dbg.onNewScript = countFunctionScripts;
+
+g.eval(`
+function f() {
+ function f2() {}
+}
+async function g() {}
+function* h() {}
+`);
+
+assertEq(gNumFunctionScripts, 4);
diff --git a/js/src/jit-test/tests/debug/Script-isInCatchScope.js b/js/src/jit-test/tests/debug/Script-isInCatchScope.js
new file mode 100644
index 0000000000..c5d3cfe12e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-isInCatchScope.js
@@ -0,0 +1,68 @@
+// Test if isInCatchScope properly detects catch blocks.
+
+let g = newGlobal({newCompartment: true});
+let dbg = new Debugger(g);
+
+function test(string, mustBeCaught) {
+ let index = 0;
+ dbg.onExceptionUnwind = function (frame) {
+ let willBeCaught = false;
+ do {
+ if (frame.script.isInCatchScope(frame.offset)) {
+ willBeCaught = true;
+ break;
+ }
+ frame = frame.older;
+ } while (frame != null);
+ assertEq(willBeCaught, mustBeCaught[index++]);
+ };
+
+ try {
+ g.eval(string);
+ } catch (ex) {}
+ assertEq(index, mustBeCaught.length);
+}
+
+// Should correctly detect catch blocks
+test("throw new Error();", [false]);
+test("try { throw new Error(); } catch (e) {}", [true]);
+test("try { throw new Error(); } finally {}", [false, false]);
+test("try { throw new Error(); } catch (e) {} finally {}", [true]);
+
+// Source of the exception shouldn't matter
+test("(null)();", [false]);
+test("try { (null)(); } catch (e) {}", [true]);
+test("try { (null)(); } finally {}", [false, false]);
+test("try { (null)(); } catch (e) {} finally {}", [true]);
+
+// Should correctly detect catch blocks in functions
+test("function f() { throw new Error(); } f();", [false, false]);
+test("function f() { try { throw new Error(); } catch (e) {} } f();", [true]);
+test("function f() { try { throw new Error(); } finally {} } f();", [false, false, false]);
+test("function f() { try { throw new Error(); } catch (e) {} finally {} } f();", [true]);
+
+// Should correctly detect catch blocks in evals
+test("eval('throw new Error();')", [false, false]);
+test("eval('try { throw new Error(); } catch (e) {}');", [true]);
+test("eval('try { throw new Error(); } finally {}');", [false, false, false]);
+test("eval('try { throw new Error(); } catch (e) {} finally {}');", [true]);
+
+// Should correctly detect rethrows
+test("try { throw new Error(); } catch (e) { throw e; }", [true, false]);
+test("try { try { throw new Error(); } catch (e) { throw e; } } catch (e) {}", [true, true]);
+test("try { try { throw new Error(); } finally {} } catch (e) {}", [true, true]);
+test("function f() { try { throw new Error(); } catch (e) { throw e; } } f();", [true, false, false]);
+test("function f() { try { try { throw new Error(); } catch (e) { throw e; } } catch (e) {} } f();", [true, true]);
+test("function f() { try { try { throw new Error(); } finally {} } catch (e) {} } f();", [true, true]);
+test("eval('try { throw new Error(); } catch (e) { throw e; }')", [true, false, false]);
+test("eval('try { try { throw new Error(); } catch (e) { throw e; } } catch (e) {}')", [true, true]);
+
+// Should correctly detect catch blocks across frame boundaries
+test("function f() { throw new Error(); } try { f(); } catch (e) {}", [true, true]);
+test("function f() { throw new Error(); } try { f(); } catch (e) { throw e; }", [true, true, false]);
+test("try { eval('throw new Error()'); } catch (e) {}", [true, true]);
+test("try { eval('throw new Error()'); } catch (e) { throw e; }", [true, true, false]);
+
+// Should correctly detect catch blocks just before and just after throws
+test("throw new Error; try {} catch (e) {}", [false]);
+test("try {} catch (e) {} throw new Error();", [false]);
diff --git a/js/src/jit-test/tests/debug/Script-isModule-01.js b/js/src/jit-test/tests/debug/Script-isModule-01.js
new file mode 100644
index 0000000000..36e1c9301e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-isModule-01.js
@@ -0,0 +1,13 @@
+// Debugger.Object.prototype.isModule
+
+const g = newGlobal({newCompartment: true});
+const dbg = Debugger(g);
+let count = 0;
+dbg.onNewScript = function (script) {
+ count += 1;
+ assertEq(script.isModule, true);
+};
+const m = g.parseModule("");
+m.declarationInstantiation();
+m.evaluation();
+assertEq(count, 1);
diff --git a/js/src/jit-test/tests/debug/Script-isModule-02.js b/js/src/jit-test/tests/debug/Script-isModule-02.js
new file mode 100644
index 0000000000..c469f11ec8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-isModule-02.js
@@ -0,0 +1,11 @@
+// Debugger.Object.prototype.isModule
+
+const g = newGlobal({newCompartment: true});
+const dbg = Debugger(g);
+let count = 0;
+dbg.onNewScript = function (script) {
+ count += 1;
+ assertEq(script.isModule, false);
+};
+const m = g.eval("");
+assertEq(count, 1);
diff --git a/js/src/jit-test/tests/debug/Script-isModule-03.js b/js/src/jit-test/tests/debug/Script-isModule-03.js
new file mode 100644
index 0000000000..0b13718dcf
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-isModule-03.js
@@ -0,0 +1,18 @@
+// Debugger.Object.prototype.isModule
+
+const g = newGlobal({newCompartment: true});
+const dbg = Debugger(g);
+let count = 0;
+dbg.onNewScript = function (script) {
+ count += 1;
+ assertEq(script.isModule, true);
+
+ dbg.onNewScript = function (script) {
+ count += 1;
+ assertEq(script.isModule, false);
+ };
+};
+const m = g.parseModule("eval('')");
+m.declarationInstantiation();
+m.evaluation();
+assertEq(count, 2);
diff --git a/js/src/jit-test/tests/debug/Script-isModule-04.js b/js/src/jit-test/tests/debug/Script-isModule-04.js
new file mode 100644
index 0000000000..8d22ca680a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-isModule-04.js
@@ -0,0 +1,19 @@
+// Debugger.Object.prototype.isModule
+
+const g = newGlobal({newCompartment: true});
+const dbg = Debugger(g);
+let count = 0;
+dbg.onNewScript = function (script) {
+ count += 1;
+ assertEq(script.isModule, true);
+};
+dbg.onDebuggerStatement = function (frame) {
+ const { script } = frame;
+
+ assertEq(script.isModule, false);
+};
+const m = g.parseModule("(function(){ debugger; })()");
+m.declarationInstantiation();
+m.evaluation();
+
+assertEq(count, 1);
diff --git a/js/src/jit-test/tests/debug/Script-lineCount.js b/js/src/jit-test/tests/debug/Script-lineCount.js
new file mode 100644
index 0000000000..1eeb28013d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-lineCount.js
@@ -0,0 +1,23 @@
+// Test Script.lineCount.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+
+function test(scriptText, expectedLineCount) {
+ let found = false;
+
+ dbg.onNewScript = function(script, global) {
+ assertEq(script.lineCount, expectedLineCount);
+ found = true;
+ };
+
+ g.evaluate(scriptText);
+ assertEq(found, true);
+}
+
+src = 'var a = (function(){\n' + // 0
+ 'var b = 9;\n' + // 1
+ 'console.log("x", b);\n'+ // 2
+ 'return b;\n' + // 3
+ '})();'; // 4
+test(src, 5);
diff --git a/js/src/jit-test/tests/debug/Script-mainOffset-01.js b/js/src/jit-test/tests/debug/Script-mainOffset-01.js
new file mode 100644
index 0000000000..ae958a523b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-mainOffset-01.js
@@ -0,0 +1,20 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
+// The main offset of a script should be hit before it performs any actions.
+
+var g = newGlobal({newCompartment: true});
+g.eval("var n = 0; function foo() { n = 1; }");
+var dbg = Debugger(g);
+
+var hits = 0;
+function breakpointHit(frame) {
+ hits++;
+ assertEq(frame.eval("n").return, 0);
+}
+
+dbg.onDebuggerStatement = function (frame) {
+ var script = frame.eval("foo").return.script;
+ script.setBreakpoint(script.mainOffset, { hit: breakpointHit });
+};
+g.eval("debugger; foo()");
+assertEq(g.eval("n"), 1);
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/Script-parameterNames.js b/js/src/jit-test/tests/debug/Script-parameterNames.js
new file mode 100644
index 0000000000..1557a97e22
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-parameterNames.js
@@ -0,0 +1,36 @@
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gDO = dbg.addDebuggee(g);
+
+function check(expr, expected) {
+ let completion = gDO.executeInGlobal(expr);
+ if (completion.throw)
+ throw completion.throw.unsafeDereference();
+
+ let fn = completion.return;
+ if (expected === undefined)
+ assertEq(fn.script.parameterNames, undefined);
+ else
+ assertDeepEq(fn.script.parameterNames, expected);
+}
+
+check('(function () {})', []);
+check('(function (x) {})', ["x"]);
+check('(function (x = 1) {})', ["x"]);
+check('(function (a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z) {})',
+ ["a","b","c","d","e","f","g","h","i","j","k","l","m",
+ "n","o","p","q","r","s","t","u","v","w","x","y","z"]);
+check('(function (a, [b, c], {d, e:f}) { })',
+ ["a", undefined, undefined]);
+check('(async function (a, b, c) {})', ["a", "b", "c"]);
+check('(async function* (d, e, f) {})', ["d", "e", "f"]);
+
+// Non-function scripts have |undefined| parameterNames.
+var log = [];
+dbg.onEnterFrame = function(frame) {
+ log.push(frame.script.parameterNames);
+};
+g.evaluate("function foo(a, b) { return eval('1'); }; foo();");
+assertDeepEq(log, [undefined, ["a", "b"], undefined]); // global, function, eval
diff --git a/js/src/jit-test/tests/debug/Script-selfhosted-builtins.js b/js/src/jit-test/tests/debug/Script-selfhosted-builtins.js
new file mode 100644
index 0000000000..ff981f5400
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-selfhosted-builtins.js
@@ -0,0 +1,15 @@
+// The script of self-hosted builtins is not exposed to the debugger and
+// instead is reported as |undefined| just like native builtins.
+
+let g = newGlobal({newCompartment: true});
+
+let dbg = new Debugger();
+let gw = dbg.addDebuggee(g);
+
+// Array is a known native builtin function.
+let nativeBuiltin = gw.makeDebuggeeValue(g.Array);
+assertEq(nativeBuiltin.script, undefined);
+
+// Array.prototype[@@iterator] is a known self-hosted builtin function.
+let selfhostedBuiltin = gw.makeDebuggeeValue(g.Array.prototype[Symbol.iterator]);
+assertEq(selfhostedBuiltin.script, undefined);
diff --git a/js/src/jit-test/tests/debug/Script-source-01.js b/js/src/jit-test/tests/debug/Script-source-01.js
new file mode 100644
index 0000000000..d50d53e583
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-source-01.js
@@ -0,0 +1,26 @@
+/*
+ * Script.prototype.source should be an object. Moreover, it should be the
+ * same object for each child script within the same debugger.
+ */
+let g = newGlobal({newCompartment: true});
+let dbg = new Debugger(g);
+
+let count = 0;
+dbg.onNewScript = function (script) {
+ assertEq(typeof script.source, "object");
+ function traverse(script) {
+ ++count;
+ script.getChildScripts().forEach(function (child) {
+ assertEq(child.source, script.source);
+ traverse(child);
+ });
+ }
+ traverse(script);
+}
+
+g.eval("2 * 3");
+g.eval("function f() {}");
+g.eval("function f() { function g() {} }");
+g.eval("eval('2 * 3')");
+g.eval("new Function('2 * 3')");
+assertEq(count, 10);
diff --git a/js/src/jit-test/tests/debug/Script-source-02.js b/js/src/jit-test/tests/debug/Script-source-02.js
new file mode 100644
index 0000000000..43bb375d22
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-source-02.js
@@ -0,0 +1,16 @@
+/*
+ * Script.prototype.source should be the same object for both the top-level
+ * script and the script of functions accessed as debuggee values on the global
+ */
+let g = newGlobal({newCompartment: true});
+let dbg = new Debugger();
+let gw = dbg.addDebuggee(g);
+
+let count = 0;
+dbg.onDebuggerStatement = function (frame) {
+ ++count;
+ assertEq(frame.script.source, gw.makeDebuggeeValue(g.f).script.source);
+}
+
+g.eval("function f() {}; debugger;");
+assertEq(count, 1);
diff --git a/js/src/jit-test/tests/debug/Script-source-03.js b/js/src/jit-test/tests/debug/Script-source-03.js
new file mode 100644
index 0000000000..ed42967d7c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-source-03.js
@@ -0,0 +1,22 @@
+/*
+ * Script.prototype.source should be a different object for the same script
+ * within different debuggers.
+ */
+let g = newGlobal({newCompartment: true});
+let dbg1 = new Debugger(g);
+let dbg2 = new Debugger(g);
+
+var count = 0;
+var source;
+function test(script) {
+ ++count;
+ if (!source)
+ source = script.source;
+ else
+ assertEq(script.source != source, true);
+};
+dbg1.onNewScript = test;
+dbg2.onNewScript = test;
+
+g.eval("2 * 3");
+assertEq(count, 2);
diff --git a/js/src/jit-test/tests/debug/Script-sourceStart-01.js b/js/src/jit-test/tests/debug/Script-sourceStart-01.js
new file mode 100644
index 0000000000..d5399a2b7d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-sourceStart-01.js
@@ -0,0 +1,22 @@
+/*
+ * Script.prototype.sourceStart and Script.prototype.sourceLength should both be
+ * a number.
+ */
+let g = newGlobal({newCompartment: true});
+let dbg = new Debugger(g);
+
+var count = 0;
+function test(string, range) {
+ dbg.onNewScript = function (script) {
+ ++count;
+ assertEq(script.sourceStart, range[0]);
+ assertEq(script.sourceLength, range[1]);
+ };
+
+ g.eval(string);
+};
+
+test("", [0, 0]);
+test("2 * 3", [0, 5]);
+test("2\n*\n3", [0, 5]);
+assertEq(count, 3);
diff --git a/js/src/jit-test/tests/debug/Script-sourceStart-02.js b/js/src/jit-test/tests/debug/Script-sourceStart-02.js
new file mode 100644
index 0000000000..784388b1ce
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-sourceStart-02.js
@@ -0,0 +1,32 @@
+/*
+ * For function statements, Script.prototype.sourceStart and
+ * Script.prototype.sourceLength should comprise both the opening '(' and the
+ * closing '}'.
+ */
+let g = newGlobal({newCompartment: true});
+let dbg = new Debugger(g);
+
+function test(string, ranges) {
+ var index = 0;
+ dbg.onNewScript = function (script) {
+ function traverse(script) {
+ script.getChildScripts().forEach(function (script) {
+ assertEq(script.sourceStart, ranges[index][0]);
+ assertEq(script.sourceLength, ranges[index][1]);
+ ++index;
+ traverse(script);
+ });
+ }
+ traverse(script);
+ };
+
+ g.eval(string);
+ assertEq(index, ranges.length);
+};
+
+test("function f() {}", [[10, 5]]);
+test("function f() { function g() {} }", [[10, 22], [25, 5]]);
+test("function f() { function g() { function h() {} } }", [[10, 39], [25, 22], [40, 5]]);
+test("function f() { if (true) function g() {} }", [[10, 32], [35, 5]]);
+test("var o = { get p () {} }", [[16, 5]]);
+test("var o = { set p (x) {} }", [[16, 6]]);
diff --git a/js/src/jit-test/tests/debug/Script-sourceStart-03.js b/js/src/jit-test/tests/debug/Script-sourceStart-03.js
new file mode 100644
index 0000000000..073c31f2db
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-sourceStart-03.js
@@ -0,0 +1,35 @@
+/*
+ * For arrow functions, Script.prototype.sourceStart and
+ * Script.prototype.sourceLength should comprise the entire function expression
+ * (including arguments)
+ */
+let g = newGlobal({newCompartment: true});
+let dbg = new Debugger(g);
+
+function test(string, ranges) {
+ var index = 0;
+ dbg.onNewScript = function (script) {
+ function traverse(script) {
+ script.getChildScripts().forEach(function (script) {
+ assertEq(script.sourceStart, ranges[index][0]);
+ assertEq(script.sourceLength, ranges[index][1]);
+ ++index;
+ traverse(script);
+ });
+ }
+ traverse(script);
+ };
+
+ g.eval(string);
+
+ /*
+ * In some configurations certain child scripts are listed twice, so we
+ * cannot rely on index always having the exact same value
+ */
+ assertEq(0 < index && index <= ranges.length, true);
+};
+
+test("() => {}", [[0, 8]]);
+test("(x, y) => { x * y }", [[0, 19]]);
+test("x => x * x", [[0, 10]]);
+test("x => x => x * x", [[0, 15], [5, 10], [5, 10]]);
diff --git a/js/src/jit-test/tests/debug/Script-sourceStart-04.js b/js/src/jit-test/tests/debug/Script-sourceStart-04.js
new file mode 100644
index 0000000000..de03a2f582
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-sourceStart-04.js
@@ -0,0 +1,25 @@
+/*
+ * For eval and Function constructors, Script.prototype.sourceStart and
+ * Script.prototype.sourceLength should comprise the entire script (excluding
+ * arguments in the case of Function constructors)
+ */
+let g = newGlobal({newCompartment: true});
+let dbg = new Debugger(g);
+
+var count = 0;
+function test(string, range) {
+ dbg.onNewScript = function (script) {
+ ++count;
+ if (count % 2 == 0) {
+ assertEq(script.sourceStart, range[0]);
+ assertEq(script.sourceLength, range[1]);
+ }
+ }
+
+ g.eval(string);
+}
+
+test("eval('2 * 3')", [0, 5]);
+test("new Function('2 * 3')", [0, 31]);
+test("new Function('x', 'x * x')", [0, 32]);
+assertEq(count, 6);
diff --git a/js/src/jit-test/tests/debug/Script-startColumn.js b/js/src/jit-test/tests/debug/Script-startColumn.js
new file mode 100644
index 0000000000..5f8f0c5d4e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-startColumn.js
@@ -0,0 +1,99 @@
+// Script.prototype.startColumn returns the correct column for all scripts.
+
+const g = newGlobal({newCompartment: true, useWindowProxy: true});
+const dbg = Debugger(g);
+const gw = dbg.addDebuggee(g);
+
+function test(f, expected) {
+ const fw = gw.makeDebuggeeValue(f);
+ assertEq(fw.callable, true);
+ assertEq(fw.script.startColumn, expected);
+}
+
+g.eval(`
+function f1() { }
+`);
+test(g.f1, 11);
+
+g.eval(`
+var f2 = function({ a, b, c }, d, e, ...more) { };
+`);
+test(g.f2, 17);
+
+g.eval(`
+var f3 = function *() { };
+`);
+test(g.f3, 19);
+
+g.eval(`
+var f4 = async function
+ () { };
+`);
+test(g.f4, 2);
+
+g.eval(`
+var f5 = (a, b) => a + b;
+`);
+test(g.f5, 9);
+
+g.eval(`
+var f6 = a => a + 1;
+`);
+test(g.f6, 9);
+
+g.eval(`
+var MyClass = class {
+ method() { }
+};
+var myInstance = new MyClass();
+`);
+test(g.myInstance.method, 10);
+test(g.myInstance.constructor, 14);
+
+const gEager = newGlobal({newCompartment: true, useWindowProxy: true, disableLazyParsing: true});
+const eagerDbg = Debugger(gEager);
+const gEagerWrapped = eagerDbg.addDebuggee(gEager);
+gEager.eval(`
+function f7() { }
+`);
+const f7w = gEagerWrapped.makeDebuggeeValue(gEager.f7);
+assertEq(f7w.callable, true);
+assertEq(f7w.script.startColumn, 11);
+
+g.eval(`
+function f8() {
+ return function f8Inner() { }
+}
+`);
+test(g.f8, 11);
+test(g.f8(), 27);
+
+g.eval(`
+var f9 = new Function(\"\");
+`);
+test(g.f9, 0);
+
+let hit = 0;
+let column;
+dbg.onDebuggerStatement = function (frame) {
+ column = frame.script.startColumn;
+ hit += 1;
+};
+
+g.eval(` debugger;`);
+assertEq(column, 0);
+assertEq(hit, 1);
+
+const location = { fileName: "column.js", lineNumber: 1, columnNumber: 1 };
+hit = 0;
+g.evaluate(` debugger;`, location);
+assertEq(column, 1);
+assertEq(hit, 1);
+
+g.evaluate(`var f10 = function () { };`, location);
+test(g.f10, 20);
+
+g.evaluate(`
+var f11 = function () { };
+`, location);
+test(g.f11, 19);
diff --git a/js/src/jit-test/tests/debug/Script-startLine.js b/js/src/jit-test/tests/debug/Script-startLine.js
new file mode 100644
index 0000000000..baf27427e1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-startLine.js
@@ -0,0 +1,63 @@
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var start, count;
+dbg.onDebuggerStatement = function (frame) {
+ assertEq(start, undefined);
+ start = frame.script.startLine;
+ count = frame.script.lineCount;
+ assertEq(typeof frame.script.url, 'string');
+};
+
+function test(f, manualCount) {
+ start = count = g.first = g.last = undefined;
+ f();
+ if (manualCount)
+ g.last = g.first + manualCount - 1;
+ assertEq(start, g.first);
+ assertEq(count, g.last + 1 - g.first);
+ print(start, count);
+}
+
+test(function () {
+ g.eval("first = Error().lineNumber;\n" +
+ "debugger;\n" +
+ "last = Error().lineNumber;");
+});
+
+test(function () {
+ g.evaluate("first = Error().lineNumber;\n" +
+ "debugger;\n" +
+ Array(17000).join("\n") +
+ "last = Error().lineNumber;");
+});
+
+test(function () {
+ g.eval("function f1() { first = Error().lineNumber\n" +
+ " debugger;\n" +
+ " last = Error().lineNumber; }\n" +
+ "f1();");
+});
+
+g.eval("function f2() {\n" +
+ " eval('first = Error().lineNumber\\n\\ndebugger;\\n\\nlast = Error().lineNumber;');\n" +
+ "}\n");
+test(g.f2);
+test(g.f2);
+
+// Having a last = Error().lineNumber forces a setline srcnote, so test a
+// function that ends with newline srcnotes.
+g.eval("/* Any copyright is dedicated to the Public Domain.\n" +
+ " http://creativecommons.org/publicdomain/zero/1.0/ */\n" +
+ "\n" +
+ "function secondCall() { first = Error().lineNumber;\n" +
+ " debugger;\n" +
+ " // Comment\n" +
+ " eval(\"42;\");\n" +
+ " function foo() {}\n" +
+ " if (true) {\n" +
+ " foo();\n" +
+ // The "missing" newline here is a trick to make a newline
+ // source note come at the end. A real newline between the two
+ // closing braces causes a setline note instead.
+ " } }");
+test(g.secondCall, 8);
diff --git a/js/src/jit-test/tests/debug/Script-url.js b/js/src/jit-test/tests/debug/Script-url.js
new file mode 100644
index 0000000000..0bfe398963
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-url.js
@@ -0,0 +1,10 @@
+// Script.prototype.url can be a string or null.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+for (var fileName of ['file:///var/foo.js', null]) {
+ g.evaluate("function f(x) { return 2*x; }", {fileName: fileName});
+ var fw = gw.getOwnPropertyDescriptor('f').value;
+ assertEq(fw.script.url, fileName);
+}
diff --git a/js/src/jit-test/tests/debug/Source-displayURL-deprecated.js b/js/src/jit-test/tests/debug/Source-displayURL-deprecated.js
new file mode 100644
index 0000000000..efa92eddd8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-displayURL-deprecated.js
@@ -0,0 +1,26 @@
+/* -*- js-indent-level: 4; indent-tabs-mode: nil -*- */
+// Source.prototype.displayURL can be a string or null.
+
+let g = newGlobal({newCompartment: true});
+let dbg = new Debugger;
+let gw = dbg.addDebuggee(g);
+
+function getDisplayURL() {
+ let fw = gw.makeDebuggeeValue(g.f);
+ return fw.script.source.displayURL;
+}
+
+// Comment pragmas
+g.evaluate('function f() {}\n' +
+ '//@ sourceURL=file:///var/quux.js');
+assertEq(getDisplayURL(), 'file:///var/quux.js');
+
+g.evaluate('function f() {}\n' +
+ '/*//@ sourceURL=file:///var/quux.js*/');
+assertEq(getDisplayURL(), 'file:///var/quux.js');
+
+g.evaluate('function f() {}\n' +
+ '/*\n' +
+ '//@ sourceURL=file:///var/quux.js\n' +
+ '*/');
+assertEq(getDisplayURL(), 'file:///var/quux.js');
diff --git a/js/src/jit-test/tests/debug/Source-displayURL-disable.js b/js/src/jit-test/tests/debug/Source-displayURL-disable.js
new file mode 100644
index 0000000000..3ac2dd3fff
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-displayURL-disable.js
@@ -0,0 +1,14 @@
+// |jit-test| --no-source-pragmas
+
+const g = newGlobal({ newCompartment: true });
+const dbg = Debugger(g);
+let source;
+dbg.onDebuggerStatement = function (frame) {
+ source = frame.script.source;
+};
+
+g.eval(`
+ debugger;
+ //# sourceURL=file.js
+`);
+assertEq(source.displayURL, null);
diff --git a/js/src/jit-test/tests/debug/Source-displayURL.js b/js/src/jit-test/tests/debug/Source-displayURL.js
new file mode 100644
index 0000000000..c182631da0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-displayURL.js
@@ -0,0 +1,91 @@
+/* -*- js-indent-level: 4; indent-tabs-mode: nil -*- */
+// Source.prototype.displayURL can be a string or null.
+
+let g = newGlobal({newCompartment: true});
+let dbg = new Debugger;
+let gw = dbg.addDebuggee(g);
+
+function getDisplayURL() {
+ let fw = gw.makeDebuggeeValue(g.f);
+ return fw.script.source.displayURL;
+}
+
+// Without a source url
+g.evaluate("function f(x) { return 2*x; }");
+assertEq(getDisplayURL(), null);
+
+// With a source url
+g.evaluate("function f(x) { return 2*x; }", {displayURL: 'file:///var/foo.js'});
+assertEq(getDisplayURL(), 'file:///var/foo.js');
+
+// Nested functions
+let fired = false;
+dbg.onDebuggerStatement = function (frame) {
+ fired = true;
+ assertEq(frame.script.source.displayURL, 'file:///var/bar.js');
+};
+g.evaluate('(function () { (function () { debugger; })(); })();',
+ {displayURL: 'file:///var/bar.js'});
+assertEq(fired, true);
+
+// Comment pragmas
+g.evaluate('function f() {}\n' +
+ '//# sourceURL=file:///var/quux.js');
+assertEq(getDisplayURL(), 'file:///var/quux.js');
+
+g.evaluate('function f() {}\n' +
+ '/*//# sourceURL=file:///var/quux.js*/');
+assertEq(getDisplayURL(), 'file:///var/quux.js');
+
+g.evaluate('function f() {}\n' +
+ '/*\n' +
+ '//# sourceURL=file:///var/quux.js\n' +
+ '*/');
+assertEq(getDisplayURL(), 'file:///var/quux.js');
+
+// Spaces are disallowed by the URL spec (they should have been
+// percent-encoded).
+g.evaluate('function f() {}\n' +
+ '//# sourceURL=http://example.com/has illegal spaces');
+assertEq(getDisplayURL(), 'http://example.com/has');
+
+// When the URL is missing, we don't set the sourceMapURL and we don't skip the
+// next line of input.
+g.evaluate('function f() {}\n' +
+ '//# sourceURL=\n' +
+ 'function z() {}');
+assertEq(getDisplayURL(), null);
+assertEq('z' in g, true);
+
+// The last comment pragma we see should be the one which sets the displayURL.
+g.evaluate('function f() {}\n' +
+ '//# sourceURL=http://example.com/foo.js\n' +
+ '//# sourceURL=http://example.com/bar.js');
+assertEq(getDisplayURL(), 'http://example.com/bar.js');
+
+// With both a comment and the evaluate option.
+g.evaluate('function f() {}\n' +
+ '//# sourceURL=http://example.com/foo.js',
+ {displayURL: 'http://example.com/bar.js'});
+assertEq(getDisplayURL(), 'http://example.com/foo.js');
+
+
+// Bug 981987 reported that we hadn't set sourceURL yet when firing onNewScript
+// from the Function constructor.
+var capturedScript;
+var capturedDisplayURL;
+var capturedSourceMapURL;
+dbg.onNewScript = function (script) {
+ capturedScript = script;
+ capturedDisplayURL = script.source.displayURL;
+ capturedSourceMapURL = script.source.sourceMapURL;
+ dbg.onNewScript = undefined;
+};
+var fun = gw.makeDebuggeeValue(g.Function('//# sourceURL=munge.js\n//# sourceMappingURL=grunge.map\n'));
+assertEq(capturedScript, fun.script);
+
+assertEq(capturedDisplayURL, fun.script.source.displayURL);
+assertEq(capturedDisplayURL, 'munge.js');
+
+assertEq(capturedSourceMapURL, fun.script.source.sourceMapURL);
+assertEq(capturedSourceMapURL, 'grunge.map');
diff --git a/js/src/jit-test/tests/debug/Source-element-01.js b/js/src/jit-test/tests/debug/Source-element-01.js
new file mode 100644
index 0000000000..c674d2774c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-element-01.js
@@ -0,0 +1,13 @@
+// Source.prototype.element can be an object or undefined.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+g.evaluate("function f(x) { return 2*x; }", {element: { foo: "bar" }});
+var fw = gw.getOwnPropertyDescriptor('f').value;
+assertEq(typeof fw.script.source.element, "object");
+assertEq(fw.script.source.element instanceof Debugger.Object, true);
+assertEq(fw.script.source.element.getOwnPropertyDescriptor("foo").value, "bar");
+g.evaluate("function f(x) { return 2*x; }");
+var fw = gw.getOwnPropertyDescriptor('f').value;
+assertEq(typeof fw.script.source.element, "undefined");
diff --git a/js/src/jit-test/tests/debug/Source-element-02.js b/js/src/jit-test/tests/debug/Source-element-02.js
new file mode 100644
index 0000000000..38c92280b4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-element-02.js
@@ -0,0 +1,6 @@
+// Specifying an owning element in a cross-global evaluation shouldn't crash.
+// That is, when 'evaluate' switches compartments, it should properly wrap
+// the CompileOptions members that will become cross-compartment
+// references.
+
+evaluate('42 + 1729', { global: newGlobal(), element: {} });
diff --git a/js/src/jit-test/tests/debug/Source-element-03.js b/js/src/jit-test/tests/debug/Source-element-03.js
new file mode 100644
index 0000000000..ad719849af
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-element-03.js
@@ -0,0 +1,26 @@
+// |jit-test| skip-if: helperThreadCount() === 0
+
+// Owning elements and attribute names are attached to scripts compiled
+// off-thread.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gDO = dbg.addDebuggee(g);
+
+var elt = new g.Object;
+var eltDO = gDO.makeDebuggeeValue(elt);
+
+var log = '';
+dbg.onDebuggerStatement = function (frame) {
+ log += 'd';
+ var source = frame.script.source;
+ assertEq(source.element, eltDO);
+ assertEq(source.elementAttributeName, 'mass');
+};
+
+g.offThreadCompileScript('debugger;',
+ { element: elt,
+ elementAttributeName: 'mass' });
+log += 'o';
+g.runOffThreadScript();
+assertEq(log, 'od');
diff --git a/js/src/jit-test/tests/debug/Source-element-04.js b/js/src/jit-test/tests/debug/Source-element-04.js
new file mode 100644
index 0000000000..8aa0cfef3f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-element-04.js
@@ -0,0 +1,13 @@
+// source.element is undefined if the script was introduced using eval() or Function().
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+
+g.eval("function f(x) { return 2*x; }");
+var fw = gw.getOwnPropertyDescriptor('f').value;
+assertEq(fw.script.source.element, undefined);
+
+g.x = g.Function("return 13;");
+var xw = gw.getOwnPropertyDescriptor('x').value;
+assertEq(xw.script.source.element, undefined);
diff --git a/js/src/jit-test/tests/debug/Source-element-05.js b/js/src/jit-test/tests/debug/Source-element-05.js
new file mode 100644
index 0000000000..702a6ef712
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-element-05.js
@@ -0,0 +1,11 @@
+// source.element is undefined when bad values are passed to evaluate().
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+
+for (let nonObject of [32, "[object Object]", null, undefined]) {
+ g.evaluate("function f(x) { return 2*x; }", {element: nonObject});
+ var fw = gw.getOwnPropertyDescriptor('f').value;
+ assertEq(fw.script.source.element, undefined);
+}
diff --git a/js/src/jit-test/tests/debug/Source-elementAttributeName.js b/js/src/jit-test/tests/debug/Source-elementAttributeName.js
new file mode 100644
index 0000000000..5e14a6857e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-elementAttributeName.js
@@ -0,0 +1,11 @@
+// Source.prototype.elementAttributeName can be a string or undefined.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+g.evaluate("function f(x) { return 2*x; }", {elementAttributeName: "src"});
+var fw = gw.getOwnPropertyDescriptor('f').value;
+assertEq(fw.script.source.elementAttributeName, "src");
+g.evaluate("function f(x) { return 2*x; }");
+var fw = gw.getOwnPropertyDescriptor('f').value;
+assertEq(fw.script.source.elementAttributeName, undefined);
diff --git a/js/src/jit-test/tests/debug/Source-introductionScript-01.js b/js/src/jit-test/tests/debug/Source-introductionScript-01.js
new file mode 100644
index 0000000000..29dfeb0c83
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-introductionScript-01.js
@@ -0,0 +1,118 @@
+// Dynamically generated sources should have their introduction script and
+// offset set correctly.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gDO = dbg.addDebuggee(g);
+var log;
+
+// Direct eval, while the frame is live.
+dbg.onDebuggerStatement = function (frame) {
+ log += 'd';
+ var source = frame.script.source;
+ var introducer = frame.older;
+ assertEq(source.introductionScript, introducer.script);
+ assertEq(source.introductionOffset, introducer.offset);
+};
+log = '';
+g.eval('\n\neval("\\n\\ndebugger;");');
+assertEq(log, 'd');
+
+// Direct eval, after the frame has been popped.
+var introducer, introduced;
+dbg.onDebuggerStatement = function (frame) {
+ log += 'de1';
+ introducer = frame.script;
+ dbg.onDebuggerStatement = function (frame) {
+ log += 'de2';
+ introduced = frame.script.source;
+ };
+};
+log = '';
+g.evaluate('debugger; eval("\\n\\ndebugger;");', { lineNumber: 1812 });
+assertEq(log, 'de1de2');
+assertEq(introduced.introductionScript, introducer);
+assertEq(introducer.getOffsetLocation(introduced.introductionOffset).lineNumber, 1812);
+
+// Indirect eval, while the frame is live.
+dbg.onDebuggerStatement = function (frame) {
+ log += 'd';
+ var source = frame.script.source;
+ var introducer = frame.older;
+ assertEq(source.introductionScript, introducer.script);
+ assertEq(source.introductionOffset, introducer.offset);
+};
+log = '';
+g.eval('\n\n(0,eval)("\\n\\ndebugger;");');
+assertEq(log, 'd');
+
+// Indirect eval, after the frame has been popped.
+var introducer, introduced;
+dbg.onDebuggerStatement = function (frame) {
+ log += 'de1';
+ introducer = frame.script;
+ dbg.onDebuggerStatement = function (frame) {
+ log += 'de2';
+ introduced = frame.script.source;
+ };
+};
+log = '';
+g.evaluate('debugger; (0,eval)("\\n\\ndebugger;");', { lineNumber: 1066 });
+assertEq(log, 'de1de2');
+assertEq(introduced.introductionScript, introducer);
+assertEq(introducer.getOffsetLocation(introduced.introductionOffset).lineNumber, 1066);
+
+// Function constructor.
+dbg.onDebuggerStatement = function (frame) {
+ log += 'o';
+ var outerScript = frame.script;
+ var outerOffset = frame.offset;
+ dbg.onDebuggerStatement = function (frame) {
+ log += 'i';
+ var source = frame.script.source;
+ assertEq(source.introductionScript, outerScript);
+ assertEq(outerScript.getOffsetLocation(source.introductionOffset).lineNumber,
+ outerScript.getOffsetLocation(outerOffset).lineNumber);
+ };
+};
+log = '';
+g.eval('\n\n\ndebugger; Function("debugger;")()');
+assertEq(log, 'oi');
+
+// Function constructor, after the the introduction call's frame has been
+// popped.
+var introducer;
+dbg.onDebuggerStatement = function (frame) {
+ log += 'F2';
+ introducer = frame.script;
+};
+log = '';
+var fDO = gDO.executeInGlobal('debugger; Function("origami;")', { lineNumber: 1685 }).return;
+var source = fDO.script.source;
+assertEq(log, 'F2');
+assertEq(source.introductionScript, introducer);
+assertEq(introducer.getOffsetLocation(source.introductionOffset).lineNumber, 1685);
+
+// If the introduction script is in a different global from the script it
+// introduced, we don't record it.
+dbg.onDebuggerStatement = function (frame) {
+ log += 'x';
+ var source = frame.script.source;
+ assertEq(source.introductionScript, undefined);
+ assertEq(source.introductionOffset, undefined);
+};
+log = '';
+g.eval('debugger;'); // introduction script is this top-level script
+assertEq(log, 'x');
+
+// If the code is introduced by a function that doesn't provide
+// introduction information, that shouldn't be a problem.
+dbg.onDebuggerStatement = function (frame) {
+ log += 'x';
+ var source = frame.script.source;
+ assertEq(source.introductionScript, undefined);
+ assertEq(source.introductionOffset, undefined);
+};
+log = '';
+g.eval('evaluate("debugger;", { lineNumber: 1729 });');
+assertEq(log, 'x');
diff --git a/js/src/jit-test/tests/debug/Source-introductionScript-02.js b/js/src/jit-test/tests/debug/Source-introductionScript-02.js
new file mode 100644
index 0000000000..45b0f61cb7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-introductionScript-02.js
@@ -0,0 +1,44 @@
+// Calls to 'eval', etc. by JS primitives get attributed to the point of
+// the primitive's call.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gDO = dbg.addDebuggee(g);
+var log = '';
+
+function outerHandler(frame) {
+ log += 'o';
+ var outerScript = frame.script;
+
+ dbg.onDebuggerStatement = function (frame) {
+ log += 'i';
+ var source = frame.script.source;
+ var introScript = source.introductionScript;
+ assertEq(introScript, outerScript);
+ assertEq(introScript.getOffsetLocation(source.introductionOffset).lineNumber, 1234);
+ };
+};
+
+log = '';
+dbg.onDebuggerStatement = outerHandler;
+g.evaluate('debugger; ["debugger;"].map(eval)', { lineNumber: 1234 });
+assertEq(log, 'oi');
+
+log = '';
+dbg.onDebuggerStatement = outerHandler;
+g.evaluate('debugger; "debugger;".replace(/.*/, eval);',
+ { lineNumber: 1234 });
+assertEq(log, 'oi');
+
+
+// If the call takes place in another global, however, we don't record the
+// introduction script.
+log = '';
+dbg.onDebuggerStatement = function (frame) {
+ log += 'd';
+ assertEq(frame.script.source.introductionScript, undefined);
+ assertEq(frame.script.source.introductionOffset, undefined);
+};
+["debugger;"].map(g.eval);
+"debugger;".replace(/.*/, g.eval);
+assertEq(log, 'dd');
diff --git a/js/src/jit-test/tests/debug/Source-introductionScript-03.js b/js/src/jit-test/tests/debug/Source-introductionScript-03.js
new file mode 100644
index 0000000000..7488309df1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-introductionScript-03.js
@@ -0,0 +1,32 @@
+// We don't record introduction scripts in a different global from the
+// introduced script, even if they're both debuggees.
+
+var dbg = new Debugger;
+
+var g1 = newGlobal({newCompartment: true});
+g1.g1 = g1;
+var g1DO = dbg.addDebuggee(g1);
+
+var g2 = newGlobal({newCompartment: true});
+g2.g1 = g1;
+
+var log = '';
+dbg.onDebuggerStatement = function (frame) {
+ log += 'd';
+ assertEq(frame.script.source.introductionScript, undefined);
+ assertEq(frame.script.source.introductionOffset, undefined);
+};
+
+g2.eval('g1.eval("debugger;");');
+assertEq(log, 'd');
+
+// Just for sanity: when it's not cross-global, we do note the introducer.
+log = '';
+dbg.onDebuggerStatement = function (frame) {
+ log += 'd';
+ assertEq(frame.script.source.introductionScript instanceof Debugger.Script, true);
+ assertEq(typeof frame.script.source.introductionOffset, "number");
+};
+// Exactly as above, but with g1 instead of g2.
+g1.eval('g1.eval("debugger;");');
+assertEq(log, 'd');
diff --git a/js/src/jit-test/tests/debug/Source-introductionType-data b/js/src/jit-test/tests/debug/Source-introductionType-data
new file mode 100644
index 0000000000..eab7469213
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-introductionType-data
@@ -0,0 +1 @@
+debugger;
diff --git a/js/src/jit-test/tests/debug/Source-introductionType.js b/js/src/jit-test/tests/debug/Source-introductionType.js
new file mode 100644
index 0000000000..36faf268cd
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-introductionType.js
@@ -0,0 +1,119 @@
+// |jit-test| skip-if: helperThreadCount() === 0
+
+// Check that scripts' introduction types are properly marked.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gDO = dbg.addDebuggee(g);
+var log;
+
+// (Indirect) eval.
+dbg.onDebuggerStatement = function (frame) {
+ log += 'd';
+ assertEq(frame.script.source.introductionType, 'eval');
+};
+log = '';
+g.eval('debugger;');
+assertEq(log, 'd');
+
+// Function constructor.
+dbg.onDebuggerStatement = function (frame) {
+ log += 'd';
+ assertEq(frame.script.source.introductionType, 'Function');
+};
+log = '';
+g.Function('debugger;')();
+assertEq(log, 'd');
+
+// GeneratorFunction constructor.
+dbg.onDebuggerStatement = function (frame) {
+ log += 'd';
+ assertEq(frame.script.source.introductionType, 'GeneratorFunction');
+};
+log = '';
+g.eval('(function*() {})').constructor('debugger;')().next();
+assertEq(log, 'd');
+
+// Shell 'evaluate' function
+dbg.onDebuggerStatement = function (frame) {
+ log += 'd';
+ assertEq(frame.script.source.introductionType, "js shell evaluate");
+};
+log = '';
+g.evaluate('debugger;');
+assertEq(log, 'd');
+
+// Shell 'load' function
+dbg.onDebuggerStatement = function (frame) {
+ log += 'd';
+ assertEq(frame.script.source.introductionType, "js shell load");
+};
+log = '';
+g.load(scriptdir + 'Source-introductionType-data');
+assertEq(log, 'd');
+
+// Shell 'run' function
+dbg.onDebuggerStatement = function (frame) {
+ log += 'd';
+ assertEq(frame.script.source.introductionType, "js shell run");
+};
+log = '';
+g.run(scriptdir + 'Source-introductionType-data');
+assertEq(log, 'd');
+
+// Shell 'offThreadCompileScript' function.
+dbg.onDebuggerStatement = function (frame) {
+ log += 'd';
+ assertEq(frame.script.source.introductionType, "js shell offThreadCompileScript");
+};
+log = '';
+g.offThreadCompileScript('debugger;');
+g.runOffThreadScript();
+assertEq(log, 'd');
+
+// Debugger.Frame.prototype.eval
+dbg.onDebuggerStatement = function (frame) {
+ log += 'o';
+ dbg.onDebuggerStatement = innerHandler;
+ frame.eval('debugger');
+ function innerHandler(frame) {
+ log += 'i';
+ assertEq(frame.script.source.introductionType, "debugger eval");
+ }
+};
+log = '';
+g.eval('debugger;');
+assertEq(log, 'oi');
+
+// Debugger.Frame.prototype.evalWithBindings
+dbg.onDebuggerStatement = function (frame) {
+ log += 'o';
+ dbg.onDebuggerStatement = innerHandler;
+ frame.evalWithBindings('debugger', { x: 42 });
+ function innerHandler(frame) {
+ log += 'i';
+ assertEq(frame.script.source.introductionType, "debugger eval");
+ }
+};
+log = '';
+g.eval('debugger;');
+assertEq(log, 'oi');
+
+// Debugger.Object.executeInGlobal
+dbg.onDebuggerStatement = function (frame) {
+ log += 'd';
+ assertEq(frame.script.source.introductionType, "debugger eval");
+};
+log = '';
+gDO.executeInGlobal('debugger;');
+assertEq(log, 'd');
+
+// Debugger.Object.executeInGlobalWithBindings
+dbg.onDebuggerStatement = function (frame) {
+ log += 'd';
+ assertEq(frame.script.source.introductionType, "debugger eval");
+};
+log = '';
+gDO.executeInGlobalWithBindings('debugger;', { x: 42 });
+assertEq(log, 'd');
+
diff --git a/js/src/jit-test/tests/debug/Source-invisible.js b/js/src/jit-test/tests/debug/Source-invisible.js
new file mode 100644
index 0000000000..420b6faec8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-invisible.js
@@ -0,0 +1,10 @@
+// Looking at ScriptSourceObjects in invisible-to-debugger compartments is okay.
+
+var gi = newGlobal({ newCompartment: true, invisibleToDebugger: true });
+
+var gv = newGlobal({newCompartment: true});
+gi.cloneAndExecuteScript('function f() {}', gv);
+
+var dbg = new Debugger;
+var gvw = dbg.addDebuggee(gv);
+gvw.getOwnPropertyDescriptor('f').value.script.source;
diff --git a/js/src/jit-test/tests/debug/Source-reparse.js b/js/src/jit-test/tests/debug/Source-reparse.js
new file mode 100644
index 0000000000..3667001f3e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-reparse.js
@@ -0,0 +1,42 @@
+// reparsing a source should produce equivalent scripts and avoid invoking the
+// onNewScript hook.
+
+const g = newGlobal({newCompartment: true});
+const dbg = new Debugger;
+dbg.addDebuggee(g);
+
+let globalScript;
+dbg.onNewScript = script => { globalScript = script };
+
+g.evaluate(`
+function f() {
+ for (var i = 0; i < 10; i++) {
+ g();
+ }
+}
+
+function g() {
+ return 3;
+}
+
+f();
+`, { fileName: "foobar.js", lineNumber: 3 });
+
+let onNewScriptCalls = 0;
+dbg.onNewScript = script => { onNewScriptCalls++; };
+
+const reparsedScript = globalScript.source.reparse();
+
+assertEq(onNewScriptCalls, 0);
+
+assertEq(reparsedScript.url, "foobar.js");
+assertEq(reparsedScript.startLine, 3);
+
+// Test for the same breakpoint positions in the original and reparsed script.
+function getBreakpointPositions(script) {
+ const offsets = script.getPossibleBreakpoints();
+ const str = offsets.map(({ lineNumber, columnNumber }) => `${lineNumber}:${columnNumber}`).toString();
+ const childPositions = script.getChildScripts().map(getBreakpointPositions);
+ return str + childPositions.toString();
+}
+assertEq(getBreakpointPositions(globalScript), getBreakpointPositions(reparsedScript));
diff --git a/js/src/jit-test/tests/debug/Source-sourceMapURL-deprecated.js b/js/src/jit-test/tests/debug/Source-sourceMapURL-deprecated.js
new file mode 100644
index 0000000000..f25f652a04
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-sourceMapURL-deprecated.js
@@ -0,0 +1,82 @@
+// Source.prototype.sourceMapURL can be a string or null.
+
+let g = newGlobal({newCompartment: true});
+let dbg = new Debugger;
+let gw = dbg.addDebuggee(g);
+
+function getSourceMapURL() {
+ let fw = gw.makeDebuggeeValue(g.f);
+ return fw.script.source.sourceMapURL;
+}
+
+function setSourceMapURL(url) {
+ let fw = gw.makeDebuggeeValue(g.f);
+ fw.script.source.sourceMapURL = url;
+}
+
+// Without a source map
+g.evaluate("function f(x) { return 2*x; }");
+assertEq(getSourceMapURL(), null);
+
+// With a source map
+g.evaluate("function f(x) { return 2*x; }", {sourceMapURL: 'file:///var/foo.js.map'});
+assertEq(getSourceMapURL(), 'file:///var/foo.js.map');
+
+// Nested functions
+let fired = false;
+dbg.onDebuggerStatement = function (frame) {
+ fired = true;
+ assertEq(frame.script.source.sourceMapURL, 'file:///var/bar.js.map');
+};
+g.evaluate('(function () { (function () { debugger; })(); })();',
+ {sourceMapURL: 'file:///var/bar.js.map'});
+assertEq(fired, true);
+
+// Comment pragmas
+g.evaluate('function f() {}\n' +
+ '//@ sourceMappingURL=file:///var/quux.js.map');
+assertEq(getSourceMapURL(), 'file:///var/quux.js.map');
+
+g.evaluate('function f() {}\n' +
+ '/*//@ sourceMappingURL=file:///var/quux.js.map*/');
+assertEq(getSourceMapURL(), 'file:///var/quux.js.map');
+
+g.evaluate('function f() {}\n' +
+ '/*\n' +
+ '//@ sourceMappingURL=file:///var/quux.js.map\n' +
+ '*/');
+assertEq(getSourceMapURL(), 'file:///var/quux.js.map');
+
+// Spaces are disallowed by the URL spec (they should have been
+// percent-encoded).
+g.evaluate('function f() {}\n' +
+ '//@ sourceMappingURL=http://example.com/has illegal spaces.map');
+assertEq(getSourceMapURL(), 'http://example.com/has');
+
+// When the URL is missing, we don't set the sourceMapURL and we don't skip the
+// next line of input.
+g.evaluate('function f() {}\n' +
+ '//@ sourceMappingURL=\n' +
+ 'function z() {}');
+assertEq(getSourceMapURL(), null);
+assertEq('z' in g, true);
+
+// The last comment pragma we see should be the one which sets the source map's
+// URL.
+g.evaluate('function f() {}\n' +
+ '//@ sourceMappingURL=http://example.com/foo.js.map\n' +
+ '//@ sourceMappingURL=http://example.com/bar.js.map');
+assertEq(getSourceMapURL(), 'http://example.com/bar.js.map');
+
+// With both a comment and the evaluate option.
+g.evaluate('function f() {}\n' +
+ '//@ sourceMappingURL=http://example.com/foo.js.map',
+ {sourceMapURL: 'http://example.com/bar.js.map'});
+assertEq(getSourceMapURL(), 'http://example.com/foo.js.map');
+
+// Make sure setting the sourceMapURL manually works
+setSourceMapURL('baz.js.map');
+assertEq(getSourceMapURL(), 'baz.js.map');
+
+setSourceMapURL('');
+assertEq(getSourceMapURL(), 'baz.js.map');
diff --git a/js/src/jit-test/tests/debug/Source-sourceMapURL-disable.js b/js/src/jit-test/tests/debug/Source-sourceMapURL-disable.js
new file mode 100644
index 0000000000..0e103d7df3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-sourceMapURL-disable.js
@@ -0,0 +1,14 @@
+// |jit-test| --no-source-pragmas
+
+const g = newGlobal({ newCompartment: true });
+const dbg = Debugger(g);
+let source;
+dbg.onDebuggerStatement = function (frame) {
+ source = frame.script.source;
+};
+
+g.eval(`
+ debugger;
+ //# sourceMappingURL=file.js
+`);
+assertEq(source.sourceMapURL, null);
diff --git a/js/src/jit-test/tests/debug/Source-sourceMapURL.js b/js/src/jit-test/tests/debug/Source-sourceMapURL.js
new file mode 100644
index 0000000000..449b44cebd
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-sourceMapURL.js
@@ -0,0 +1,82 @@
+// Source.prototype.sourceMapURL can be a string or null.
+
+let g = newGlobal({newCompartment: true});
+let dbg = new Debugger;
+let gw = dbg.addDebuggee(g);
+
+function getSourceMapURL() {
+ let fw = gw.makeDebuggeeValue(g.f);
+ return fw.script.source.sourceMapURL;
+}
+
+function setSourceMapURL(url) {
+ let fw = gw.makeDebuggeeValue(g.f);
+ fw.script.source.sourceMapURL = url;
+}
+
+// Without a source map
+g.evaluate("function f(x) { return 2*x; }");
+assertEq(getSourceMapURL(), null);
+
+// With a source map
+g.evaluate("function f(x) { return 2*x; }", {sourceMapURL: 'file:///var/foo.js.map'});
+assertEq(getSourceMapURL(), 'file:///var/foo.js.map');
+
+// Nested functions
+let fired = false;
+dbg.onDebuggerStatement = function (frame) {
+ fired = true;
+ assertEq(frame.script.source.sourceMapURL, 'file:///var/bar.js.map');
+};
+g.evaluate('(function () { (function () { debugger; })(); })();',
+ {sourceMapURL: 'file:///var/bar.js.map'});
+assertEq(fired, true);
+
+// Comment pragmas
+g.evaluate('function f() {}\n' +
+ '//# sourceMappingURL=file:///var/quux.js.map');
+assertEq(getSourceMapURL(), 'file:///var/quux.js.map');
+
+g.evaluate('function f() {}\n' +
+ '/*//# sourceMappingURL=file:///var/quux.js.map*/');
+assertEq(getSourceMapURL(), 'file:///var/quux.js.map');
+
+g.evaluate('function f() {}\n' +
+ '/*\n' +
+ '//# sourceMappingURL=file:///var/quux.js.map\n' +
+ '*/');
+assertEq(getSourceMapURL(), 'file:///var/quux.js.map');
+
+// Spaces are disallowed by the URL spec (they should have been
+// percent-encoded).
+g.evaluate('function f() {}\n' +
+ '//# sourceMappingURL=http://example.com/has illegal spaces.map');
+assertEq(getSourceMapURL(), 'http://example.com/has');
+
+// When the URL is missing, we don't set the sourceMapURL and we don't skip the
+// next line of input.
+g.evaluate('function f() {}\n' +
+ '//# sourceMappingURL=\n' +
+ 'function z() {}');
+assertEq(getSourceMapURL(), null);
+assertEq('z' in g, true);
+
+// The last comment pragma we see should be the one which sets the source map's
+// URL.
+g.evaluate('function f() {}\n' +
+ '//# sourceMappingURL=http://example.com/foo.js.map\n' +
+ '//# sourceMappingURL=http://example.com/bar.js.map');
+assertEq(getSourceMapURL(), 'http://example.com/bar.js.map');
+
+// With both a comment and the evaluate option.
+g.evaluate('function f() {}\n' +
+ '//# sourceMappingURL=http://example.com/foo.js.map',
+ {sourceMapURL: 'http://example.com/bar.js.map'});
+assertEq(getSourceMapURL(), 'http://example.com/foo.js.map');
+
+// Make sure setting the sourceMapURL manually works
+setSourceMapURL('baz.js.map');
+assertEq(getSourceMapURL(), 'baz.js.map');
+
+setSourceMapURL('');
+assertEq(getSourceMapURL(), 'baz.js.map');
diff --git a/js/src/jit-test/tests/debug/Source-startLine.js b/js/src/jit-test/tests/debug/Source-startLine.js
new file mode 100644
index 0000000000..5f748f2cf2
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-startLine.js
@@ -0,0 +1,11 @@
+// Source.prototype.startLine reflects the start line supplied when parsing.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+g.evaluate("function f(x) {}");
+var fw = gw.getOwnPropertyDescriptor('f').value;
+assertEq(fw.script.source.startLine, 1);
+g.evaluate("function g(x) {}", {lineNumber: 10});
+var gw = gw.getOwnPropertyDescriptor('g').value;
+assertEq(gw.script.source.startLine, 10);
diff --git a/js/src/jit-test/tests/debug/Source-surfaces.js b/js/src/jit-test/tests/debug/Source-surfaces.js
new file mode 100644
index 0000000000..f2b3e81d9d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-surfaces.js
@@ -0,0 +1,33 @@
+// Debugger.Source.prototype
+
+load(libdir + 'asserts.js');
+
+assertThrowsInstanceOf(function () {
+ Debugger.Source.prototype.text.call(42)
+}, TypeError);
+assertThrowsInstanceOf(function () {
+ Debugger.Source.prototype.text.call({})
+}, TypeError);
+assertThrowsInstanceOf(function () {
+ Debugger.Source.prototype.text.call(Debugger.Source.prototype)
+}, TypeError);
+
+assertThrowsInstanceOf(function () {
+ Debugger.Source.prototype.element.call(42)
+}, TypeError);
+assertThrowsInstanceOf(function () {
+ Debugger.Source.prototype.element.call({})
+}, TypeError);
+assertThrowsInstanceOf(function () {
+ Debugger.Source.prototype.element.call(Debugger.Source.prototype)
+}, TypeError);
+
+assertThrowsInstanceOf(function () {
+ Debugger.Source.prototype.elementAttributeName.call(42)
+}, TypeError);
+assertThrowsInstanceOf(function () {
+ Debugger.Source.prototype.elementAttributeName.call({})
+}, TypeError);
+assertThrowsInstanceOf(function () {
+ Debugger.Source.prototype.elementAttributeName.call(Debugger.Source.prototype)
+}, TypeError);
diff --git a/js/src/jit-test/tests/debug/Source-text-01.js b/js/src/jit-test/tests/debug/Source-text-01.js
new file mode 100644
index 0000000000..799ccf36e0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-text-01.js
@@ -0,0 +1,26 @@
+/*
+ * Debugger.Source.prototype.text should return a string. Moreover, it
+ * should be the same string for each child script sharing that
+ * Debugger.Source.
+ */
+let g = newGlobal({newCompartment: true});
+let dbg = new Debugger(g);
+
+var count = 0;
+dbg.onNewScript = function (script) {
+ var text = script.source.text;
+ assertEq(typeof text, "string");
+ function traverse(script) {
+ ++count;
+ script.getChildScripts().forEach(function (script) {
+ assertEq(script.source.text, text);
+ traverse(script);
+ });
+ };
+ traverse(script);
+}
+
+g.eval("2 * 3");
+g.eval("function f() {}");
+g.eval("function f() { function g() {} }");
+assertEq(count, 6);
diff --git a/js/src/jit-test/tests/debug/Source-text-02.js b/js/src/jit-test/tests/debug/Source-text-02.js
new file mode 100644
index 0000000000..45f0b9660b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-text-02.js
@@ -0,0 +1,20 @@
+// Nested compilation units (say, an eval with in an eval) should have the
+// correct sources attributed to them.
+let g = newGlobal({newCompartment: true});
+let dbg = new Debugger(g);
+
+var text;
+var count = 0;
+dbg.onNewScript = function (script) {
+ ++count;
+ if (count % 2 == 0)
+ assertEq(script.source.text, text);
+}
+
+g.eval("eval('" + (text = "") + "')");
+g.eval("eval('" + (text = "2 * 3") + "')");
+g.eval("new Function('" + (text = "") + "')");
+g.eval("new Function('" + (text = "2 * 3") + "')");
+evaluate("", { global: g });
+evaluate("2 * 3", { global: g });
+assertEq(count, 10);
diff --git a/js/src/jit-test/tests/debug/Source-text-lazy.js b/js/src/jit-test/tests/debug/Source-text-lazy.js
new file mode 100644
index 0000000000..b6bf3e2423
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-text-lazy.js
@@ -0,0 +1,38 @@
+// |jit-test| skip-if: typeof withSourceHook !== 'function'
+// withSourceHook isn't defined if you pass the shell the --fuzzing-safe
+// option. Skip this test silently, to avoid spurious failures.
+
+/*
+ * Debugger.Source.prototype.text should correctly retrieve the source for
+ * code compiled with CompileOptions::LAZY_SOURCE.
+ */
+
+let g = newGlobal({newCompartment: true});
+let dbg = new Debugger(g);
+
+function test(source) {
+ // To ensure that we're getting the value the source hook returns, make
+ // it differ from the actual source.
+ let frobbed = source.replace(/debugger/, 'reggubed');
+ let log = '';
+
+ withSourceHook(function (url) {
+ log += 's';
+ assertEq(url, "BanalBivalve.jsm");
+ return frobbed;
+ }, () => {
+ dbg.onDebuggerStatement = function (frame) {
+ log += 'd';
+ assertEq(frame.script.source.text, frobbed);
+ }
+
+ g.evaluate(source, { fileName: "BanalBivalve.jsm",
+ sourceIsLazy: true });
+ });
+
+ assertEq(log, 'ds');
+}
+
+test("debugger; // Ignominious Iguana");
+test("(function () { debugger; /* Meretricious Marmoset */})();");
+test("(() => { debugger; })(); // Gaunt Gibbon");
diff --git a/js/src/jit-test/tests/debug/Source-url-01.js b/js/src/jit-test/tests/debug/Source-url-01.js
new file mode 100644
index 0000000000..4a020aa756
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-url-01.js
@@ -0,0 +1,10 @@
+// Source.prototype.url returns a synthesized URL for eval code.
+
+var g = newGlobal({newCompartment: true});
+g.eval('function double() { return 2*x }');
+
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+
+var fw = gw.getOwnPropertyDescriptor('double').value;
+assertEq(!!fw.script.source.url.match(/Source-url-01.js line [0-9]+ > eval/), true);
diff --git a/js/src/jit-test/tests/debug/Source-url-02.js b/js/src/jit-test/tests/debug/Source-url-02.js
new file mode 100644
index 0000000000..9d20155b59
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-url-02.js
@@ -0,0 +1,11 @@
+// Source.prototype.url returns a synthesized URL for Function code.
+
+var g = newGlobal({newCompartment: true});
+g.eval('var double = Function("x", "return 2*x;");');
+
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+
+var fw = gw.getOwnPropertyDescriptor('double').value;
+print(fw.script.source.url);
+assertEq(!!fw.script.source.url.match(/Source-url-02.js .* > Function/), true);
diff --git a/js/src/jit-test/tests/debug/Source-url.js b/js/src/jit-test/tests/debug/Source-url.js
new file mode 100644
index 0000000000..b37f609cf6
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-url.js
@@ -0,0 +1,10 @@
+// Source.prototype.url can be a string or null.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+for (var fileName of ['file:///var/foo.js', null]) {
+ g.evaluate("function f(x) { return 2*x; }", {fileName: fileName});
+ var fw = gw.getOwnPropertyDescriptor('f').value;
+ assertEq(fw.script.source.url, fileName);
+}
diff --git a/js/src/jit-test/tests/debug/breakpoint-01.js b/js/src/jit-test/tests/debug/breakpoint-01.js
new file mode 100644
index 0000000000..3adde6b977
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-01.js
@@ -0,0 +1,22 @@
+// Basic breakpoint test.
+
+var g = newGlobal({newCompartment: true});
+g.s = '';
+var handler = {
+ hit: function (frame) {
+ assertEq(this, handler);
+ g.s += '1';
+ }
+};
+var dbg = Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+ g.s += '0';
+ var line0 = frame.script.getOffsetLocation(frame.offset).lineNumber;
+ var offs = frame.script.getLineOffsets(line0 + 2);
+ for (var i = 0; i < offs.length; i++)
+ frame.script.setBreakpoint(offs[i], handler);
+};
+g.eval("debugger;\n" +
+ "s += 'a';\n" + // line0 + 1
+ "s += 'b';\n"); // line0 + 2
+assertEq(g.s, "0a1b");
diff --git a/js/src/jit-test/tests/debug/breakpoint-02.js b/js/src/jit-test/tests/debug/breakpoint-02.js
new file mode 100644
index 0000000000..45dd420515
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-02.js
@@ -0,0 +1,15 @@
+// Setting a breakpoint in a non-debuggee Script is an error.
+
+load(libdir + "asserts.js");
+
+var g1 = newGlobal({newCompartment: true});
+var g2 = g1.eval("newGlobal('same-compartment')");
+g2.eval("function f() { return 2; }");
+g1.f = g2.f;
+
+var dbg = Debugger(g1);
+var s;
+dbg.onDebuggerStatement = function (frame) { s = frame.eval("f").return.script; };
+g1.eval("debugger;");
+
+assertThrowsInstanceOf(function () { s.setBreakpoint(0, {}); }, Error);
diff --git a/js/src/jit-test/tests/debug/breakpoint-03.js b/js/src/jit-test/tests/debug/breakpoint-03.js
new file mode 100644
index 0000000000..ce582cb214
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-03.js
@@ -0,0 +1,16 @@
+// Setting a breakpoint in a script we are no longer debugging is an error.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger();
+var gobj = dbg.addDebuggee(g);
+g.eval("function f() { return 2; }");
+
+var s;
+dbg.onDebuggerStatement = function (frame) { s = frame.eval("f").return.script; };
+g.eval("debugger;");
+s.setBreakpoint(0, {}); // ok
+
+dbg.removeDebuggee(gobj);
+assertThrowsInstanceOf(function () { s.setBreakpoint(0, {}); }, Error);
diff --git a/js/src/jit-test/tests/debug/breakpoint-04.js b/js/src/jit-test/tests/debug/breakpoint-04.js
new file mode 100644
index 0000000000..f5c0c957b3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-04.js
@@ -0,0 +1,30 @@
+// Hitting a breakpoint with no hit method does nothing.
+
+var g = newGlobal({newCompartment: true});
+g.s = '';
+g.eval("var line0 = Error().lineNumber;\n" +
+ "function f() {\n" + // line0 + 1
+ " debugger;\n" + // line0 + 2
+ " s += 'x';\n" + // line0 + 3
+ "}\n")
+var dbg = Debugger(g);
+var bp = [];
+dbg.onDebuggerStatement = function (frame) {
+ g.s += 'D';
+ var arr = frame.script.getLineOffsets(g.line0 + 3);
+ for (var i = 0; i < arr.length; i++) {
+ var obj = {};
+ bp[i] = obj;
+ frame.script.setBreakpoint(arr[i], obj);
+ }
+};
+
+g.f();
+assertEq(g.s, "Dx");
+
+dbg.onDebuggerStatement = undefined;
+
+for (var i = 0; i < bp.length; i++)
+ bp[i].hit = function () { g.s += 'B'; };
+g.f();
+assertEq(g.s, "DxBx");
diff --git a/js/src/jit-test/tests/debug/breakpoint-05.js b/js/src/jit-test/tests/debug/breakpoint-05.js
new file mode 100644
index 0000000000..61ae53eb4e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-05.js
@@ -0,0 +1,19 @@
+// If the offset parameter to setBreakpoint is invalid, throw an error.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ // We assume at least one offset between 0 and frame.offset is invalid.
+ assertThrowsInstanceOf(
+ function () {
+ for (var i = 0; i < frame.offset; i++)
+ frame.script.setBreakpoint(i, {});
+ },
+ Error);
+ hits++;
+};
+g.eval("x = 256; debugger;");
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/breakpoint-06.js b/js/src/jit-test/tests/debug/breakpoint-06.js
new file mode 100644
index 0000000000..897220d40a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-06.js
@@ -0,0 +1,20 @@
+// The argument to a breakpoint hit method is a frame.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame1) {
+ function hit(frame2) {
+ assertEq(frame2, frame1);
+ hits++;
+ }
+ var s = frame1.script;
+ var offs = s.getLineOffsets(g.line0 + 2);
+ for (var i = 0; i < offs.length; i++)
+ s.setBreakpoint(offs[i], {hit: hit});
+};
+g.eval("var line0 = Error().lineNumber;\n" +
+ "debugger;\n" + // line0 + 1
+ "x = 1;\n"); // line0 + 2
+assertEq(hits, 1);
+assertEq(g.x, 1);
diff --git a/js/src/jit-test/tests/debug/breakpoint-07.js b/js/src/jit-test/tests/debug/breakpoint-07.js
new file mode 100644
index 0000000000..01ff27a339
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-07.js
@@ -0,0 +1,30 @@
+// Code runs fine if do-nothing breakpoints are set on every line.
+
+var g = newGlobal({newCompartment: true});
+var src = ("var line0 = Error().lineNumber;\n" +
+ "function gcd(a, b) {\n" + // line0 + 1
+ " if (a > b)\n" + // line0 + 2
+ " return gcd(b, a);\n" + // line0 + 3
+ " var c = b % a;\n" + // line0 + 4
+ " if (c === 0)\n" + // line0 + 5
+ " return a;\n" + // line0 + 6
+ " return gcd(c, a);\n" + // line0 + 7
+ "}\n"); // line0 + 8
+g.eval(src);
+
+var dbg = Debugger(g);
+var hits = 0 ;
+dbg.onDebuggerStatement = function (frame) {
+ var s = frame.eval("gcd").return.script;
+ var offs;
+ for (var lineno = g.line0 + 2; (offs = s.getLineOffsets(lineno)).length > 0; lineno++) {
+ for (var i = 0; i < offs.length; i++)
+ s.setBreakpoint(offs[i], {hit: function (f) { hits++; }});
+ }
+ assertEq(lineno > g.line0 + 7, true);
+ assertEq(lineno <= g.line0 + 9, true);
+};
+
+g.eval("debugger;");
+assertEq(g.gcd(31 * 7 * 5 * 3 * 2, 11 * 3 * 3 * 2), 6);
+assertEq(hits >= 18, true);
diff --git a/js/src/jit-test/tests/debug/breakpoint-08.js b/js/src/jit-test/tests/debug/breakpoint-08.js
new file mode 100644
index 0000000000..ae0e905503
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-08.js
@@ -0,0 +1,31 @@
+// Breakpoints are dropped from eval scripts when they finish executing.
+// (The eval cache doesn't cache breakpoints.)
+
+var g = newGlobal({newCompartment: true});
+
+g.line0 = undefined;
+g.eval("function f() {\n" +
+ " return eval(s);\n" +
+ "}\n");
+g.s = ("line0 = Error().lineNumber;\n" +
+ "debugger;\n" + // line0 + 1
+ "result = 'ok';\n"); // line0 + 2
+
+var dbg = Debugger(g);
+var hits = 0, bphits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ assertEq(frame.type, 'eval');
+ assertEq(frame.script.getBreakpoints().length, 0);
+ var h = {hit: function (frame) { bphits++; }};
+ var offs = frame.script.getLineOffsets(g.line0 + 2);
+ for (var i = 0; i < offs.length; i++)
+ frame.script.setBreakpoint(offs[i], h);
+ hits++;
+};
+
+for (var i = 0; i < 3; i++) {
+ assertEq(g.f(), 'ok');
+ assertEq(g.result, 'ok');
+}
+assertEq(hits, 3);
+assertEq(bphits, 3);
diff --git a/js/src/jit-test/tests/debug/breakpoint-09.js b/js/src/jit-test/tests/debug/breakpoint-09.js
new file mode 100644
index 0000000000..45aee69985
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-09.js
@@ -0,0 +1,13 @@
+// Setting a breakpoint in an eval script that is not on the stack. Bug 746973.
+// We don't assert that the breakpoint actually hits because that depends on
+// the eval cache, an implementation detail.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+g.eval("function f() { return eval('2+2'); }");
+var s;
+dbg.onNewScript = function (script) { s = script; };
+g.f();
+for (var offset of s.getLineOffsets(s.startLine))
+ s.setBreakpoint(offset, {hit: function () {}});
+assertEq(g.f(), 4);
diff --git a/js/src/jit-test/tests/debug/breakpoint-10.js b/js/src/jit-test/tests/debug/breakpoint-10.js
new file mode 100644
index 0000000000..65665ae173
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-10.js
@@ -0,0 +1,19 @@
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+var fscript = null;
+dbg.onNewScript = function(script) {
+ dbg.onNewScript = undefined;
+ fscript = script.getChildScripts()[0];
+}
+
+g.eval("function f(x) { arguments[0] = 3; return x }");
+assertEq(fscript !== null, true);
+
+fscript.setBreakpoint(0, {hit:function(frame) {
+ assertEq(frame.eval('x').return, 1);
+ assertEq(frame.arguments[0], 1);
+ return {return:42};
+}});
+
+assertEq(g.f(1), 42);
diff --git a/js/src/jit-test/tests/debug/breakpoint-11.js b/js/src/jit-test/tests/debug/breakpoint-11.js
new file mode 100644
index 0000000000..4dce2e95b4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-11.js
@@ -0,0 +1,38 @@
+// Setting a breakpoint in a generator function works, and we can
+// traverse the stack and evaluate expressions in the context of older
+// generator frames.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+ function hit(frame) {
+ frame.older.eval("q += 16");
+ }
+
+ var s = frame.script;
+ var offs = s.getLineOffsets(g.line0 + 9);
+ for (var i = 0; i < offs.length; i++)
+ s.setBreakpoint(offs[i], {hit: hit});
+};
+
+g.eval("line0 = Error().lineNumber;\n" +
+ "function* g(x) {\n" + // + 1
+ " var q = 10;\n" + // + 2
+ " yield* x;\n" + // + 3
+ " return q;\n" + // + 4
+ "}\n" + // + 5
+ "function* range(n) {\n" + // + 6
+ " debugger;\n" + // + 7
+ " for (var i = 0; i < n; i++)\n" + // + 8
+ " yield i;\n" + // + 9 <-- breakpoint
+ " return;\n" + // so that line 9 only has the yield
+ "}");
+
+g.eval("var iter = g(range(2))");
+g.eval("var first = iter.next().value");
+g.eval("var second = iter.next().value");
+g.eval("var third = iter.next().value");
+
+assertEq(g.first, 0);
+assertEq(g.second, 1);
+assertEq(g.third, 42);
diff --git a/js/src/jit-test/tests/debug/breakpoint-12.js b/js/src/jit-test/tests/debug/breakpoint-12.js
new file mode 100644
index 0000000000..96770c8f26
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-12.js
@@ -0,0 +1,78 @@
+// Removing a global as a debuggee forgets all its breakpoints.
+
+
+var dbgA = new Debugger;
+var logA = '';
+
+var dbgB = new Debugger;
+var logB = '';
+
+var g1 = newGlobal({newCompartment: true});
+g1.eval('function g1f() { print("Weltuntergang"); }');
+var DOAg1 = dbgA.addDebuggee(g1);
+var DOAg1f = DOAg1.getOwnPropertyDescriptor('g1f').value;
+DOAg1f.script.setBreakpoint(0, { hit: () => { logA += '1'; } });
+
+var DOBg1 = dbgB.addDebuggee(g1);
+var DOBg1f = DOBg1.getOwnPropertyDescriptor('g1f').value;
+DOBg1f.script.setBreakpoint(0, { hit: () => { logB += '1'; } });
+
+
+var g2 = newGlobal({newCompartment: true});
+g2.eval('function g2f() { print("Mokushi"); }');
+
+var DOAg2 = dbgA.addDebuggee(g2);
+var DOAg2f = DOAg2.getOwnPropertyDescriptor('g2f').value;
+DOAg2f.script.setBreakpoint(0, { hit: () => { logA += '2'; } });
+
+var DOBg2 = dbgB.addDebuggee(g2);
+var DOBg2f = DOBg2.getOwnPropertyDescriptor('g2f').value;
+DOBg2f.script.setBreakpoint(0, { hit: () => { logB += '2'; } });
+
+assertEq(logA, '');
+assertEq(logB, '');
+g1.g1f();
+g2.g2f();
+assertEq(logA, '12');
+assertEq(logB, '12');
+logA = logB = '';
+
+// Removing a global as a debuggee should make its breakpoint not hit.
+dbgA.removeDebuggee(g2);
+dbgB.removeDebuggee(g1);
+assertEq(logA, '');
+assertEq(logB, '');
+g1.g1f();
+g2.g2f();
+assertEq(logA, '1');
+assertEq(logB, '2');
+logA = logB = '';
+
+// Adding the global back as a debuggee should not resurrect its breakpoints.
+dbgA.addDebuggee(g2);
+dbgB.addDebuggee(g1);
+assertEq(logA, '');
+assertEq(logB, '');
+g1.g1f();
+g2.g2f();
+assertEq(logA, '1');
+assertEq(logB, '2');
+logA = logB = '';
+
+// But, we can set them again, and it all works.
+DOAg2f.script.setBreakpoint(0, { hit: () => { logA += '2'; } });
+assertEq(logA, '');
+assertEq(logB, '');
+g2.g2f();
+g1.g1f();
+assertEq(logA, '21');
+assertEq(logB, '2');
+logA = logB = '';
+
+DOBg1f.script.setBreakpoint(0, { hit: () => { logB += '1'; } });
+assertEq(logA, '');
+assertEq(logB, '');
+g2.g2f();
+g1.g1f();
+assertEq(logA, '21');
+assertEq(logB, '21');
diff --git a/js/src/jit-test/tests/debug/breakpoint-13.js b/js/src/jit-test/tests/debug/breakpoint-13.js
new file mode 100644
index 0000000000..d839195ce4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-13.js
@@ -0,0 +1,13 @@
+// Breakpoints should be hit on scripts gotten not via Debugger.Frame.
+
+var g = newGlobal({newCompartment: true});
+g.eval("function f(x) { return x + 1; }");
+// Warm up so f gets OSRed into the jits.
+g.eval("for (var i = 0; i < 10000; i++) f(i);");
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+var fw = gw.getOwnPropertyDescriptor("f").value;
+var hits = 0;
+fw.script.setBreakpoint(0, { hit: function(frame) { hits++; } });
+g.eval("f(42);");
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/breakpoint-14.js b/js/src/jit-test/tests/debug/breakpoint-14.js
new file mode 100644
index 0000000000..48ee53594d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-14.js
@@ -0,0 +1,14 @@
+// Breakpoints should be hit on scripts gotten not via Debugger.Frame.
+
+var g = newGlobal({newCompartment: true});
+g.eval("function f(x) { return x + 1; }");
+g.eval("function g(x) { f(x); }");
+// Warm up so f gets OSRed into the jits and g inlines f.
+g.eval("for (var i = 0; i < 10000; i++) g(i);");
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+var fw = gw.getOwnPropertyDescriptor("f").value;
+var hits = 0;
+fw.script.setBreakpoint(0, { hit: function(frame) { hits++; } });
+g.eval("g(42);");
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/breakpoint-gc-01.js b/js/src/jit-test/tests/debug/breakpoint-gc-01.js
new file mode 100644
index 0000000000..2cbfe66fb4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-gc-01.js
@@ -0,0 +1,25 @@
+// Handlers for breakpoints in an eval script are live as long as the script is on the stack.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var log = '';
+dbg.onDebuggerStatement = function (frame) {
+ function handler(i) {
+ return {hit: function () { log += '' + i; }};
+ }
+
+ var s = frame.script;
+ var offs = s.getLineOffsets(g.line0 + 2);
+ for (var i = 0; i < 7; i++) {
+ var h = handler(i);
+ for (var j = 0; j < offs.length; j++)
+ s.setBreakpoint(offs[j], h);
+ }
+ gc();
+};
+
+
+g.eval("var line0 = Error().lineNumber;\n" +
+ "debugger;\n" + // line0 + 1
+ "x = 1;\n"); // line0 + 2
+assertEq(log, '0123456');
diff --git a/js/src/jit-test/tests/debug/breakpoint-gc-02.js b/js/src/jit-test/tests/debug/breakpoint-gc-02.js
new file mode 100644
index 0000000000..89c447ae67
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-gc-02.js
@@ -0,0 +1,28 @@
+// A Debugger with live breakpoints is live.
+
+var g = newGlobal({newCompartment: true});
+g.eval("var line0 = Error().lineNumber;\n" +
+ "function f() {\n" + // line0 + 1
+ " return 2;\n" + // line0 + 2
+ "}\n");
+
+var N = 4;
+var hits = 0;
+for (var i = 0; i < N; i++) {
+ var dbg = Debugger(g);
+ dbg.onDebuggerStatement = function (frame) {
+ var handler = {hit: function () { hits++; }};
+ var s = frame.eval("f").return.script;
+ var offs = s.getLineOffsets(g.line0 + 2);
+ for (var j = 0; j < offs.length; j++)
+ s.setBreakpoint(offs[j], handler);
+ };
+ g.eval('debugger;');
+ dbg.onDebuggerStatement = undefined;
+ dbg = null;
+}
+
+gc();
+
+assertEq(g.f(), 2);
+assertEq(hits, N);
diff --git a/js/src/jit-test/tests/debug/breakpoint-gc-04.js b/js/src/jit-test/tests/debug/breakpoint-gc-04.js
new file mode 100644
index 0000000000..38c7d14975
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-gc-04.js
@@ -0,0 +1,23 @@
+// Enabled debuggers keep breakpoint handlers alive.
+
+var g = newGlobal({newCompartment: true});
+g.eval("var line0 = Error().lineNumber;\n" +
+ "function f() {\n" + // line0 + 1
+ " return 2;\n" + // line0 + 2
+ "}\n");
+var N = 4;
+var hits = 0;
+for (var i = 0; i < N; i++) {
+ var dbg = Debugger(g);
+ dbg.onDebuggerStatement = function (frame) {
+ var handler = {hit: function () { hits++; }};
+ var s = frame.eval("f").return.script;
+ var offs = s.getLineOffsets(g.line0 + 2);
+ for (var j = 0; j < offs.length; j++)
+ s.setBreakpoint(offs[j], handler);
+ };
+}
+g.eval('debugger;');
+gc({});
+assertEq(g.f(), 2);
+assertEq(hits, N);
diff --git a/js/src/jit-test/tests/debug/breakpoint-gc-05.js b/js/src/jit-test/tests/debug/breakpoint-gc-05.js
new file mode 100644
index 0000000000..41e4e2aaaf
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-gc-05.js
@@ -0,0 +1,25 @@
+// Disabled debuggers keep breakpoint handlers alive.
+
+var g = newGlobal({newCompartment: true});
+g.eval("var line0 = Error().lineNumber;\n" +
+ "function f() {\n" + // line0 + 1
+ " return 2;\n" + // line0 + 2
+ "}\n");
+var N = 4;
+var hits = 0;
+for (var i = 0; i < N; i++) {
+ var dbg = Debugger(g);
+ dbg.onDebuggerStatement = function (frame) {
+ var handler = {hit: function () { hits++; }};
+ var s = frame.eval("f").return.script;
+ var offs = s.getLineOffsets(g.line0 + 2);
+ for (var j = 0; j < offs.length; j++)
+ s.setBreakpoint(offs[j], handler);
+ };
+}
+g.eval('debugger;');
+dbg.enabled = false;
+gc({});
+dbg.enabled = true;
+assertEq(g.f(), 2);
+assertEq(hits, N);
diff --git a/js/src/jit-test/tests/debug/breakpoint-multi-01.js b/js/src/jit-test/tests/debug/breakpoint-multi-01.js
new file mode 100644
index 0000000000..88f52aedf5
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-multi-01.js
@@ -0,0 +1,28 @@
+// A single Debugger object can set multiple breakpoints at an instruction.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var log = '';
+dbg.onDebuggerStatement = function (frame) {
+ log += 'D';
+ function handler(i) {
+ return {hit: function (frame) { log += '' + i; }};
+ }
+ var f = frame.eval("f").return;
+ var s = f.script;
+ var offs = s.getLineOffsets(g.line0 + 2);
+ for (var i = 0; i < 10; i++) {
+ var bp = handler(i);
+ for (var j = 0; j < offs.length; j++)
+ s.setBreakpoint(offs[j], bp);
+ }
+ assertEq(f.call().return, 42);
+ log += 'X';
+};
+
+g.eval("var line0 = Error().lineNumber;\n" +
+ "function f() {\n" + // line0 + 1
+ " return 42;\n" + // line0 + 2
+ "}\n" +
+ "debugger;\n");
+assertEq(log, 'D0123456789X');
diff --git a/js/src/jit-test/tests/debug/breakpoint-multi-02.js b/js/src/jit-test/tests/debug/breakpoint-multi-02.js
new file mode 100644
index 0000000000..b8d9b51d95
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-multi-02.js
@@ -0,0 +1,42 @@
+// After clearing one breakpoint, another breakpoint at the same instruction still works.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var script = null;
+var handlers = [];
+dbg.onDebuggerStatement = function (frame) {
+ function handler(i) {
+ return {hit: function (frame) { g.log += '' + i; }};
+ }
+ var f = frame.eval("f").return;
+ var s = f.script;
+ if (script === null)
+ script = s;
+ else
+ assertEq(s, script);
+
+ var offs = s.getLineOffsets(g.line0 + 3);
+ for (var i = 0; i < 3; i++) {
+ handlers[i] = handler(i);
+ for (var j = 0; j < offs.length; j++)
+ s.setBreakpoint(offs[j], handlers[i]);
+ }
+};
+
+g.eval("var line0 = Error().lineNumber;\n" +
+ "function f() {\n" + // line0 + 1
+ " log += 'x';\n" + // line0 + 2
+ " log += 'y';\n" + // line0 + 3
+ "}\n" +
+ "debugger;\n");
+assertEq(handlers.length, 3);
+
+g.log = '';
+g.f();
+assertEq(g.log, 'x012y');
+
+script.clearBreakpoint(handlers[0]);
+
+g.log = '';
+g.f();
+assertEq(g.log, 'x12y');
diff --git a/js/src/jit-test/tests/debug/breakpoint-multi-03.js b/js/src/jit-test/tests/debug/breakpoint-multi-03.js
new file mode 100644
index 0000000000..930123bbc6
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-multi-03.js
@@ -0,0 +1,27 @@
+// Multiple Debugger objects can set breakpoints at the same instruction.
+
+var g = newGlobal({newCompartment: true});
+function attach(g, i) {
+ var dbg = Debugger(g);
+ dbg.onDebuggerStatement = function (frame) {
+ var s = frame.eval("f").return.script;
+ var offs = s.getLineOffsets(g.line0 + 3);
+ for (var j = 0; j < offs.length; j++)
+ s.setBreakpoint(offs[j], {hit: function () { g.log += "" + i; }});
+ };
+}
+
+g.eval("var line0 = Error().lineNumber;\n" +
+ "function f() {\n" + // line0 + 1
+ " log += 'a';\n" + // line0 + 2
+ " log += 'b';\n" + // line0 + 3
+ "}\n");
+
+for (var i = 0; i < 3; i++)
+ attach(g, i);
+
+g.log = '';
+g.eval('debugger;');
+g.log += 'x';
+g.f();
+assertEq(g.log, 'xa012b');
diff --git a/js/src/jit-test/tests/debug/breakpoint-multi-04.js b/js/src/jit-test/tests/debug/breakpoint-multi-04.js
new file mode 100644
index 0000000000..fa407a0e17
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-multi-04.js
@@ -0,0 +1,48 @@
+// After clearing one breakpoint, another breakpoint from a different Debugger object at the same instruction still works.
+
+function test(which) {
+ var g = newGlobal({newCompartment: true});
+ g.eval("var line0 = Error().lineNumber;\n" +
+ "function f() {\n" + // line0 + 1
+ " return " + which + ";\n" + // line0 + 2
+ "}\n");
+
+ var log;
+ var scripts = [];
+ var handlers = [];
+ function addDebugger(g, i) {
+ var dbg = Debugger(g);
+ dbg.onDebuggerStatement = function (frame) {
+ var s = frame.eval("f").return.script;
+ scripts[i] = s;
+ var offs = s.getLineOffsets(g.line0 + 2);
+ var handler = {hit: function (frame) { log += '' + i; } };
+ s.setBreakpoint(0, handler);
+ handlers[i] = handler;
+ };
+ }
+
+ var expected = '';
+ for (var i = 0; i < 3; i++) {
+ addDebugger(g, i);
+ if (i !== which)
+ expected += '' + i;
+ }
+ g.eval('debugger;');
+
+ for (var i = 0; i < 3; i++)
+ assertEq(scripts[i].getBreakpoints()[0], handlers[i]);
+
+ log = '';
+ g.f();
+ assertEq(log, '012');
+
+ scripts[which].clearAllBreakpoints();
+
+ log = '';
+ g.f();
+ assertEq(log, expected);
+}
+
+for (var j = 0; j < 3; j++)
+ test(j);
diff --git a/js/src/jit-test/tests/debug/breakpoint-noncng.js b/js/src/jit-test/tests/debug/breakpoint-noncng.js
new file mode 100644
index 0000000000..2ce779cbd7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-noncng.js
@@ -0,0 +1,20 @@
+// Breakpoints work in non-compile-and-go code. Bug 738479.
+
+var g = newGlobal({newCompartment: true});
+g.s = '';
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+g.evaluate(
+ "function f() {\n" + // fscript.startLine
+ " s += 'a';\n" + // fscript.startLine + 1
+ " s += 'b';\n" + // fscript.startLine + 2
+ "}\n");
+
+var fscript = gw.makeDebuggeeValue(g.f).script;
+var handler = {hit: function (frame) { g.s += '1'; }};
+for (var pc of fscript.getLineOffsets(fscript.startLine + 2))
+ fscript.setBreakpoint(pc, handler);
+
+g.f();
+
+assertEq(g.s, "a1b");
diff --git a/js/src/jit-test/tests/debug/breakpoint-oom-01.js b/js/src/jit-test/tests/debug/breakpoint-oom-01.js
new file mode 100644
index 0000000000..4fd709ccd0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-oom-01.js
@@ -0,0 +1,41 @@
+// |jit-test| skip-if: !('oomTest' in this)
+
+// Test for OOM hitting a breakpoint in a generator.
+//
+// (The purpose is to test OOM-handling in the code that creates the
+// Debugger.Frame object and associates it with the generator object.)
+
+let g = newGlobal({newCompartment: true});
+g.eval(`\
+ function* gen(x) { // line 1
+ x++; // 2
+ yield x; // 3
+ } // 4
+`);
+
+let dbg = new Debugger;
+
+// On OOM in the debugger, propagate it to the debuggee.
+dbg.uncaughtExceptionHook = exc => exc === "out of memory" ? {throw: exc} : null;
+
+let gw = dbg.addDebuggee(g);
+let script = gw.makeDebuggeeValue(g.gen).script;
+let hits = 0;
+let handler = {
+ hit(frame) {
+ hits++;
+ print("x=", frame.environment.getVariable("x"));
+ }
+};
+for (let offset of script.getLineOffsets(2))
+ script.setBreakpoint(offset, handler);
+
+let result;
+oomTest(() => {
+ hits = 0;
+ result = g.gen(1).next();
+}, false);
+assertEq(hits, 1);
+assertEq(result.done, false);
+assertEq(result.value, 2);
+
diff --git a/js/src/jit-test/tests/debug/breakpoint-resume-01.js b/js/src/jit-test/tests/debug/breakpoint-resume-01.js
new file mode 100644
index 0000000000..ce73b53810
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-resume-01.js
@@ -0,0 +1,25 @@
+// A breakpoint handler hit method can return {throw: exc} to throw an exception.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+ function hit(frame) {
+ return {throw: frame.eval("new Error('PASS')").return};
+ }
+
+ var s = frame.script;
+ var offs = s.getLineOffsets(g.line0 + 3);
+ for (var i = 0; i < offs.length; i++)
+ s.setBreakpoint(offs[i], {hit: hit});
+};
+
+g.s = null;
+g.eval("line0 = Error().lineNumber;\n" +
+ "debugger;\n" + // line0 + 1
+ "try {\n" + // line0 + 2
+ " s = 'FAIL';\n" + // line0 + 3
+ "} catch (exc) {\n" +
+ " assertEq(exc.constructor, Error);\n" +
+ " s = exc.message;\n" +
+ "}\n");
+assertEq(g.s, 'PASS');
diff --git a/js/src/jit-test/tests/debug/breakpoint-resume-02.js b/js/src/jit-test/tests/debug/breakpoint-resume-02.js
new file mode 100644
index 0000000000..966851fa3d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-resume-02.js
@@ -0,0 +1,34 @@
+// A breakpoint handler hit method can return null to raise an uncatchable error.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+ g.log += 'D';
+
+ function hit(frame) {
+ g.log += 'H';
+ return null;
+ }
+
+ var f = frame.eval("f").return;
+ var s = f.script;
+ var offs = s.getLineOffsets(g.line0 + 3);
+ for (var i = 0; i < offs.length; i++)
+ s.setBreakpoint(offs[i], {hit: hit});
+
+ var rv = f.call();
+ assertEq(rv, null);
+ g.log += 'X';
+};
+
+g.log = '';
+g.eval("line0 = Error().lineNumber;\n" +
+ "function f() {\n" + // line0 + 1
+ " try {\n" + // line0 + 2
+ " log += '3';\n" + // line0 + 3
+ " } catch (exc) {\n" +
+ " log += '5';\n" +
+ " }\n" +
+ "}\n" +
+ "debugger;\n");
+assertEq(g.log, 'DHX');
diff --git a/js/src/jit-test/tests/debug/breakpoint-resume-03.js b/js/src/jit-test/tests/debug/breakpoint-resume-03.js
new file mode 100644
index 0000000000..84222bf198
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-resume-03.js
@@ -0,0 +1,30 @@
+// A breakpoint handler hit method can return {return: val} to force the frame to return.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+ g.log += 'D';
+
+ function hit(frame) {
+ g.log += 'H';
+ return {return: 'ok'};
+ }
+
+ var f = frame.eval("f").return;
+ var s = f.script;
+ var offs = s.getLineOffsets(g.line0 + 2);
+ for (var i = 0; i < offs.length; i++)
+ s.setBreakpoint(offs[i], {hit: hit});
+
+ var rv = f.call();
+ assertEq(rv.return, 'ok');
+ g.log += 'X';
+};
+
+g.log = '';
+g.eval("line0 = Error().lineNumber;\n" +
+ "function f() {\n" + // line0 + 1
+ " log += '2';\n" + // line0 + 2
+ "}\n" +
+ "debugger;\n");
+assertEq(g.log, 'DHX');
diff --git a/js/src/jit-test/tests/debug/breakpoint-resume-04.js b/js/src/jit-test/tests/debug/breakpoint-resume-04.js
new file mode 100644
index 0000000000..4fed89170f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-resume-04.js
@@ -0,0 +1,37 @@
+// |jit-test| error:all-jobs-completed-successfully
+// Verifiy that a breakpoints force-return queues the promise microtask to run
+// in the debuggee's job queue, not the debugger's
+// AutoDebuggerJobQueueInterruption.
+
+let g = newGlobal({ newCompartment: true });
+g.eval(`
+ async function asyncFn(x) {
+ await Promise.resolve();
+ debugger;
+ }
+ function enterDebuggee(){}
+`);
+const dbg = new Debugger(g);
+
+(async function() {
+ let it = g.asyncFn();
+
+ // Force-return when the await resumes and steps.
+ dbg.onEnterFrame = frame => {
+ dbg.onEnterFrame = undefined;
+ const bps = frame.script.getPossibleBreakpoints({ line: 4 });
+ assertEq(bps.length, 1);
+ frame.script.setBreakpoint(bps[0].offset, {
+ hit: () => ({ return: "exit" })
+ });
+ };
+
+ const result = await it;
+ assertEq(result, "exit");
+ // If execution here is resumed from the debugger's queue, this call will
+ // trigger DebuggeeWouldRun exception.
+ g.enterDebuggee();
+
+
+ throw "all-jobs-completed-successfully";
+})();
diff --git a/js/src/jit-test/tests/debug/bug-1102549.js b/js/src/jit-test/tests/debug/bug-1102549.js
new file mode 100644
index 0000000000..fbe38d9ae4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-1102549.js
@@ -0,0 +1,9 @@
+// |jit-test| error: log is not defined
+
+if (!this.Promise)
+ throw new Error('log is not defined');
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+dbg.onPromiseSettled = function (g) { log += 's'; throw "foopy"; };
+g.settlePromiseNow(new g.Promise(function (){}));
diff --git a/js/src/jit-test/tests/debug/bug-1103386.js b/js/src/jit-test/tests/debug/bug-1103386.js
new file mode 100644
index 0000000000..3db8e10a7b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-1103386.js
@@ -0,0 +1,10 @@
+load(libdir + "immutable-prototype.js");
+
+// Random chosen test: js/src/jit-test/tests/auto-regress/bug700295.js
+if (globalPrototypeChainIsMutable()) {
+ __proto__ = null;
+ Object.prototype.__proto__ = this;
+}
+
+// Random chosen test: js/src/jit-test/tests/debug/Memory-takeCensus-05.js
+Debugger(newGlobal({newCompartment: true})).memory.takeCensus();
diff --git a/js/src/jit-test/tests/debug/bug-1103813.js b/js/src/jit-test/tests/debug/bug-1103813.js
new file mode 100644
index 0000000000..0e060b32d8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-1103813.js
@@ -0,0 +1,7 @@
+// Random chosen test: js/src/jit-test/tests/debug/Source-invisible.js
+newGlobal({
+ newCompartment: true,
+ invisibleToDebugger: true
+})
+// Random chosen test: js/src/jit-test/tests/debug/Debugger-findObjects-05.js
+x = (new Debugger).findObjects()
diff --git a/js/src/jit-test/tests/debug/bug-1103817.js b/js/src/jit-test/tests/debug/bug-1103817.js
new file mode 100644
index 0000000000..f571b6f603
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-1103817.js
@@ -0,0 +1,5 @@
+// Random chosen test: js/src/jit-test/tests/debug/Source-introductionScript-04.js
+x = (new Debugger).addDebuggee(newGlobal({newCompartment: true}));
+print(x.getOwnPropertyDescriptor('Function').value.proto.script);
+// Random chosen test: js/src/jit-test/tests/debug/Memory-takeCensus-03.js
+(new Debugger).memory.takeCensus();
diff --git a/js/src/jit-test/tests/debug/bug-1110327.js b/js/src/jit-test/tests/debug/bug-1110327.js
new file mode 100644
index 0000000000..e45d88884f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-1110327.js
@@ -0,0 +1,5 @@
+// Randomly chosen test: js/src/jit-test/tests/debug/Debugger-debuggees-10.js
+x = newGlobal({newCompartment: true})
+x.t = this
+// Randomly chosen test: js/src/jit-test/tests/debug/Debugger-findObjects-06.js
+Debugger(x).findObjects()
diff --git a/js/src/jit-test/tests/debug/bug-1136806.js b/js/src/jit-test/tests/debug/bug-1136806.js
new file mode 100644
index 0000000000..104e79b1ee
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-1136806.js
@@ -0,0 +1,7 @@
+// |jit-test| allow-unhandlable-oom; allow-oom
+
+if (typeof oomAfterAllocations == "function") {
+ Debugger()
+ oomAfterAllocations(6)
+ Debugger()
+}
diff --git a/js/src/jit-test/tests/debug/bug-1192401.js b/js/src/jit-test/tests/debug/bug-1192401.js
new file mode 100644
index 0000000000..d55d22b8e5
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-1192401.js
@@ -0,0 +1,6 @@
+// |jit-test| --more-compartments
+const dbg = new Debugger();
+const g = evalcx("lazy");
+dbg.addDebuggee(g);
+dbg.memory.trackingAllocationSites = true;
+g.eval("this.alloc = {}");
diff --git a/js/src/jit-test/tests/debug/bug-1238610.js b/js/src/jit-test/tests/debug/bug-1238610.js
new file mode 100644
index 0000000000..54d519f29d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-1238610.js
@@ -0,0 +1,21 @@
+// |jit-test| allow-oom; skip-if: !('oomAfterAllocations' in this) || helperThreadCount() === 0
+
+lfcode = new Array();
+dbg = Debugger();
+dbg.onEnterFrame = function() {};
+g = newGlobal();
+lfcode.push(`
+ oomAfterAllocations(100);
+ new Number();
+ dbg.addDebuggee(g);
+`)
+file = lfcode.shift();
+loadFile(file);
+function loadFile(lfVarx) {
+ lfGlobal = newGlobal()
+ for (lfLocal in this)
+ if (!(lfLocal in lfGlobal))
+ lfGlobal[lfLocal] = this[lfLocal]
+ offThreadCompileScript(lfVarx)
+ lfGlobal.runOffThreadScript()
+}
diff --git a/js/src/jit-test/tests/debug/bug-1240090.js b/js/src/jit-test/tests/debug/bug-1240090.js
new file mode 100644
index 0000000000..20be20adb4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-1240090.js
@@ -0,0 +1,23 @@
+gczeal(2);
+g = newGlobal({newCompartment: true, disableLazyParsing: true});
+dbg = Debugger(g);
+dbg.onNewScript = function() { return function() { return this; } };
+schedulegc(10);
+g.evaluate("function one() {}");
+g.evaluate(`
+ function target () {}
+ function two2() {}
+ `, {});
+g.evaluate(`
+ function three1() {}
+ function three2() {}
+ function three3() {}
+ `, {});
+dbg.memory.takeCensus({
+ breakdown: {
+ by: "coarseType",
+ scripts: {
+ by: "filename"
+ }
+ }
+});
diff --git a/js/src/jit-test/tests/debug/bug-1248162.js b/js/src/jit-test/tests/debug/bug-1248162.js
new file mode 100644
index 0000000000..825b3376e4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-1248162.js
@@ -0,0 +1,11 @@
+// |jit-test| allow-oom; skip-if: !('oomTest' in this)
+
+// Adapted from randomly chosen test: js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-01.js
+for (var i = 0; i < 9; ++i) {
+ var dbg = new Debugger;
+ dbg.onNewGlobalObject = function() {};
+}
+// jsfunfuzz-generated
+oomTest(function() {
+ newGlobal({sameZoneAs: this});
+})
diff --git a/js/src/jit-test/tests/debug/bug-1260725.js b/js/src/jit-test/tests/debug/bug-1260725.js
new file mode 100644
index 0000000000..ce1c263f6a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-1260725.js
@@ -0,0 +1,9 @@
+// |jit-test| skip-if: !('oomTest' in this)
+
+var dbg = new Debugger;
+dbg.onNewGlobalObject = function(global) {
+ dbg.memory.takeCensus({});
+};
+oomTest(function() {
+ newGlobal({sameZoneAs: this})
+});
diff --git a/js/src/jit-test/tests/debug/bug-1260728.js b/js/src/jit-test/tests/debug/bug-1260728.js
new file mode 100644
index 0000000000..fbccb448e7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-1260728.js
@@ -0,0 +1,12 @@
+// |jit-test| error:Error
+
+var g = newGlobal();
+var dbg = new Debugger(g);
+dbg.onNewScript = function(script) {
+ fscript = script.getChildScripts()[0];
+}
+g.eval("function f(x) { arguments[0] = 3; return x }");
+fscript.setBreakpoint(0, {hit:function(frame) {
+ assertEq(frame.eval("assertEq(arguments, undefined)").return, 1);
+}});
+assertEq(g.f(1), 42);
diff --git a/js/src/jit-test/tests/debug/bug-1385844-2.js b/js/src/jit-test/tests/debug/bug-1385844-2.js
new file mode 100644
index 0000000000..50d070b41e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-1385844-2.js
@@ -0,0 +1,39 @@
+// Frame invalidatation should not follow 'debugger eval prev' links.
+// This should not fail a 'frame.isDebuggee()' assertion.
+// This version of the test requires only a single Debugger, and a single
+// debuggee global.
+
+var g = newGlobal({ newCompartment: true });
+var dbg = new Debugger(g);
+
+g.eval(`
+ function f() { debugger; }
+`);
+g.observeAll = observeAll;
+
+dbg.onDebuggerStatement = first;
+g.eval(`debugger;`);
+
+var saved;
+function first(frame) {
+ saved = frame;
+ dbg.onDebuggerStatement = second;
+ saved.eval(`f()`);
+}
+
+function second() {
+ saved.eval(`observeAll()`);
+}
+
+function observeAll() {
+ // Setting this hook causes `Debugger::updateExecutionObservabilityOfFrames`
+ // to walk the stack looking for frames running in `g` and marking them as
+ // debuggees. It should ignore 'debugger eval prev' links; if it does not, the
+ // traversal will jump from the frame for `second`'s eval directly to `saved`,
+ // the frame for the `g.eval` call. In particular, it will not visit the frame
+ // for the eval in `first`, which was never marked as a debuggee. (Simply
+ // being created for a `Debugger.Frame.prototype.eval` call doesn't
+ // necessarily mark you as a debuggee, if your behavior doesn't need to be
+ // observed.)
+ dbg.onEnterFrame = function () { };
+}
diff --git a/js/src/jit-test/tests/debug/bug-1385844.js b/js/src/jit-test/tests/debug/bug-1385844.js
new file mode 100644
index 0000000000..3f40803a07
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-1385844.js
@@ -0,0 +1,24 @@
+// Frame invalidatation should not follow 'debugger eval prev' links.
+// This should not fail a 'frame.isDebuggee()' assertion.
+
+var g1 = this;
+
+var h = newGlobal({ newCompartment: true });
+h.parent = g1;
+h.eval(`
+ var hdbg = new Debugger(parent);
+ function j() {
+ hdbg.onEnterFrame = function(frame) {};
+ }
+`);
+
+var g2 = newGlobal({ newCompartment: true });
+g2.j = h.j;
+
+var dbg = new Debugger(g2);
+var g2DO = dbg.addDebuggee(g2);
+
+dbg.onDebuggerStatement = function(f) {
+ f.eval('j()');
+};
+g2.eval('debugger');
diff --git a/js/src/jit-test/tests/debug/bug-1444604-reduced.js b/js/src/jit-test/tests/debug/bug-1444604-reduced.js
new file mode 100644
index 0000000000..6e84fef194
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-1444604-reduced.js
@@ -0,0 +1,27 @@
+// LiveSavedFrameCache should not be confused by eval-in-frame-prev links.
+
+const g = newGlobal({newCompartment: true});
+const dbg = new Debugger();
+const gDO = dbg.addDebuggee(g);
+
+g.evaluate(`
+ function inner() { debugger; }
+ function outer() { inner(); }
+`);
+
+dbg.onDebuggerStatement = function handler(frame) {
+ // Capture the JS stack, putting frames for handler, inner, and outer in the
+ // cache. Perform all captures in g's compartment, to avoid flushing the cache
+ // for compartment mismatches.
+ frame.eval('print(`in inner: ${saveStack()}`)');
+
+ // Carry out a capture in outer's context, following the eval-in-frame prev
+ // link and thus skipping over inner, removing it from the cache.
+ frame.older.eval('print(`in outer: ${saveStack()}`)');
+
+ // Capture again. inner's hasCachedSavedFrame bit should be set, but its entry
+ // has been flushed.
+ frame.eval('print(`in inner: ${saveStack()}`)');
+};
+
+g.outer();
diff --git a/js/src/jit-test/tests/debug/bug-1444604.js b/js/src/jit-test/tests/debug/bug-1444604.js
new file mode 100644
index 0000000000..d5906791db
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-1444604.js
@@ -0,0 +1,16 @@
+// Fuzz test: LiveSavedFrameCache should not be confused by eval-in-frame-prev links.
+// See bug-144604-reduced.js for a more direct version.
+
+var evalInFrame = (function (global) {
+ var dbgGlobal = newGlobal({newCompartment: true});
+ var dbg = new dbgGlobal.Debugger();
+ return function evalInFrame(upCount, code) {
+ dbg.addDebuggee(global);
+ var frame = dbg.getNewestFrame().older;
+ for (var i = 0; i < upCount; i++) {
+ }
+ var completion = frame.eval(code);
+ };
+})(this);
+enableTrackAllocations();
+evalInFrame(1, "print(a)");
diff --git a/js/src/jit-test/tests/debug/bug-1477084.js b/js/src/jit-test/tests/debug/bug-1477084.js
new file mode 100644
index 0000000000..2e45a8cc05
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-1477084.js
@@ -0,0 +1,30 @@
+// Don't assert trying to force return before the initial yield of an async function.
+
+var g = newGlobal({newCompartment: true});
+g.parent = this;
+g.parentExc = new Error("pants");
+g.eval(`
+ var dbg = new Debugger;
+ var pw = dbg.addDebuggee(parent);
+ var hits = 0;
+ dbg.onExceptionUnwind = function (frame) {
+ dbg.onExceptionUnwind = undefined;
+ return {return: undefined};
+ };
+ dbg.uncaughtExceptionHook = exc => {
+ hits++;
+ assertEq(exc instanceof TypeError, true);
+ assertEq(/force return.*before the initial yield/.test(exc.message), true);
+ return {throw: pw.makeDebuggeeValue(parentExc)};
+ };
+`);
+
+async function* method({ x: callbackfn = unresolvableReference }) {}
+try {
+ method();
+} catch (exc) {
+ g.dbg.enabled = false;
+ assertEq(exc, g.parentExc);
+}
+
+assertEq(g.hits, 1);
diff --git a/js/src/jit-test/tests/debug/bug-1564012.js b/js/src/jit-test/tests/debug/bug-1564012.js
new file mode 100644
index 0000000000..b51f9e16fc
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-1564012.js
@@ -0,0 +1,8 @@
+fullcompartmentchecks(true);
+var g = newGlobal({
+ newCompartment: true
+});
+g.eval("function*f(){debugger;yield}");
+var dbg = new Debugger(g);
+dbg.onDebuggerStatement = function(frame) {};
+g.f().next();
diff --git a/js/src/jit-test/tests/debug/bug-1565275.js b/js/src/jit-test/tests/debug/bug-1565275.js
new file mode 100644
index 0000000000..242bce3a85
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-1565275.js
@@ -0,0 +1,19 @@
+// |jit-test| skip-if: !('oomTest' in this)
+
+Object.defineProperty(this, "fuzzutils", {
+ value: {
+ evaluate: function() {},
+ }
+});
+var g = newGlobal({
+ newCompartment: true
+});
+g.parent = this;
+g.eval("(" + function() {
+ var dbg = Debugger(parent);
+ dbg.onEnterFrame = function(frame) {
+ frame.onStep = function() {}
+ }
+} + ")()");
+fuzzutils.evaluate();
+oomTest(new Function(), {expectExceptionOnFailure: false});
diff --git a/js/src/jit-test/tests/debug/bug-1572391.js b/js/src/jit-test/tests/debug/bug-1572391.js
new file mode 100644
index 0000000000..816173a154
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-1572391.js
@@ -0,0 +1,15 @@
+g = newGlobal({ newCompartment: true });
+dbg = Debugger(g);
+dbg.onDebuggerStatement = function(frame) {
+ frame.older
+}
+
+g.eval(`
+ function* countdown(recur) {
+ if (recur)
+ yield* countdown(false);
+ debugger;
+ }
+`);
+
+g.countdown(true).next()
diff --git a/js/src/jit-test/tests/debug/bug-1576862-2.js b/js/src/jit-test/tests/debug/bug-1576862-2.js
new file mode 100644
index 0000000000..6f7c31e98a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-1576862-2.js
@@ -0,0 +1,23 @@
+// |jit-test| skip-if: !('stackTest' in this)
+// Failure to rewrap an exception in Completion::fromJSResult should be propagated.
+
+var dbgGlobal = newGlobal({ newCompartment: true });
+var dbg = new dbgGlobal.Debugger();
+dbg.addDebuggee(this);
+
+function test() {
+ // Make this call's stack frame a debuggee, to ensure that
+ // slowPathOnLeaveFrame runs when this frame exits. That calls
+ // Completion::fromJSResult to capture this frame's completion value.
+ dbg.getNewestFrame();
+
+ // Throw from the non-debuggee compartment, to force Completion::fromJSResult
+ // to rewrap the exception.
+ dbgGlobal.assertEq(1,2);
+}
+
+stackTest(test, {
+ // When the bug is fixed, the failure to rewrap the exception turns the throw
+ // into a termination, so we won't get an exception.
+ expectExceptionOnFailure: false
+});
diff --git a/js/src/jit-test/tests/debug/bug-1584195.js b/js/src/jit-test/tests/debug/bug-1584195.js
new file mode 100644
index 0000000000..93aca7291d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-1584195.js
@@ -0,0 +1,31 @@
+// |jit-test| skip-if: !('gczeal' in this)
+// Bug 1584195: Debugger.Frame finalizer should't try to apply
+// IsAboutToBeFinalized to cells of other alloc kinds, whose arenas may have
+// been turned over to fresh allocations.
+
+try {
+ evaluate(`
+ var g9 = newGlobal({newCompartment: true});
+ g9.parent = this;
+ g9.eval(\`
+ var dbg = new Debugger(parent);
+ dbg.onEnterFrame = frame => {};
+ \`);
+ function lfPromise(x) {
+ return new Promise(resolve => {});
+ }
+ async function lfAsync() {
+ await lfPromise();
+ } lfAsync();
+ `);
+ gczeal(20, 2);
+ evaluate(`
+ async function lfAsync() {} lfAsync();
+ `);
+ gczeal(12, 7);
+ drainJobQueue();
+ evaluate(`
+ new { ... v22 => {
+ `);
+} catch (exc) {}
+evaluate("");
diff --git a/js/src/jit-test/tests/debug/bug-725733.js b/js/src/jit-test/tests/debug/bug-725733.js
new file mode 100644
index 0000000000..d518ceb74b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-725733.js
@@ -0,0 +1,9 @@
+// |jit-test|
+// Adding a debuggee must leave its scripts in a safe state.
+
+var g = newGlobal({newCompartment: true});
+g.eval(
+ "function f(x) { return {q: x}; }\n" +
+ "var n = f('').q;\n");
+var dbg = new Debugger(g);
+g.eval("f(0)");
diff --git a/js/src/jit-test/tests/debug/bug-800586.js b/js/src/jit-test/tests/debug/bug-800586.js
new file mode 100644
index 0000000000..06cc33300a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-800586.js
@@ -0,0 +1,7 @@
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+dbg.onDebuggerStatement = function (f) {
+ gw.executeInGlobal("eval('var x = \"A Brief History of Love\"');\n")
+};
+g.eval('debugger');
diff --git a/js/src/jit-test/tests/debug/bug-826669.js b/js/src/jit-test/tests/debug/bug-826669.js
new file mode 100644
index 0000000000..21b50441ff
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-826669.js
@@ -0,0 +1,7 @@
+gczeal(9, 2)
+var g1 = newGlobal({newCompartment: true});
+var g2 = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var g1w = dbg.addDebuggee(g1);
+g1.eval('function f() {}');
+scripts = dbg.findScripts({});
diff --git a/js/src/jit-test/tests/debug/bug-858170.js b/js/src/jit-test/tests/debug/bug-858170.js
new file mode 100644
index 0000000000..f3e1a334bc
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-858170.js
@@ -0,0 +1,7 @@
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+dbg.onNewScript = function (s) {
+ throw new Error();
+};
+dbg.uncaughtExceptionHook = function () {}
+g.eval("2 * 3");
diff --git a/js/src/jit-test/tests/debug/bug-876654.js b/js/src/jit-test/tests/debug/bug-876654.js
new file mode 100644
index 0000000000..08514695a5
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-876654.js
@@ -0,0 +1,13 @@
+// |jit-test|
+// Exercise finding a DebuggerSource cross compartment wrapper in
+// Compartment::findOutgoingEdges()
+
+let g = newGlobal({newCompartment: true});
+let dbg = new Debugger(g);
+dbg.onNewScript = function (script) {
+ var text = script.source.text;
+}
+g.eval("function f() { function g() {} }");
+gczeal(9, 1)
+var a = {}
+var b = {}
diff --git a/js/src/jit-test/tests/debug/bug1001372.js b/js/src/jit-test/tests/debug/bug1001372.js
new file mode 100644
index 0000000000..77511754be
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1001372.js
@@ -0,0 +1,21 @@
+var lfcode = new Array();
+lfcode.push = loadFile;
+lfcode.push("");
+lfcode.push("");
+lfcode.push("3");
+lfcode.push("");
+lfcode.push("");
+lfcode.push("");
+lfcode.push("");
+lfcode.push("4");
+lfcode.push("");
+lfcode.push("0");
+lfcode.push("gczeal(2);");
+lfcode.push("\
+ var g = newGlobal({newCompartment: true});\
+ g.parent = this;\
+ g.eval('new Debugger(parent).onExceptionUnwind = function() {};');\
+ ");
+function loadFile(lfVarx) {
+ evaluate(lfVarx);
+}
diff --git a/js/src/jit-test/tests/debug/bug1002797.js b/js/src/jit-test/tests/debug/bug1002797.js
new file mode 100644
index 0000000000..f7cbcfccf7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1002797.js
@@ -0,0 +1,15 @@
+var lfcode = new Array();
+lfcode.push("");
+lfcode.push("0");
+lfcode.push("newGlobal(\"1\").eval(\"(new Debugger).addAllGlobalsAsDebuggees();\");\n");
+while (true) {
+ var file = lfcode.shift(); if (file == undefined) { break; }
+ loadFile(file)
+}
+function loadFile(lfVarx) {
+ try {
+ if (lfVarx.substr(-3) != ".js" && lfVarx.length != 1) {
+ evaluate(lfVarx);
+ } else if (!isNaN(lfVarx)) {}
+ } catch (lfVare) { }
+}
diff --git a/js/src/jit-test/tests/debug/bug1004447.js b/js/src/jit-test/tests/debug/bug1004447.js
new file mode 100644
index 0000000000..18ee6757f5
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1004447.js
@@ -0,0 +1,23 @@
+// Tests that earlier try notes don't interfere with later exception handling.
+
+var g = newGlobal({newCompartment: true});
+g.debuggeeGlobal = this;
+g.eval("(" + function () {
+ dbg = new Debugger(debuggeeGlobal);
+} + ")();");
+var myObj = { p1: 'a', }
+try {
+ with(myObj) {
+ do {
+ throw value;
+ } while(false);
+ }
+} catch(e) {
+ // The above is expected to throw.
+}
+
+try {
+ if(!(p1 === 1)) { }
+} catch (e) {
+ // The above is expected to throw.
+}
diff --git a/js/src/jit-test/tests/debug/bug1006205.js b/js/src/jit-test/tests/debug/bug1006205.js
new file mode 100644
index 0000000000..dc899523a1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1006205.js
@@ -0,0 +1,20 @@
+var lfcode = new Array();
+lfcode.push = loadFile;
+lfcode.push("\
+var g = newGlobal({newCompartment: true});\
+g.debuggeeGlobal = this;\
+g.eval(\"(\" + function () {\
+ dbg = new Debugger(debuggeeGlobal);\
+ } + \")();\");\
+");
+lfcode.push("gc();");
+lfcode.push("\
+var g = newGlobal({newCompartment: true});\
+g.debuggeeGlobal = this;\
+g.eval(\"(\" + function () {\
+ dbg = new Debugger(debuggeeGlobal);\
+} + \")();\");\
+");
+function loadFile(lfVarx) {
+function newFunc(x) { new Function(x)(); }; newFunc(lfVarx);
+}
diff --git a/js/src/jit-test/tests/debug/bug1006473.js b/js/src/jit-test/tests/debug/bug1006473.js
new file mode 100644
index 0000000000..9eeefc2625
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1006473.js
@@ -0,0 +1,19 @@
+// |jit-test| error: ReferenceError
+
+var lfcode = new Array();
+lfcode.push("gczeal(4);");
+lfcode.push("setJitCompilerOption('ion.warmup.trigger', 30);");
+lfcode.push("\
+var g = newGlobal({newCompartment: true});\
+g.parent = this;\
+g.eval('function f(frame, exc) { f2 = function () { return exc; }; exc = 123; }');\
+g.eval('new Debugger(parent).onExceptionUnwind = f;');\
+var obj = int8 ('oops');\
+");
+while (true) {
+ var file = lfcode.shift(); if (file == undefined) { break; }
+ loadFile(file)
+}
+function loadFile(lfVarx) {
+ evaluate(lfVarx);
+}
diff --git a/js/src/jit-test/tests/debug/bug1106164.js b/js/src/jit-test/tests/debug/bug1106164.js
new file mode 100644
index 0000000000..7075134dda
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1106164.js
@@ -0,0 +1,17 @@
+// |jit-test| error: ReferenceError
+var g = newGlobal({newCompartment: true});
+g.parent = this;
+g.eval("new Debugger(parent).onExceptionUnwind = function () { };");
+evaluate("\
+ var tokenCodes = {};\
+ tokenCodes.continue = 0;\
+ var arr = [\
+ (0.E87 ), \
+ ];\
+ for(var reportCompare in tokenCodes) {\
+ for(var p1 in arr) {\
+ if(arr[j . arr ++ ] === p) {\
+ }\
+ }\
+ }\
+", { noScriptRval : true, isRunOnce: true });
diff --git a/js/src/jit-test/tests/debug/bug1106719.js b/js/src/jit-test/tests/debug/bug1106719.js
new file mode 100644
index 0000000000..4f42975cfb
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1106719.js
@@ -0,0 +1,12 @@
+// |jit-test| allow-oom; allow-unhandlable-oom; allow-overrecursed; skip-if: getBuildConfiguration()['android']
+// Disabled on Android due to harness problems (Bug 1532654)
+
+g = newGlobal({newCompartment: true})
+g.parent = this
+g.eval("Debugger(parent).onExceptionUnwind=(function(){})")
+gcparam("maxBytes", gcparam("gcBytes"))
+function f() {
+ f()
+ y(arguments)
+}
+f()
diff --git a/js/src/jit-test/tests/debug/bug1107525.js b/js/src/jit-test/tests/debug/bug1107525.js
new file mode 100644
index 0000000000..35a5eb466f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1107525.js
@@ -0,0 +1,9 @@
+// |jit-test| error: InternalError
+enableGeckoProfiling();
+var g = newGlobal({newCompartment: true});
+g.parent = this;
+g.eval("new Debugger(parent).onExceptionUnwind = function () { hits++; };");
+function f() {
+ var x = f();
+}
+f();
diff --git a/js/src/jit-test/tests/debug/bug1107913.js b/js/src/jit-test/tests/debug/bug1107913.js
new file mode 100644
index 0000000000..b1ba03ba2a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1107913.js
@@ -0,0 +1,7 @@
+// |jit-test| error: TypeError
+
+var g = newGlobal();
+g.parent = this;
+g.eval("new Debugger(parent).onExceptionUnwind = function () {};");
+Object.preventExtensions(this);
+evaluate("function testcase() { }", { noScriptRval : true, isRunOnce: true });
diff --git a/js/src/jit-test/tests/debug/bug1108159.js b/js/src/jit-test/tests/debug/bug1108159.js
new file mode 100644
index 0000000000..4fff79de57
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1108159.js
@@ -0,0 +1,12 @@
+// |jit-test| slow; skip-if: helperThreadCount() === 0
+
+var s = '';
+for (var i = 0; i < 70000; i++)
+ {
+ s += 'function x' + i + '() { x' + i + '(); }\n';
+}
+evaluate(s);
+var g = newGlobal({newCompartment: true});
+(new Debugger).addDebuggee(g);
+g.offThreadCompileScript('debugger;',{});
+g.runOffThreadScript();
diff --git a/js/src/jit-test/tests/debug/bug1108556.js b/js/src/jit-test/tests/debug/bug1108556.js
new file mode 100644
index 0000000000..181dbc2f99
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1108556.js
@@ -0,0 +1,10 @@
+// |jit-test| error: ReferenceError
+
+var g = newGlobal({newCompartment: true});
+g.parent = this;
+g.eval("new Debugger(parent).onExceptionUnwind = function () { hits++; };");
+evaluate('\
+var fe="v";\
+for (i=0; String.fromCharCode(0x004E); i++)\
+ fe += fe;\
+', { isRunOnce: true });
diff --git a/js/src/jit-test/tests/debug/bug1109328.js b/js/src/jit-test/tests/debug/bug1109328.js
new file mode 100644
index 0000000000..8bbadace22
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1109328.js
@@ -0,0 +1,8 @@
+try {
+ gcslice(0)(""());
+} catch (e) {}
+g = newGlobal({newCompartment: true})
+g.parent = this
+g.eval("Debugger(parent).onExceptionUnwind=(function(){})");
+gcparam("maxBytes", gcparam("maxBytes") - 8);
+gc();
diff --git a/js/src/jit-test/tests/debug/bug1109915.js b/js/src/jit-test/tests/debug/bug1109915.js
new file mode 100644
index 0000000000..be93d8cd68
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1109915.js
@@ -0,0 +1,17 @@
+var evalInFrame = (function (global) {
+ var dbgGlobal = newGlobal({newCompartment: true});
+ var dbg = new dbgGlobal.Debugger();
+ return function evalInFrame(upCount, code) {
+ dbg.addDebuggee(global);
+ var frame = dbg.getNewestFrame().older;
+ var completion = frame.eval(code);
+ };
+})(this);
+function g1(x, args) {}
+function f1(x, y, o) {
+ for (var i=0; i<50; i++) {
+ o.apply(evalInFrame(0, "x"), x);
+ }
+}
+var o1 = {apply: g1};
+assertEq(f1(3, 5, o1), undefined);
diff --git a/js/src/jit-test/tests/debug/bug1109964.js b/js/src/jit-test/tests/debug/bug1109964.js
new file mode 100644
index 0000000000..82b2760680
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1109964.js
@@ -0,0 +1,10 @@
+var dbgGlobal = newGlobal({newCompartment: true});
+var dbg = new dbgGlobal.Debugger();
+dbg.addDebuggee(this);
+function f() {
+ var a = arguments;
+ a[1];
+ dbg.getNewestFrame().eval("a");
+}
+f();
+
diff --git a/js/src/jit-test/tests/debug/bug1111199.js b/js/src/jit-test/tests/debug/bug1111199.js
new file mode 100644
index 0000000000..b7aa78d04c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1111199.js
@@ -0,0 +1,17 @@
+g = newGlobal({newCompartment: true})
+g.parent = this
+g.eval("Debugger(parent).onExceptionUnwind=(function(){})")
+try {
+function f(code) {
+ n = parseInt('', 0);
+ return g("try{}catch(e){}", n)
+}
+function g(s, n) {
+ s2 = s + s
+ d = (n - (function () {
+ return "" + this.id + eval.id;
+ } )().abstract) / 2
+ m = g(s2, d)
+}
+f("switch(''){default:break;}")
+} catch(exc1) {}
diff --git a/js/src/jit-test/tests/debug/bug1114587.js b/js/src/jit-test/tests/debug/bug1114587.js
new file mode 100644
index 0000000000..538419b038
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1114587.js
@@ -0,0 +1,26 @@
+// |jit-test| error: TypeError
+var lfcode = new Array();
+lfcode.push("");
+lfcode.push("");
+lfcode.push("\
+var g = newGlobal();\
+g.debuggeeGlobal = this;\
+g.eval('(' + function () {\
+ dbg = new Debugger(debuggeeGlobal);\
+ dbg.onExceptionUnwind = function (frame, exc) {\
+ var s = '!';\
+ for (var f = frame; f; f = f.older)\
+ s += f.callee.name;\
+ };\
+ } + ')();');\
+Debugger(17) = this;\
+");
+while (true) {
+ var file = lfcode.shift(); if (file == undefined) { break; }
+ loadFile(file)
+}
+function loadFile(lfVarx) {
+ if (lfVarx.substr(-3) != ".js" && lfVarx.length != 1) {
+ function newFunc(x) { new Function(x)(); }; newFunc(lfVarx);
+ }
+}
diff --git a/js/src/jit-test/tests/debug/bug1116103.js b/js/src/jit-test/tests/debug/bug1116103.js
new file mode 100644
index 0000000000..63703e77a2
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1116103.js
@@ -0,0 +1,11 @@
+// |jit-test| error: ReferenceError
+
+evaluate(`
+ var g = newGlobal({newCompartment: true});
+ g.parent = this;
+ g.eval('new Debugger(parent).onExceptionUnwind = function() {};');
+`)
+{
+ while (x && 0) {}
+ let x
+}
diff --git a/js/src/jit-test/tests/debug/bug1118878.js b/js/src/jit-test/tests/debug/bug1118878.js
new file mode 100644
index 0000000000..26dcc68382
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1118878.js
@@ -0,0 +1,11 @@
+
+var evalInFrame = (function (global) {
+ var dbgGlobal = newGlobal({newCompartment: true});
+ var dbg = new dbgGlobal.Debugger();
+ return function evalInFrame(upCount, code) {
+ dbg.addDebuggee(global);
+ var frame = dbg.getNewestFrame().older;
+ var completion = frame.eval(code);
+ };
+})(this);
+evaluate("for (var k in 'xxx') (function g() { Math.atan2(42); evalInFrame((0), (''), true); })();", { noScriptRval : true, isRunOnce : true });
diff --git a/js/src/jit-test/tests/debug/bug1121083.js b/js/src/jit-test/tests/debug/bug1121083.js
new file mode 100644
index 0000000000..9879df8fa6
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1121083.js
@@ -0,0 +1,15 @@
+// |jit-test| exitstatus: 6
+
+g = newGlobal({newCompartment: true});
+g.parent = this;
+g.eval("Debugger(parent).onExceptionUnwind = function () {};");
+
+function f(x) {
+ if (x === 0) {
+ return;
+ }
+ f(x - 1);
+ f(x - 1);
+}
+timeout(0.00001);
+f(100);
diff --git a/js/src/jit-test/tests/debug/bug1130768.js b/js/src/jit-test/tests/debug/bug1130768.js
new file mode 100644
index 0000000000..64f1a7c0cc
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1130768.js
@@ -0,0 +1,12 @@
+// |jit-test| error:foo
+var g = newGlobal({newCompartment: true});
+g.parent = this;
+g.eval("(" + function() {
+ var dbg = new Debugger(parent);
+ count = 0;
+ dbg.onExceptionUnwind = function(frame) {
+ frame.onPop = function() { if (count++ < 30) frame.eval("x = 3"); };
+ };
+} + ")()");
+Object.defineProperty(this, "x", {set: [].map});
+throw "foo";
diff --git a/js/src/jit-test/tests/debug/bug1133196.js b/js/src/jit-test/tests/debug/bug1133196.js
new file mode 100644
index 0000000000..60d963a8bc
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1133196.js
@@ -0,0 +1,16 @@
+// |jit-test| error: TypeError
+
+var g = newGlobal();
+g.parent = this;
+g.eval("(" + function() {
+ var dbg = new Debugger(parent);
+ dbg.onExceptionUnwind = function(frame) {
+ frame.older.onStep = function() {}
+ };
+} + ")()");
+function f() {
+ (function inner(arr) {
+ inner(arr.map);
+ })([]);
+}
+f();
diff --git a/js/src/jit-test/tests/debug/bug1147939.js b/js/src/jit-test/tests/debug/bug1147939.js
new file mode 100644
index 0000000000..a2bd2cf798
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1147939.js
@@ -0,0 +1,8 @@
+// |jit-test| error: TypeError
+var g = newGlobal();
+g.debuggeeGlobal = this;
+g.eval("(" + function () {
+ dbg = new Debugger(debuggeeGlobal);
+ dbg.onExceptionUnwind = Map;
+} + ")();");
+throw new Error("oops");
diff --git a/js/src/jit-test/tests/debug/bug1148917.js b/js/src/jit-test/tests/debug/bug1148917.js
new file mode 100644
index 0000000000..2645f913f3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1148917.js
@@ -0,0 +1,14 @@
+// |jit-test| error: Error
+
+var g = newGlobal();
+g.eval('function f(a) { evaluate("f(" + " - 1);", {newContext: true}); }');
+var dbg = new Debugger(g);
+var frames = [];
+dbg.onEnterFrame = function (frame) {
+ if (frames.length == 3)
+ return;
+ frames.push(frame);
+ for (var f of frames)
+ f.eval('a').return
+};
+g.f();
diff --git a/js/src/jit-test/tests/debug/bug1160182.js b/js/src/jit-test/tests/debug/bug1160182.js
new file mode 100644
index 0000000000..d4471a42f2
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1160182.js
@@ -0,0 +1,13 @@
+// |jit-test| skip-if: isLcovEnabled()
+
+var g = newGlobal({newCompartment: true});
+g.eval("(" + function() {
+ var o = {get x() {}};
+ this.method = Object.getOwnPropertyDescriptor(o, "x").get;
+ assertEq(isLazyFunction(method), true);
+} + ")()");
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+var scripts = dbg.findScripts();
+var methodv = gw.makeDebuggeeValue(g.method);
+assertEq(scripts.indexOf(methodv.script) != -1, true);
diff --git a/js/src/jit-test/tests/debug/bug1161332.js b/js/src/jit-test/tests/debug/bug1161332.js
new file mode 100644
index 0000000000..2b8f371495
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1161332.js
@@ -0,0 +1,16 @@
+// |jit-test| error: Error
+
+var g = newGlobal();
+g.eval('function f(a) { if (a == 1) debugger; evaluate("f(" + a + " - 1);"); }');
+var N = 2;
+var dbg = new Debugger(g);
+var frames = [];
+dbg.onEnterFrame = function (frame) {
+ frames.push(frame);
+ frame.onPop = function () { assertEq(frame.onPop, frame.onPop); };
+};
+dbg.onDebuggerStatement = function (frame) {
+ for (var f of frames)
+ f.eval('a').return;
+};
+evaluate("g.f(N);");
diff --git a/js/src/jit-test/tests/debug/bug1188334.js b/js/src/jit-test/tests/debug/bug1188334.js
new file mode 100644
index 0000000000..546fe71aa6
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1188334.js
@@ -0,0 +1,18 @@
+var evalInFrame = (function (global) {
+ var dbgGlobal = newGlobal({newCompartment: true});
+ var dbg = new dbgGlobal.Debugger();
+ return function evalInFrame(upCount, code) {
+ dbg.addDebuggee(global);
+ var frame = dbg.getNewestFrame().older;
+ var completion = frame.eval(code);
+ };
+})(this);
+function* f() {
+ {
+ let {} = "xxx";
+ yield evalInFrame(0, "x");
+ }
+}
+var gen = f();
+gen.next()
+
diff --git a/js/src/jit-test/tests/debug/bug1191499.js b/js/src/jit-test/tests/debug/bug1191499.js
new file mode 100644
index 0000000000..4cd59267f9
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1191499.js
@@ -0,0 +1,17 @@
+
+setJitCompilerOption('ion.warmup.trigger', 2);
+setJitCompilerOption('offthread-compilation.enable', 0);
+var g = newGlobal({newCompartment: true});
+var dbg2 = new Debugger;
+g.toggle = function toggle(x, d) {
+ if (d) {
+ dbg2.addDebuggee(g);
+ dbg2.getNewestFrame().environment.getVariable("x");
+ }
+};
+g.eval("" + function f(x, d) { toggle(++arguments, d); });
+g.eval("(" + function test() {
+ for (var i = 0; i < 30; i++)
+ f(42, false);
+ f(42, true);
+} + ")();");
diff --git a/js/src/jit-test/tests/debug/bug1216261.js b/js/src/jit-test/tests/debug/bug1216261.js
new file mode 100644
index 0000000000..0d98327256
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1216261.js
@@ -0,0 +1,12 @@
+// |jit-test| exitstatus: 3; skip-if: !('oomAfterAllocations' in this)
+
+var g = newGlobal();
+var dbg = new Debugger(g);
+dbg.onDebuggerStatement = function(frame) {
+ oomAfterAllocations(5);
+ // OOMs here, and possibly again in the error reporter when trying to
+ // report the OOM, so the shell just exits with code 3.
+ frame.older.eval("escaped = function() { return y }");
+}
+g.eval("function h() { debugger }");
+g.eval("(function () { var y = {p:42}; h(); yield })().next();");
diff --git a/js/src/jit-test/tests/debug/bug1219905.js b/js/src/jit-test/tests/debug/bug1219905.js
new file mode 100644
index 0000000000..5ed51a2423
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1219905.js
@@ -0,0 +1,11 @@
+// |jit-test| allow-oom; skip-if: !('oomTest' in this)
+
+// We need allow-oom here because the debugger reports an uncaught exception if
+// it hits OOM calling the exception unwind hook. This causes the shell to exit
+// with the OOM reason.
+
+var g = newGlobal({newCompartment: true});
+g.parent = this;
+g.eval("new Debugger(parent).onExceptionUnwind = function() {}");
+let finished = false;
+oomTest(() => l, false);
diff --git a/js/src/jit-test/tests/debug/bug1221378.js b/js/src/jit-test/tests/debug/bug1221378.js
new file mode 100644
index 0000000000..d8e6781dff
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1221378.js
@@ -0,0 +1,11 @@
+// Bug 1221378: allocating an array from within the object metadata callback
+// shouldn't cause re-entrant resolution of lazy prototypes.
+
+// To test for this regression, we need some way to make the code that collects
+// metadata about object allocations allocate an Array. Presently,
+// enableShellObjectMetadataCallback installs a callback that does this, but if
+// that hook gets removed (in production there's only ever one callback we
+// actually use), then the ability to make whatever metadata collection code
+// remains allocate an Array will cover this regression. For example, it could
+// be a flag that one can only set in debug builds from TestingFunctions.cpp.
+newGlobal().eval('enableShellAllocationMetadataBuilder(); Array');
diff --git a/js/src/jit-test/tests/debug/bug1232655.js b/js/src/jit-test/tests/debug/bug1232655.js
new file mode 100644
index 0000000000..c849465b4e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1232655.js
@@ -0,0 +1,5 @@
+g = newGlobal({newCompartment: true});
+g.log = "";
+Debugger(g).onDebuggerStatement = frame => frame.eval("log += this.Math.toString();");
+g.eval("(function() { with ({}) debugger })()");
+assertEq(g.log, "[object Math]");
diff --git a/js/src/jit-test/tests/debug/bug1240546.js b/js/src/jit-test/tests/debug/bug1240546.js
new file mode 100644
index 0000000000..6d548d8b92
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1240546.js
@@ -0,0 +1,9 @@
+// |jit-test| allow-oom; skip-if: !('oomAfterAllocations' in this)
+
+var g = newGlobal();
+g.debuggeeGlobal = this;
+g.eval("(" + function() {
+ oomAfterAllocations(100);
+ var dbg = Debugger(debuggeeGlobal);
+ dbg.onEnterFrame = function(frame) {}
+} + ")()");
diff --git a/js/src/jit-test/tests/debug/bug1240803.js b/js/src/jit-test/tests/debug/bug1240803.js
new file mode 100644
index 0000000000..ab1e0fb641
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1240803.js
@@ -0,0 +1,20 @@
+// |jit-test| allow-oom; skip-if: !('oomAfterAllocations' in this)
+
+(function() {
+ g = newGlobal({newCompartment: true})
+ dbg = new Debugger
+ g.toggle = function(d) {
+ if (d) {
+ dbg.addDebuggee(g);
+ dbg.getNewestFrame();
+ oomAfterAllocations(2);
+ setBreakpoint;
+ }
+ }
+ g.eval("" + function f(d) { return toggle(d); })
+ g.eval("(" + function() {
+ f(false);
+ f(true);
+ } + ")()")
+})();
+
diff --git a/js/src/jit-test/tests/debug/bug1242111.js b/js/src/jit-test/tests/debug/bug1242111.js
new file mode 100644
index 0000000000..dae0efcdab
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1242111.js
@@ -0,0 +1,8 @@
+// |jit-test| allow-oom; skip-if: !('oomAfterAllocations' in this)
+
+var g = newGlobal();
+g.debuggeeGlobal = [];
+g.eval("(" + function() {
+ oomAfterAllocations(57);
+ Debugger(debuggeeGlobal).onEnterFrame = function() {}
+} + ")()");
diff --git a/js/src/jit-test/tests/debug/bug1242798.js b/js/src/jit-test/tests/debug/bug1242798.js
new file mode 100644
index 0000000000..38a4780a9d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1242798.js
@@ -0,0 +1,14 @@
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+g.eval("" + function f(c) {
+ if (c == 0)
+ return;
+ if (c == 2)
+ debugger;
+ f(c-1);
+ for (var i = 0; i < 100; i++)
+ Debugger += newGlobal('#15: myObj.parseFloat !== parseFloat');
+});
+dbg.onDebuggerStatement = function (frame) {};
+g.eval("f(2)");
diff --git a/js/src/jit-test/tests/debug/bug1245862.js b/js/src/jit-test/tests/debug/bug1245862.js
new file mode 100644
index 0000000000..274903bc40
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1245862.js
@@ -0,0 +1,22 @@
+// |jit-test| allow-oom; skip-if: !('oomAfterAllocations' in this)
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+g.h = function h(d) {
+ if (d) {
+ dbg.addDebuggee(g);
+ var f = dbg.getNewestFrame().older;
+ f.st_p1((oomAfterAllocations(10)) + "foo = 'string of 42'");
+ }
+}
+g.eval("" + function f(d) {
+ g(d);
+});
+g.eval("" + function g(d) {
+ h(d);
+});
+g.eval("(" + function () {
+ for (i = 0; i < 5; i++)
+ f(false);
+ assertEq(f(true), "string of 42");
+} + ")();");
diff --git a/js/src/jit-test/tests/debug/bug1246605.js b/js/src/jit-test/tests/debug/bug1246605.js
new file mode 100644
index 0000000000..5bf873ee5f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1246605.js
@@ -0,0 +1,13 @@
+g = newGlobal({newCompartment: true});
+dbg = Debugger(g);
+dbg.onNewScript = function(script) { fscript = script.getChildScripts()[0]; }
+g.eval("function f() { arguments[0]; }");
+var hitBreakpoint = false;
+fscript.setBreakpoint(0, {
+ hit: function() {
+ getBacktrace({ args: 1 });
+ hitBreakpoint = true;
+ }
+});
+g.f("");
+assertEq(hitBreakpoint, true);
diff --git a/js/src/jit-test/tests/debug/bug1251919.js b/js/src/jit-test/tests/debug/bug1251919.js
new file mode 100644
index 0000000000..79fd05f890
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1251919.js
@@ -0,0 +1,10 @@
+// |jit-test| skip-if: !('oomTest' in this)
+
+// jsfunfuzz-generated
+fullcompartmentchecks(true);
+// Adapted from randomly chosen test: js/src/jit-test/tests/debug/bug-1248162.js
+var dbg = new Debugger;
+dbg.onNewGlobalObject = function() {};
+oomTest(function() {
+ newGlobal({sameZoneAs: this});
+})
diff --git a/js/src/jit-test/tests/debug/bug1252453.js b/js/src/jit-test/tests/debug/bug1252453.js
new file mode 100644
index 0000000000..626d9be624
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1252453.js
@@ -0,0 +1,21 @@
+// |jit-test| --no-threads
+
+lfcode = new Array();
+gczeal(8,2);
+lfcode.push(`
+const root = newGlobal();
+const dbg = new Debugger;
+const wrappedRoot = dbg.addDebuggee(root);
+dbg.memory.trackingAllocationSites = 1;
+root.eval("(" + function immediate() { '_' << foo } + "())");
+`);
+file = lfcode.shift();
+loadFile(file);
+function loadFile(lfVarx) {
+ try {
+ function newFunc(x) { return Function(x)(); }
+ newFunc(lfVarx)();
+ } catch (lfVare) {
+ print(lfVare)
+ }
+}
diff --git a/js/src/jit-test/tests/debug/bug1252464.js b/js/src/jit-test/tests/debug/bug1252464.js
new file mode 100644
index 0000000000..e93e5ec32f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1252464.js
@@ -0,0 +1,15 @@
+// |jit-test| error: ReferenceError
+
+g = newGlobal({newCompartment: true});
+dbg = Debugger(g);
+hits = 0;
+dbg.onNewScript = function () { return hits++; };
+assertEq(g.eval("eval('2 + 3')"), 5);
+this.gczeal(hits,1);
+dbg = Debugger(g);
+g.h = function () {
+ var env = dbg.getNewestFrame().environment;
+ dbg = 0;
+ assertThrowsInstanceOf;
+}
+g.eval("h()");
diff --git a/js/src/jit-test/tests/debug/bug1253246.js b/js/src/jit-test/tests/debug/bug1253246.js
new file mode 100644
index 0000000000..c7ab843419
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1253246.js
@@ -0,0 +1,5 @@
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+dbg.onDebuggerStatement = (frame) => { frame.eval("c = 42;"); };
+g.evalReturningScope("'use strict'; debugger;");
diff --git a/js/src/jit-test/tests/debug/bug1254123.js b/js/src/jit-test/tests/debug/bug1254123.js
new file mode 100644
index 0000000000..72f36ef3ec
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1254123.js
@@ -0,0 +1,14 @@
+// |jit-test| skip-if: !('oomTest' in this)
+
+evaluate(`
+function ERROR(msg) {
+ throw new Error("boom");
+}
+for (var i = 0; i < 9; ++ i) {
+ var dbg = new Debugger;
+ dbg.onNewGlobalObject = ERROR;
+}
+oomTest(function() {
+ newGlobal({sameZoneAs: this});
+})
+`);
diff --git a/js/src/jit-test/tests/debug/bug1254190.js b/js/src/jit-test/tests/debug/bug1254190.js
new file mode 100644
index 0000000000..3d3572b469
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1254190.js
@@ -0,0 +1,12 @@
+// |jit-test| slow; skip-if: !('oomTest' in this); allow-oom
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+dbg.onNewScript = function (s) {
+ log += dbg.findScripts({ source: s.source }).length;
+}
+log = "";
+oomTest(() => {
+ var static = newGlobal({sameZoneAs: this});
+ g.eval("(function() {})()");
+});
diff --git a/js/src/jit-test/tests/debug/bug1254578.js b/js/src/jit-test/tests/debug/bug1254578.js
new file mode 100644
index 0000000000..f36bcef601
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1254578.js
@@ -0,0 +1,19 @@
+// |jit-test| slow; skip-if: !('oomTest' in this)
+
+var g = newGlobal({newCompartment: true});
+g.debuggeeGlobal = this;
+g.eval("(" + function() {
+ dbg = new Debugger(debuggeeGlobal);
+ dbg.onExceptionUnwind = function(frame, exc) {
+ var s = '!';
+ for (var f = frame; f; f = f.older)
+ debuggeeGlobal.log += s;
+ };
+} + ")();");
+var dbg = new Debugger;
+dbg.onNewGlobalObject = function(global) {
+ get.seen = true;
+};
+oomTest(function() {
+ newGlobal({sameZoneAs: this})
+});
diff --git a/js/src/jit-test/tests/debug/bug1257045.js b/js/src/jit-test/tests/debug/bug1257045.js
new file mode 100644
index 0000000000..15b33288c8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1257045.js
@@ -0,0 +1,10 @@
+// |jit-test| skip-if: !wasmDebuggingEnabled()
+
+fullcompartmentchecks(true);
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+dbg.onNewScript = (function(script) {
+ s = script;
+})
+g.eval(`new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (func) (export "" (func 0)))')));`);
+s.source;
diff --git a/js/src/jit-test/tests/debug/bug1263899.js b/js/src/jit-test/tests/debug/bug1263899.js
new file mode 100644
index 0000000000..5392b89603
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1263899.js
@@ -0,0 +1,30 @@
+try {
+ evaluate(`
+ function runTestCase() $ERROR()
+ function $ERROR() {
+ throw Error
+ }
+ Object.defineProperty(this, "x", { value: 0 });
+ setJitCompilerOption("baseline.warmup.trigger", 0);
+ setJitCompilerOption("ion.warmup.trigger", 0)
+ `)
+ evaluate(`function f() {} f(x)`)
+ runTestCase()
+} catch (exc) {}
+evaluate(`
+ g = newGlobal({newCompartment: true})
+ g.parent = this
+ g.eval("(" + function() {
+ Debugger(parent).onExceptionUnwind = function(frame) {
+ frame.older
+ }
+ } + ")()")
+ try { $ERROR() } catch(e){}
+`)
+try {
+evaluate(`
+ x ^= null;
+ if (x = 1)
+ $ERROR()
+`);
+} catch(e) {}
diff --git a/js/src/jit-test/tests/debug/bug1264961.js b/js/src/jit-test/tests/debug/bug1264961.js
new file mode 100644
index 0000000000..c43a29504d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1264961.js
@@ -0,0 +1,23 @@
+// |jit-test| slow; skip-if: !('oomTest' in this)
+
+loadFile(`
+ var o = {}
+ var global = this;
+ var p = new Proxy(o, {
+ "deleteProperty": function (await , key) {
+ var g = newGlobal({sameZoneAs: this});
+ g.parent = global;
+ g.eval("var dbg = new Debugger(parent); dbg.onEnterFrame = function(frame) {};");
+ }
+ })
+ for (var i=0; i<100; i++);
+ assertEq(delete p.foo, true);
+`);
+function loadFile(lfVarx) {
+ var k = 0;
+ oomTest(function() {
+ // In practice a crash occurs before iteration 4000.
+ if (k++ <= 4000)
+ eval(lfVarx);
+ }, {expectExceptionOnFailure: false});
+}
diff --git a/js/src/jit-test/tests/debug/bug1266434.js b/js/src/jit-test/tests/debug/bug1266434.js
new file mode 100644
index 0000000000..34a59dac26
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1266434.js
@@ -0,0 +1,8 @@
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var g = newGlobal({newCompartment: true});
+g.evaluate("function f(x) { return x + 1; }");
+var gw = dbg.addDebuggee(g);
+gczeal(2, 1);
+var s = dbg.findScripts();
+gczeal(0);
diff --git a/js/src/jit-test/tests/debug/bug1272908.js b/js/src/jit-test/tests/debug/bug1272908.js
new file mode 100644
index 0000000000..b1e1c5aeaf
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1272908.js
@@ -0,0 +1,19 @@
+// |jit-test| slow; skip-if: !('oomTest' in this)
+
+// Adapted from randomly chosen test: js/src/jit-test/tests/modules/bug-1233915.js
+g = newGlobal({newCompartment: true});
+g.parent = this;
+g.eval("(" + function() {
+ Debugger(parent).onExceptionUnwind = function(frame) {
+ frame.eval("")
+ } } + ")()");
+// Adapted from randomly chosen test: js/src/jit-test/tests/debug/bug1254123.js
+function ERROR(msg) {
+ throw new Error("boom");
+}
+var dbg = new Debugger;
+dbg.onNewGlobalObject = ERROR;
+oomTest(function() {
+ newGlobal({sameZoneAs: this});
+})
+
diff --git a/js/src/jit-test/tests/debug/bug1275001.js b/js/src/jit-test/tests/debug/bug1275001.js
new file mode 100644
index 0000000000..52e3a1fc0d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1275001.js
@@ -0,0 +1,30 @@
+
+g = newGlobal({newCompartment: true});
+g.parent = this;
+g.eval("(" + function() {
+ Debugger(parent).onExceptionUnwind = function(frame) {
+ frame.older
+ }
+} + ")()")
+function check_one(expected, f, err) {
+ try {
+ f()
+ } catch (ex) {
+ s = ex.message;
+ assertEq(s.includes("undefined"), true);
+ }
+}
+ieval = eval
+function check(expr, expected = expr) {
+ var end, err
+ for ([end, err] of[[".random_prop", ` is undefined` ]])
+ statement = "o = {};" + expr + end;
+ cases = [
+ function() { return ieval("var undef;" + statement); },
+ Function(statement)
+ ]
+ for (f of cases)
+ check_one(expected, f, err)
+}
+check("undef");
+check("o.b");
diff --git a/js/src/jit-test/tests/debug/bug1282741.js b/js/src/jit-test/tests/debug/bug1282741.js
new file mode 100644
index 0000000000..9e75495f58
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1282741.js
@@ -0,0 +1,28 @@
+
+function removeAdd(dbg, g) {
+ dbg.removeDebuggee(g);
+ dbg.addDebuggee(g);
+ switch (dbg.removeDebuggee(g)) {}
+}
+function newGlobalDebuggerPair(toggleSeq) {
+ var g = newGlobal({newCompartment: true});
+ var dbg = new Debugger;
+ dbg.addDebuggee(g);
+ g.eval("" + function f() {
+ for (var i = 0; i < 100; i++) interruptIf(i == 95);
+ });
+ setInterruptCallback(function() {
+ return true;
+ });
+ return [g, dbg];
+}
+function testEpilogue(toggleSeq) {
+ var [g, dbg] = newGlobalDebuggerPair(toggleSeq);
+ dbg.onEnterFrame = function(f) {
+ f.onPop = function() {
+ toggleSeq(dbg, g);
+ }
+ };
+ g.f()
+}
+testEpilogue(removeAdd);
diff --git a/js/src/jit-test/tests/debug/bug1299121.js b/js/src/jit-test/tests/debug/bug1299121.js
new file mode 100644
index 0000000000..95df524cb0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1299121.js
@@ -0,0 +1,10 @@
+var g = newGlobal({newCompartment: true});
+g.parent = this;
+g.eval("(" + function() {
+ var dbg = new Debugger(parent);
+ dbg.onExceptionUnwind = function(frame) {
+ frame.eval("h = 3");
+ };
+} + ")()");
+g = function h() { }
+g();
diff --git a/js/src/jit-test/tests/debug/bug1300517.js b/js/src/jit-test/tests/debug/bug1300517.js
new file mode 100644
index 0000000000..7ea07a0c74
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1300517.js
@@ -0,0 +1,12 @@
+// |jit-test| error: ReferenceError
+g = newGlobal({newCompartment: true});
+g.log *= "";
+Debugger(g).onDebuggerStatement = frame => frame.eval("log += this.Math.toString();");
+let forceException = g.eval(`
+ (class extends class {} {
+ constructor() {
+ debugger;
+ }
+ })
+`);
+new forceException;
diff --git a/js/src/jit-test/tests/debug/bug1300528.js b/js/src/jit-test/tests/debug/bug1300528.js
new file mode 100644
index 0000000000..239a21df21
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1300528.js
@@ -0,0 +1,33 @@
+// |jit-test| skip-if: helperThreadCount() === 0
+
+load(libdir + "asserts.js");
+
+function BigInteger(a, b, c) {}
+function montConvert(x) {
+ var r = new BigInteger(null);
+ return r;
+}
+var ba = new Array();
+a = new BigInteger(ba);
+g = montConvert(a);
+var lfGlobal = newGlobal({newCompartment: true});
+for (lfLocal in this) {
+ if (!(lfLocal in lfGlobal)) {
+ lfGlobal[lfLocal] = this[lfLocal];
+ }
+}
+lfGlobal.offThreadCompileScript(`
+ var dbg = new Debugger(g);
+ dbg.onEnterFrame = function (frame) {
+ var frameThis = frame.this;
+ }
+`);
+lfGlobal.runOffThreadScript();
+assertThrowsInstanceOf(test, ReferenceError);
+function test() {
+ function check(fun, msg, todo) {
+ success = fun();
+ }
+ check(() => Object.getPrototypeOf(view) == Object.getPrototypeOf(simple));
+ typeof this;
+}
diff --git a/js/src/jit-test/tests/debug/bug1302432.js b/js/src/jit-test/tests/debug/bug1302432.js
new file mode 100644
index 0000000000..2cd5678993
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1302432.js
@@ -0,0 +1,11 @@
+setJitCompilerOption("baseline.warmup.trigger", 0);
+setJitCompilerOption('ion.warmup.trigger', 0);
+gczeal(7, 1);
+var dbgGlobal = newGlobal({newCompartment: true});
+var dbg = new dbgGlobal.Debugger();
+dbg.addDebuggee(this);
+function f(x, await = () => Array.isArray(revocable.proxy), ...get) {
+ dbg.getNewestFrame().older.eval("print(a)");
+}
+function a() {}
+for (var i = 0; i < 10; i++) f();
diff --git a/js/src/jit-test/tests/debug/bug1304553.js b/js/src/jit-test/tests/debug/bug1304553.js
new file mode 100644
index 0000000000..bf85d10c6e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1304553.js
@@ -0,0 +1,21 @@
+var dbgGlobal = newGlobal({newCompartment: true});
+var dbg = new dbgGlobal.Debugger();
+dbg.addDebuggee(this);
+function evalInFrame() {
+ var frame = dbg.getNewestFrame().older.older;
+ frame.eval("1");
+};
+var actual = '';
+try {
+ function f() {
+ evalInFrame();
+ }
+} catch (e) {}
+let m = parseModule(`
+ actual = '';
+ for (var i = 0; i < 1; ++i)
+ f(i);
+ actual;
+ `);
+m.declarationInstantiation();
+m.evaluation();
diff --git a/js/src/jit-test/tests/debug/bug1308578.js b/js/src/jit-test/tests/debug/bug1308578.js
new file mode 100644
index 0000000000..20df304eac
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1308578.js
@@ -0,0 +1,10 @@
+// |jit-test| error: ReferenceError
+
+g = newGlobal({newCompartment: true});
+g.parent = this;
+g.eval("new Debugger(parent).onExceptionUnwind = function () {}");
+a = new class extends Array {
+ constructor() {
+ for (;; ([] = p)) {}
+ }
+}
diff --git a/js/src/jit-test/tests/debug/bug1330339.js b/js/src/jit-test/tests/debug/bug1330339.js
new file mode 100644
index 0000000000..4654a93fae
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1330339.js
@@ -0,0 +1,36 @@
+// |jit-test| test-also=--wasm-compiler=optimizing; error: TestComplete
+
+if (!wasmDebuggingEnabled())
+ throw "TestComplete";
+
+let module = new WebAssembly.Module(wasmTextToBinary(`
+ (module
+ (import "global" "func" (func))
+ (func (export "test")
+ call 0 ;; calls the import, which is func #0
+ )
+ )
+`));
+
+let imports = {
+ global: {
+ func: function () {
+ let g = newGlobal({newCompartment: true});
+ let dbg = new Debugger(g);
+ dbg.onExceptionUnwind = function (frame) {
+ frame.older;
+ };
+ g.eval("throw new Error();");
+ }
+ }
+};
+let instance = new WebAssembly.Instance(module, imports);
+
+try {
+ instance.exports.test();
+ assertEq(false, true);
+} catch (e) {
+ assertEq(e.constructor.name, 'Error');
+}
+
+throw "TestComplete";
diff --git a/js/src/jit-test/tests/debug/bug1330489-sps.js b/js/src/jit-test/tests/debug/bug1330489-sps.js
new file mode 100644
index 0000000000..ffed0c6588
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1330489-sps.js
@@ -0,0 +1,44 @@
+// |jit-test| test-also=--wasm-compiler=optimizing; error: TestComplete
+
+load(libdir + "asserts.js");
+
+if (!wasmDebuggingEnabled())
+ throw "TestComplete";
+
+// Single-step profiling currently only works in the ARM simulator
+if (!getBuildConfiguration()["arm-simulator"])
+ throw "TestComplete";
+
+enableGeckoProfiling();
+enableSingleStepProfiling();
+
+var g = newGlobal({newCompartment: true});
+g.parent = this;
+g.eval("Debugger(parent).onExceptionUnwind = function () {};");
+
+let module = new WebAssembly.Module(wasmTextToBinary(`
+ (module
+ (import "a" "b" (func $imp (result i32)))
+ (memory 1 1)
+ (table 2 2 anyfunc)
+ (elem (i32.const 0) $imp $def)
+ (func $def (result i32) (i32.load (i32.const 0)))
+ (type $v2i (func (result i32)))
+ (func $call (param i32) (result i32) (call_indirect (type $v2i) (get_local 0)))
+ (export "call" (func $call))
+ )
+`));
+
+let instance = new WebAssembly.Instance(module, {
+ a: { b: function () { throw "test"; } }
+});
+
+try {
+ instance.exports.call(0);
+ assertEq(false, true);
+} catch (e) {
+ assertEq(e, "test");
+}
+
+disableGeckoProfiling();
+throw "TestComplete";
diff --git a/js/src/jit-test/tests/debug/bug1330489.js b/js/src/jit-test/tests/debug/bug1330489.js
new file mode 100644
index 0000000000..79017fa301
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1330489.js
@@ -0,0 +1,36 @@
+// |jit-test| test-also=--wasm-compiler=optimizing; error: TestComplete
+
+load(libdir + "asserts.js");
+
+if (!wasmDebuggingEnabled())
+ throw "TestComplete";
+
+var g = newGlobal({newCompartment: true});
+g.parent = this;
+g.eval("Debugger(parent).onExceptionUnwind = function () {};");
+
+let module = new WebAssembly.Module(wasmTextToBinary(`
+ (module
+ (import "a" "b" (func $imp (result i32)))
+ (memory 1 1)
+ (table 2 2 anyfunc)
+ (elem (i32.const 0) $imp $def)
+ (func $def (result i32) (i32.load (i32.const 0)))
+ (type $v2i (func (result i32)))
+ (func $call (param i32) (result i32) (call_indirect (type $v2i) (get_local 0)))
+ (export "call" (func $call))
+ )
+`));
+
+let instance = new WebAssembly.Instance(module, {
+ a: { b: function () { throw "test"; } }
+});
+
+try {
+ instance.exports.call(0);
+ assertEq(false, true);
+} catch (e) {
+ assertEq(e, "test");
+}
+
+throw "TestComplete";
diff --git a/js/src/jit-test/tests/debug/bug1330491.js b/js/src/jit-test/tests/debug/bug1330491.js
new file mode 100644
index 0000000000..7fff829ab0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1330491.js
@@ -0,0 +1,21 @@
+// |jit-test| test-also=--wasm-compiler=optimizing; error: TestComplete
+
+if (!wasmDebuggingEnabled())
+ throw "TestComplete";
+
+(function createShortLivedDebugger() {
+ var g = newGlobal({newCompartment: true});
+ g.debuggeeGlobal = this;
+ g.eval("(" + function () {
+ dbg = new Debugger(debuggeeGlobal);
+ } + ")();");
+})();
+
+let module = new WebAssembly.Module(wasmTextToBinary('(module (func))'));
+new WebAssembly.Instance(module);
+
+gcslice(1000000);
+
+new WebAssembly.Instance(module);
+
+throw "TestComplete";
diff --git a/js/src/jit-test/tests/debug/bug1331064.js b/js/src/jit-test/tests/debug/bug1331064.js
new file mode 100644
index 0000000000..4ade2305b1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1331064.js
@@ -0,0 +1,19 @@
+// |jit-test| test-also=--wasm-compiler=optimizing; exitstatus: 3; skip-if: !wasmDebuggingEnabled()
+
+load(libdir + "asserts.js");
+
+var g = newGlobal();
+g.parent = this;
+g.eval("new Debugger(parent).onExceptionUnwind = function () { some_error; };");
+
+var module = new WebAssembly.Module(wasmTextToBinary(`
+(module
+ (import $imp "a" "b" (result i32))
+ (func $call (result i32) (call 0))
+ (export "call" $call)
+)`));
+
+var instance = new WebAssembly.Instance(module, { a: { b: () => {
+ some_other_error;
+}}});
+instance.exports.call();
diff --git a/js/src/jit-test/tests/debug/bug1331592.js b/js/src/jit-test/tests/debug/bug1331592.js
new file mode 100644
index 0000000000..6282083de2
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1331592.js
@@ -0,0 +1,28 @@
+// |jit-test| test-also=--wasm-compiler=optimizing; error: TestComplete
+
+if (!wasmDebuggingEnabled())
+ throw "TestComplete";
+
+var module = new WebAssembly.Module(wasmTextToBinary(`
+ (module
+ (import "global" "func" (func (result i32)))
+ (func (export "func_0") (result i32)
+ call 0 ;; calls the import, which is func #0
+ )
+ )
+`));
+
+var dbg;
+(function (global) {
+ var dbgGlobal = newGlobal({newCompartment: true});
+ dbg = new dbgGlobal.Debugger();
+ dbg.addDebuggee(global);
+})(this);
+
+var instance = new WebAssembly.Instance(module, { global: { func: () => {
+ var frame = dbg.getNewestFrame().older;
+ frame.eval("some_error");
+}}});
+instance.exports.func_0();
+
+throw "TestComplete";
diff --git a/js/src/jit-test/tests/debug/bug1332493.js b/js/src/jit-test/tests/debug/bug1332493.js
new file mode 100644
index 0000000000..5b3ce5dda2
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1332493.js
@@ -0,0 +1,14 @@
+// |jit-test| test-also=--wasm-compiler=optimizing; exitstatus: 3; skip-if: !wasmDebuggingEnabled()
+// Checking in debug frame is initialized properly during stack overflow.
+
+var dbg;
+(function () { dbg = new (newGlobal().Debugger)(this); })();
+
+var m = new WebAssembly.Module(wasmTextToBinary(`(module
+ (import "a" "b" (result f64))
+ ;; function that allocated large space for locals on the stack
+ (func (export "f") ${Array(200).join("(param f64)")} (result f64) (call 0))
+)`));
+var f = () => i.exports.f();
+var i = new WebAssembly.Instance(m, {a: {b: f}});
+f();
diff --git a/js/src/jit-test/tests/debug/bug1343579.js b/js/src/jit-test/tests/debug/bug1343579.js
new file mode 100644
index 0000000000..3cefa8b056
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1343579.js
@@ -0,0 +1,26 @@
+// |jit-test| test-also=--wasm-compiler=optimizing; skip-if: !wasmDebuggingEnabled()
+// Checking if Debugger.Script.isInCatchScope return false for wasm.
+
+load(libdir + "wasm.js");
+
+var results;
+wasmRunWithDebugger(
+ '(module (memory 1) ' +
+ '(func $func0 i32.const 1000000 i32.load drop) ' +
+ '(func (export "test") call $func0))',
+ undefined,
+ function ({dbg, wasmScript}) {
+ results = [];
+ dbg.onExceptionUnwind = function (frame, value) {
+ if (frame.type != 'wasmcall') return;
+ var result = wasmScript.isInCatchScope(frame.offset);
+ results.push(result);
+ };
+ },
+ function ({error}) {
+ assertEq(error !== undefined, true);
+ assertEq(results.length, 2);
+ assertEq(results[0], false);
+ assertEq(results[1], false);
+ }
+);
diff --git a/js/src/jit-test/tests/debug/bug1351059.js b/js/src/jit-test/tests/debug/bug1351059.js
new file mode 100644
index 0000000000..c21595e6f3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1351059.js
@@ -0,0 +1,22 @@
+// |jit-test| skip-if: !wasmDebuggingEnabled()
+
+// Tests that onEnterFrame events are enabled when Debugger callbacks set
+// before Instance creation.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+g.parent = this;
+g.onEnterFrameCalled = false;
+g.eval(`
+ var dbg = new Debugger(parent);
+ dbg.onEnterFrame = frame => {
+ onEnterFrameCalled = true;
+ };
+`);
+var i = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(`
+ (module (func (export "f")))
+`)));
+i.exports.f();
+
+assertEq(g.onEnterFrameCalled, true);
diff --git a/js/src/jit-test/tests/debug/bug1353356.js b/js/src/jit-test/tests/debug/bug1353356.js
new file mode 100644
index 0000000000..3641f835da
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1353356.js
@@ -0,0 +1,65 @@
+// |jit-test| allow-oom; --fuzzing-safe
+
+var lfLogBuffer = `
+//corefuzz-dcd-endofdata
+//corefuzz-dcd-endofdata
+//corefuzz-dcd-endofdata
+ setJitCompilerOption("ion.warmup.trigger", 4);
+ var g = newGlobal();
+ g.debuggeeGlobal = this;
+ g.eval("(" + function () {
+ dbg = new Debugger(debuggeeGlobal);
+ dbg.onExceptionUnwind = function (frame, exc) {
+ var s = '!';
+ for (var f = frame; f; f = f.older)
+ debuggeeGlobal.log += s;
+ };
+ } + ")();");
+ j('Number.prototype.toString.call([])');
+//corefuzz-dcd-endofdata
+//corefuzz-dcd-endofdata
+//corefuzz-dcd-endofdata
+//corefuzz-dcd-selectmode 4
+//corefuzz-dcd-endofdata
+}
+//corefuzz-dcd-endofdata
+//corefuzz-dcd-selectmode 5
+//corefuzz-dcd-endofdata
+oomTest(() => i({
+ new : (true ),
+ thisprops: true
+}));
+`;
+lfLogBuffer = lfLogBuffer.split('\n');
+var lfRunTypeId = -1;
+var lfCodeBuffer = "";
+while (true) {
+ var line = lfLogBuffer.shift();
+ if (line == null) {
+ break;
+ } else if (line == "//corefuzz-dcd-endofdata") {
+ loadFile(lfCodeBuffer);
+ lfCodeBuffer = "";
+ loadFile(line);
+ } else {
+ lfCodeBuffer += line + "\n";
+ }
+}
+if (lfCodeBuffer) loadFile(lfCodeBuffer);
+function loadFile(lfVarx) {
+ try {
+ if (lfVarx.indexOf("//corefuzz-dcd-selectmode ") === 0) {
+ lfRunTypeId = parseInt(lfVarx.split(" ")[1]) % 6;
+ } else {
+ switch (lfRunTypeId) {
+ case 4:
+ oomTest(function() {
+ let m = parseModule(lfVarx);
+ });
+ break;
+ default:
+ evaluate(lfVarx);
+ }
+ }
+ } catch (lfVare) {}
+}
diff --git a/js/src/jit-test/tests/debug/bug1363233.js b/js/src/jit-test/tests/debug/bug1363233.js
new file mode 100644
index 0000000000..feacc65aff
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1363233.js
@@ -0,0 +1,14 @@
+// |jit-test| error: SyntaxError;
+g = newGlobal({newCompartment: true});
+dbg = new Debugger;
+setInterruptCallback(function () {
+ dbg.addDebuggee(g);
+ dbg.getNewestFrame();
+ return true;
+});
+g.eval("" + function f() {
+ for (var i = 0; i < 1; evaluate("class h { constructor() {} }")) {
+ interruptIf(1);
+ }
+});
+g.f();
diff --git a/js/src/jit-test/tests/debug/bug1368736.js b/js/src/jit-test/tests/debug/bug1368736.js
new file mode 100644
index 0000000000..e61a202ccb
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1368736.js
@@ -0,0 +1,19 @@
+g = newGlobal({newCompartment: true});
+hits = 0;
+Debugger(g).onDebuggerStatement = function(frame) {
+ // Set a breakpoint at the JSOP_AFTERYIELD op.
+ frame.script.setBreakpoint(117, {hit: function() { hits++; }});
+}
+g.eval(`
+function* range() {
+ eval("");
+ debugger;
+ for (var i = 0; i < 3; i++) {
+ yield i;
+ }
+}
+var iter = range();
+for (var i = 0; i < 3; i++)
+ assertEq(iter.next().value, i);
+`);
+assertEq(hits, 2);
diff --git a/js/src/jit-test/tests/debug/bug1370905.js b/js/src/jit-test/tests/debug/bug1370905.js
new file mode 100644
index 0000000000..8f8143132e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1370905.js
@@ -0,0 +1,15 @@
+// |jit-test| allow-oom; skip-if: !('oomTest' in this)
+
+function x() {
+ var global = newGlobal({sameZoneAs: this});
+ global.eval('function f() { debugger; }');
+ var debug = new Debugger(global);
+ var foo;
+ debug.onDebuggerStatement = function(frame) {
+ foo = frame.arguments[0];
+ return null;
+ };
+ global.eval('f(0)');
+}
+
+oomTest(x, false);
diff --git a/js/src/jit-test/tests/debug/bug1375447.js b/js/src/jit-test/tests/debug/bug1375447.js
new file mode 100644
index 0000000000..5801a150ed
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1375447.js
@@ -0,0 +1,18 @@
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+g.eval(`
+var line0 = Error().lineNumber;
+function f() {
+ try {
+ throw 4;
+ } catch(e) {}
+}
+`);
+var script = gw.getOwnPropertyDescriptor("f").value.script;
+var handler = {
+ hit: function() {}
+};
+var offs = script.getLineOffsets(g.line0 + 4);
+for (var i = 0; i < offs.length; i++) script.setBreakpoint(offs[i], handler);
+assertEq(g.f(), undefined);
diff --git a/js/src/jit-test/tests/debug/bug1385843.js b/js/src/jit-test/tests/debug/bug1385843.js
new file mode 100644
index 0000000000..cc34a54488
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1385843.js
@@ -0,0 +1,21 @@
+var g = newGlobal({newCompartment: true});
+g.parent = this;
+g.count = 0;
+g.eval("(" + function() {
+ var dbg = new Debugger(parent);
+ dbg.onEnterFrame = function(frame) {
+ if (count === 5)
+ dbg.onEnterFrame = undefined;
+ count++;
+ var ex = frame.eval("this").throw.unsafeDereference();
+ assertEq(ex.message.includes("call super constructor"), true);
+ }
+} + ")()");
+class Foo1 {};
+class Foo2 extends Foo1 {
+ constructor() {
+ super();
+ }
+};
+new Foo2();
+assertEq(g.count, 6);
diff --git a/js/src/jit-test/tests/debug/bug1397049.js b/js/src/jit-test/tests/debug/bug1397049.js
new file mode 100644
index 0000000000..484fc2a178
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1397049.js
@@ -0,0 +1,40 @@
+// Run debugger in its own global
+let g = newGlobal({newCompartment: true});
+g.target = this;
+g.evaluate(`
+ let d = new Debugger;
+ let gw = d.addDebuggee(target);
+
+ d.onDebuggerStatement = function(frame)
+ {
+ frame = frame.older;
+
+ let res = frame.eval("this");
+ assertEq(res.return, frame.this);
+
+ res = frame.evalWithBindings("this", {x:42});
+ assertEq(res.return, frame.this);
+ }
+`);
+
+// Debugger statement affects parse so hide in another function
+function brk() { debugger; }
+
+function f1() {
+ var temp = "string";
+ brk();
+}
+
+function f2() {
+ let temp = "string";
+ brk();
+}
+
+function f3() {
+ const temp = "string";
+ brk();
+}
+
+f1.call({});
+f2.call({});
+f3.call({});
diff --git a/js/src/jit-test/tests/debug/bug1397385.js b/js/src/jit-test/tests/debug/bug1397385.js
new file mode 100644
index 0000000000..eea2c0c01e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1397385.js
@@ -0,0 +1,16 @@
+var g = newGlobal({newCompartment: true});
+
+g.evaluate(`
+ function testInnerFun(defaultArg = 1) {
+ function innerFun(expectedThis) { return this; }
+ h();
+ return innerFun; // To prevent the JIT from optimizing out innerFun.
+ }
+`);
+
+g.h = function () {
+ var res = (new Debugger(g)).getNewestFrame().eval('assertEq(innerFun(), this)');
+ assertEq("return" in res, true);
+}
+
+g.testInnerFun();
diff --git a/js/src/jit-test/tests/debug/bug1404710.js b/js/src/jit-test/tests/debug/bug1404710.js
new file mode 100644
index 0000000000..78a8bbb5b8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1404710.js
@@ -0,0 +1,10 @@
+// |jit-test| skip-if: !('stackTest' in this)
+stackTest(new Function(`
+ var g = newGlobal();
+ var dbg = new Debugger(g);
+ dbg.onDebuggerStatement = function (frame) {
+ frame.evalWithBindings("x", {x: 2}).return;
+ };
+ g.eval("function f(y) { debugger; }");
+ g.f(3);
+`), false);
diff --git a/js/src/jit-test/tests/debug/bug1406437.js b/js/src/jit-test/tests/debug/bug1406437.js
new file mode 100644
index 0000000000..4085f80a2d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1406437.js
@@ -0,0 +1,5 @@
+var g = newGlobal({newCompartment: true});
+cloneAndExecuteScript('function f() {}', g);
+var dbg = new Debugger;
+var dg = dbg.addDebuggee(g);
+dg.getOwnPropertyDescriptor('f').value.script.source;
diff --git a/js/src/jit-test/tests/debug/bug1417961.js b/js/src/jit-test/tests/debug/bug1417961.js
new file mode 100644
index 0000000000..a10f709f71
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1417961.js
@@ -0,0 +1,31 @@
+var evalInFrame = (function evalInFrame(global) {
+ var dbgGlobal = newGlobal({newCompartment: true});
+ var dbg = new dbgGlobal.Debugger();
+ return function evalInFrame(upCount, code) {
+ dbg.addDebuggee(global);
+ var frame = dbg.getNewestFrame().older;
+ for (var i = 0; i < upCount; i++)
+ frame = frame.older;
+ var completion = frame.eval(code);
+ if (completion.throw)
+ throw 1;
+ };
+})(this);
+function f() {
+ for (var i = 0; i < 10; - i)
+ g();
+}
+function h() {
+ evalInFrame(0, "a.push(1)");
+ evalInFrame(1, "a.push(2)");
+}
+function g() {
+ h();
+}
+try {
+ f();
+} catch(e) {}
+var a = [];
+for (var i = 0; i < 3; i++)
+ g();
+assertEq(a.length, 6);
diff --git a/js/src/jit-test/tests/debug/bug1432764.js b/js/src/jit-test/tests/debug/bug1432764.js
new file mode 100644
index 0000000000..e4f7a507fb
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1432764.js
@@ -0,0 +1,16 @@
+// |jit-test| error: uncaught exception
+g = newGlobal({newCompartment: true});
+g.parent = this;
+g.eval(`
+ Debugger(parent).onExceptionUnwind = function(frame) { frame.older };
+`);
+
+var handler = {
+ has: function(tgt, key) { if (key == 'throw') { throw null; } }
+};
+
+var proxy = new Proxy({}, handler);
+
+for (let k of ['foo', 'throw']) {
+ k in proxy;
+}
diff --git a/js/src/jit-test/tests/debug/bug1434391.js b/js/src/jit-test/tests/debug/bug1434391.js
new file mode 100644
index 0000000000..86efe7970b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1434391.js
@@ -0,0 +1,8 @@
+// |jit-test| skip-if: !('oomTest' in this)
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+oomTest(function () {
+ assertEq(gw.executeInGlobal("(42).toString(0)").throw.errorMessageName, "JSMSG_BAD_RADIX");
+});
diff --git a/js/src/jit-test/tests/debug/bug1437537.js b/js/src/jit-test/tests/debug/bug1437537.js
new file mode 100644
index 0000000000..ecee9b120c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1437537.js
@@ -0,0 +1,18 @@
+// Don't assert when pausing for onStep at JSOP_EXCEPTION.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+let f = g.Function(`try { throw new Error; } catch (e) { return 'natural'; }`);
+
+let limit = -1;
+dbg.onEnterFrame = function (frame) {
+ frame.onStep = function () {
+ if (this.offset > limit) {
+ limit = this.offset;
+ return { return: 'forced' };
+ }
+ };
+};
+
+while (f() === 'forced') {
+}
diff --git a/js/src/jit-test/tests/debug/bug1480390.js b/js/src/jit-test/tests/debug/bug1480390.js
new file mode 100644
index 0000000000..7ad90c67cd
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1480390.js
@@ -0,0 +1,35 @@
+var g = newGlobal({newCompartment: true});
+g.parent = this;
+g.eval("new Debugger(parent).onExceptionUnwind = function () {};");
+
+function* wrapNoThrow() {
+ let iter = {
+ [Symbol.iterator]() {
+ return this;
+ },
+ next() {
+ return { value: 10, done: false };
+ },
+ return() { return "invalid return value" }
+ };
+ for (const i of iter)
+ yield i;
+}
+
+function foo() {
+ for (var i of [1,2,3]) {
+ for (var j of [4,5,6]) {
+ try {
+ for (const i of wrapNoThrow()) break;
+ } catch (e) {}
+ }
+ for (var j of [7,8,9]) {
+ }
+ }
+}
+
+for (var i = 0; i < 10; i++) {
+ try {
+ foo();
+ } catch(e) {}
+}
diff --git a/js/src/jit-test/tests/debug/bug1488163.js b/js/src/jit-test/tests/debug/bug1488163.js
new file mode 100644
index 0000000000..9fe9d783fb
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1488163.js
@@ -0,0 +1,17 @@
+async function f() {
+ // Enough variables that SM will eventually decide to create a lookup table
+ // for this scope.
+ var q0, q1, q2, q3, q4, q5, q6, q7, q8, q9;
+}
+
+var g = newGlobal({newCompartment: true});
+g.parent = this;
+g.eval(`
+ var dbg = new Debugger(parent);
+ dbg.onEnterFrame = frame => {};
+`);
+
+// Fragile: Trigger Shape::hashify() for the shape of the environment in f
+// under a call to GetGeneratorObjectForFrame from the Debugger.
+for (let i = 0; i < 3; i++)
+ f();
diff --git a/js/src/jit-test/tests/debug/bug1516958.js b/js/src/jit-test/tests/debug/bug1516958.js
new file mode 100644
index 0000000000..24ba0b50ef
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1516958.js
@@ -0,0 +1,3 @@
+(new (newGlobal({newCompartment:true}).Debugger)(this)).memory.trackingAllocationSites = true;
+nukeAllCCWs();
+new Date();
diff --git a/js/src/jit-test/tests/debug/bug1557343-2.js b/js/src/jit-test/tests/debug/bug1557343-2.js
new file mode 100644
index 0000000000..7eb340ff6d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1557343-2.js
@@ -0,0 +1,30 @@
+// |jit-test| --no-ggc
+// Don't crash when two Debugger.Frames refer to the same generator script, and
+// then one returns.
+
+var g = newGlobal({ newCompartment: true });
+g.eval(`
+ function* gen() {
+ debugger;
+ yield 1;
+ }
+
+ function use_gen() {
+ var gen1 = gen();
+ var gen2 = gen();
+
+ gen1.next();
+ gen2.next();
+
+ gen1.next();
+ gen2.next();
+ }
+`);
+
+var dbg = new Debugger(g);
+var frame;
+dbg.onDebuggerStatement = f => {
+ frame = f;
+};
+
+g.use_gen();
diff --git a/js/src/jit-test/tests/debug/bug1557343.js b/js/src/jit-test/tests/debug/bug1557343.js
new file mode 100644
index 0000000000..964a6d8f15
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1557343.js
@@ -0,0 +1,25 @@
+// |jit-test| --no-ggc
+
+var g = newGlobal({ newCompartment: true });
+g.eval(`
+ function* g3() {
+ debugger;
+ }
+
+ function next() {
+ g3().next();
+ }
+
+ function ggg() {
+ gc(gc);
+ }
+`);
+
+var dbg = new Debugger(g);
+var frame;
+dbg.onDebuggerStatement = f => {
+ frame = f;
+};
+
+g.next();
+g.ggg();
diff --git a/js/src/jit-test/tests/debug/bug1563051.js b/js/src/jit-test/tests/debug/bug1563051.js
new file mode 100644
index 0000000000..cf3b40019a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1563051.js
@@ -0,0 +1,11 @@
+// |jit-test| error:123
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+dbg.addDebuggee(g);
+dbg.onEnterFrame = function(frame) {
+ frame.onPop = function() {
+ dbg.removeDebuggee(g);
+ throw 123;
+ }
+}
+g.eval("(function() {})()");
diff --git a/js/src/jit-test/tests/debug/bug1586762.js b/js/src/jit-test/tests/debug/bug1586762.js
new file mode 100644
index 0000000000..416073f756
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1586762.js
@@ -0,0 +1,9 @@
+setJitCompilerOption("baseline.warmup.trigger", 0);
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+dbg.addDebuggee(g);
+g.eval("" + function f() { return 7; });
+dbg.onEnterFrame = function() {
+ dbg.removeDebuggee(g);
+}
+assertEq(g.f(), 7);
diff --git a/js/src/jit-test/tests/debug/bug1591342.js b/js/src/jit-test/tests/debug/bug1591342.js
new file mode 100644
index 0000000000..37c7b9944b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1591342.js
@@ -0,0 +1,13 @@
+// |jit-test| error: Permission denied to access object
+
+// jsfunfuzz-generated
+newGlobal({ sameCompartmentAs: this });
+nukeAllCCWs();
+// Adapted from randomly chosen testcase: js/src/jit-test/tests/debug/clear-old-analyses-02.js
+var g = newGlobal({
+ newCompartment: true
+});
+var dbg = Debugger();
+gw = dbg.addDebuggee(g);
+g.eval("" + function fib() {});
+gw.makeDebuggeeValue(g.fib).script.setBreakpoint(0, {});
diff --git a/js/src/jit-test/tests/debug/bug1602392.js b/js/src/jit-test/tests/debug/bug1602392.js
new file mode 100644
index 0000000000..e3e71bfc63
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1602392.js
@@ -0,0 +1,19 @@
+// |jit-test| error:ReferenceError: iter is not defined
+var g = newGlobal({newCompartment: true});
+g.parent = this;
+g.eval("new Debugger(parent).onExceptionUnwind = function () {};");
+function* f1() {
+ for (const x of iter) {
+ yield x;
+ }
+}
+function f2() {
+ for (var i of [1, 2, 3]) {
+ for (var j of [4, 5, 6]) {
+ for (const k of f1()) {
+ break;
+ }
+ }
+ }
+}
+f2();
diff --git a/js/src/jit-test/tests/debug/bug1644699-terminated-generator.js b/js/src/jit-test/tests/debug/bug1644699-terminated-generator.js
new file mode 100644
index 0000000000..dc87ecda8c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1644699-terminated-generator.js
@@ -0,0 +1,79 @@
+// |jit-test| exitstatus:6
+// Ensure that a frame terminated due to an interrupt in the generator
+// builtin will properly be treated as terminated.
+
+const g = newGlobal({ newCompartment: true });
+const dbg = new Debugger(g);
+g.eval(`
+var done = false;
+async function* f() {
+ await null;
+ await null;
+ await null;
+ await null;
+ done = true;
+}
+`);
+
+dbg.onEnterFrame = f => {
+ frame = f;
+ dbg.onEnterFrame = undefined;
+};
+
+setInterruptCallback(function() {
+ const stack = saveStack();
+
+ // We want to explicitly terminate inside the AsyncGeneratorNext builtin
+ // when it tries to resume execution at the 'await' in the async generator.
+ // Terminating inside AsyncGeneratorNext causes the generator to be closed,
+ // and for this test case we specifically need that to happen without
+ // entering the async generator frame because we're aiming to trigger the
+ // case where DebugAPI::onLeaveFrame does not having the opportunity to
+ // clean up the generator information associated with the Debugger.Frame.
+ if (
+ stack.parent &&
+ stack.parent.source === "self-hosted" &&
+ stack.parent.functionDisplayName === "AsyncGeneratorNext" &&
+ stack.parent.parent &&
+ stack.parent.parent.source === stack.source &&
+ stack.parent.parent.line === DRAIN_QUEUE_LINE
+ ) {
+ return false;
+ }
+
+ // Keep interrupting until we find the right place.
+ interruptIf(true);
+ return true;
+});
+
+// Run the async generator and suspend at the first await.
+const it = g.f();
+let promise = it.next();
+
+// Queue the interrupt so that it will start trying to terminate inside the
+// generator builtin.
+interruptIf(true);
+
+// Explicitly drain the queue to run the async generator to completion.
+const DRAIN_QUEUE_LINE = saveStack().line + 1;
+drainJobQueue();
+
+let threw = false;
+try {
+ // In the original testcase for this bug, this call would cause
+ // an assertion failure because the generator was closed.
+ frame.environment;
+} catch (err) {
+ threw = true;
+}
+assertEq(threw, true);
+
+// The frame here still has a GeneratorInfo datastructure because the
+// termination interrupt will cause the generator to be closed without
+// clearing that data. The frame must still be treated as terminated in
+// this case in order for the Debugger API to behave consistently.
+assertEq(frame.terminated, true);
+
+// We should never reach the end of the async generator because it will
+// have been terminated.
+assertEq(g.done, false);
diff --git a/js/src/jit-test/tests/debug/bug1647309.js b/js/src/jit-test/tests/debug/bug1647309.js
new file mode 100644
index 0000000000..e4ffcfc349
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1647309.js
@@ -0,0 +1,18 @@
+// |jit-test| skip-if: !('oomTest' in this)
+
+const g = newGlobal({ newCompartment: true });
+const dbg = new Debugger(g);
+
+// Define async generator in debuggee compartment.
+g.eval("async function* f() { }");
+
+// Use onEnterFrame hook to create generatorFrames entry.
+dbg.onEnterFrame = () => {};
+
+// Trigger failure in AsyncGeneratorNext.
+ignoreUnhandledRejections();
+oomTest(function() { g.f().next(); });
+
+// Trigger DebugAPI::onSingleStep to check generatorFrames.
+dbg.onDebuggerStatement = frame => { frame.onStep = () => {}; }
+g.eval("debugger");
diff --git a/js/src/jit-test/tests/debug/bug1675755-forceLexicalInitializationByName.js b/js/src/jit-test/tests/debug/bug1675755-forceLexicalInitializationByName.js
new file mode 100644
index 0000000000..14b1af27fc
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1675755-forceLexicalInitializationByName.js
@@ -0,0 +1,38 @@
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+
+let errorOne, errorTwo;
+
+function evalErrorStr(global, evalString) {
+ try {
+ global.evaluate(evalString);
+ return undefined;
+ } catch (e) {
+ return e.toString();
+ }
+}
+
+
+assertEq(evalErrorStr(g, "let y = IDONTEXIST;"), "ReferenceError: IDONTEXIST is not defined");
+assertEq(evalErrorStr(g, "y = 1;"),
+ "ReferenceError: can't access lexical declaration 'y' before initialization");
+
+const LINEAR_SEARCHES_MAX = 3;
+const SHAPE_CACHE_MIN_ENTRIES = 3;
+
+// Give the lexical enough properties so that it isBigEnoughForAShapeTable().
+for (i in [...Array(SHAPE_CACHE_MIN_ENTRIES)])
+ gw.executeInGlobal(`let x${i} = 1`);
+
+// Search for y just enough times to cause the next search to trigger
+// Shape::cachify().
+for (i in [...Array(LINEAR_SEARCHES_MAX - 1)])
+ gw.executeInGlobal("y");
+
+// Here we flip the uninitialized binding to undefined. But in the process, we
+// will do the lookup on y that will trigger Shape::cachify. Verify that it
+// happens in the correct compartment.
+assertEq(gw.forceLexicalInitializationByName("y"), true);
diff --git a/js/src/jit-test/tests/debug/bug1684821.js b/js/src/jit-test/tests/debug/bug1684821.js
new file mode 100644
index 0000000000..dfbefd9962
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1684821.js
@@ -0,0 +1,26 @@
+let g = newGlobal({newCompartment: true});
+let d = new Debugger(g);
+
+d.onDebuggerStatement = function (frame) {
+ frame.environment;
+};
+
+g.evaluate(`
+ function * foo() {
+ // Force CallObject + LexicalEnvironmentObject
+ let x;
+ let y = () => x;
+
+ // Force DebuggerEnvironment
+ debugger;
+
+ // Force suspend and frame snapshot
+ yield;
+
+ // Popping this frame will trigger a second snapshot
+ }
+`)
+
+let x = g.foo();
+x.next();
+x.next();
diff --git a/js/src/jit-test/tests/debug/bug911065.js b/js/src/jit-test/tests/debug/bug911065.js
new file mode 100644
index 0000000000..bec87b9344
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug911065.js
@@ -0,0 +1,34 @@
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+
+g.eval(` // 1
+var line0 = Error().lineNumber; // 2
+function f() { // 3
+ for (var x of [0]) { // 4
+ if (true == false) // 5
+ return false; // 6, aka line0 + 4
+ } // 7
+ return true; // 8
+} // 9
+`); // 10
+
+if (g.dis)
+ g.dis(g.f);
+
+var script = gw.getOwnPropertyDescriptor("f").value.script;
+
+print("Debugger's view:");
+print("----------------");
+for (var i = script.startLine; i <= script.startLine + script.lineCount; i++) {
+ print("Line " + i + ": " + JSON.stringify(script.getLineOffsets(i)));
+}
+
+var hits = 0;
+var handler = {hit: function () { hits++; }};
+var offs = script.getLineOffsets(g.line0 + 4);
+for (var i = 0; i < offs.length; i++)
+ script.setBreakpoint(offs[i], handler);
+
+assertEq(g.f(), true);
+assertEq(hits, 0);
diff --git a/js/src/jit-test/tests/debug/bug967039.js b/js/src/jit-test/tests/debug/bug967039.js
new file mode 100644
index 0000000000..fc86902c64
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug967039.js
@@ -0,0 +1,6 @@
+var g1 = newGlobal({newCompartment: true});
+var dbg = Debugger(g1);
+g1.dbg = dbg;
+g1.eval("function foo() { dbg.removeDebuggee(this); }");
+g1.eval("function f() { try { throw 3; } catch(e) { foo(); } }\n");
+g1.f();
diff --git a/js/src/jit-test/tests/debug/bug973566.js b/js/src/jit-test/tests/debug/bug973566.js
new file mode 100644
index 0000000000..5417235d93
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug973566.js
@@ -0,0 +1,7 @@
+Object.prototype[1] = 'peek';
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+dbg.onEnterFrame = function (frame) {
+ var lines = frame.script.getAllOffsets();
+};
+g.eval("1;");
diff --git a/js/src/jit-test/tests/debug/bug980585.js b/js/src/jit-test/tests/debug/bug980585.js
new file mode 100644
index 0000000000..30d8c44e52
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug980585.js
@@ -0,0 +1,10 @@
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+try {
+ g.eval("function f() { [1].map(function () {}); const x = 42; x = 43; } f();");
+} catch (e) {
+ // Ignore the syntax error.
+}
+
+dbg.findScripts();
diff --git a/js/src/jit-test/tests/debug/bug999655.js b/js/src/jit-test/tests/debug/bug999655.js
new file mode 100644
index 0000000000..756ef8d0b3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug999655.js
@@ -0,0 +1,11 @@
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+dbg.onNewScript = function(script) {
+ fscript = script.getChildScripts()[0];
+}
+g.eval("function f(x) { arguments[0] = 3; return x }");
+fscript.setBreakpoint(0, {hit:function(frame) {
+ assertEq(frame.arguments[0], 1);
+}});
+g.f(1);
diff --git a/js/src/jit-test/tests/debug/class-01.js b/js/src/jit-test/tests/debug/class-01.js
new file mode 100644
index 0000000000..66f390bd4e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/class-01.js
@@ -0,0 +1,20 @@
+// |jit-test| error: TypeError
+
+let g = newGlobal();
+let dbg = Debugger(g);
+
+let forceException = g.eval(`
+ (class extends class {} {
+ // Calling this will throw for using |this| uninitialized.
+ constructor() { }
+ })
+`);
+
+dbg.onExceptionUnwind = function() {
+ return {
+ // Force the return of an illegal value.
+ return: 1
+ }
+}
+
+new forceException;
diff --git a/js/src/jit-test/tests/debug/class-02.js b/js/src/jit-test/tests/debug/class-02.js
new file mode 100644
index 0000000000..3821eea26e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/class-02.js
@@ -0,0 +1,20 @@
+// |jit-test| error: TypeError
+
+let g = newGlobal();
+let dbg = Debugger(g);
+
+let forceException = g.eval(`
+ (class extends class {} {
+ // Calling this will return a primitive immediately.
+ constructor() { return {}; }
+ })
+`);
+
+dbg.onEnterFrame = function() {
+ return {
+ // Force the return of an illegal value.
+ return: 1
+ }
+}
+
+new forceException;
diff --git a/js/src/jit-test/tests/debug/class-03.js b/js/src/jit-test/tests/debug/class-03.js
new file mode 100644
index 0000000000..da1a4f4345
--- /dev/null
+++ b/js/src/jit-test/tests/debug/class-03.js
@@ -0,0 +1,23 @@
+// |jit-test| error: TypeError
+
+let g = newGlobal();
+let dbg = Debugger(g);
+
+let forceException = g.eval(`
+ (class extends class {} {
+ // Calling this will return a primitive immediately.
+ constructor() {
+ debugger;
+ return {};
+ }
+ })
+`);
+
+dbg.onDebuggerStatement = function() {
+ return {
+ // Force the return of an illegal value.
+ return: 1
+ }
+}
+
+new forceException;
diff --git a/js/src/jit-test/tests/debug/class-04.js b/js/src/jit-test/tests/debug/class-04.js
new file mode 100644
index 0000000000..5f772e4d38
--- /dev/null
+++ b/js/src/jit-test/tests/debug/class-04.js
@@ -0,0 +1,22 @@
+// |jit-test| error: TypeError
+
+let g = newGlobal();
+let dbg = Debugger(g);
+
+let forceException = g.eval(`
+ (class extends class {} {
+ // Calling this will return a primitive on return.
+ constructor() { return {}; }
+ })
+`);
+
+dbg.onEnterFrame = function(f) {
+ f.onPop = function() {
+ return {
+ // Force the return of an illegal value.
+ return: 1
+ }
+ }
+}
+
+new forceException;
diff --git a/js/src/jit-test/tests/debug/class-05.js b/js/src/jit-test/tests/debug/class-05.js
new file mode 100644
index 0000000000..4af7c23f83
--- /dev/null
+++ b/js/src/jit-test/tests/debug/class-05.js
@@ -0,0 +1,31 @@
+// |jit-test| error: TypeError
+
+let g = newGlobal();
+let dbg = Debugger(g);
+
+let forceException = g.eval(`
+ (class extends class {} {
+ // Calling this will return a primitive immediately.
+ constructor() {
+ debugger;
+ return {};
+ }
+ })
+`);
+
+let handler = {
+ hit() {
+ return {
+ // Force the return of an illegal value.
+ return: 1
+ }
+ }
+};
+
+dbg.onDebuggerStatement = function(frame) {
+ var line0 = frame.script.getOffsetLocation(frame.offset).lineNumber;
+ var offs = frame.script.getLineOffsets(line0 + 1);
+ frame.script.setBreakpoint(offs[0], handler);
+}
+
+new forceException;
diff --git a/js/src/jit-test/tests/debug/class-06.js b/js/src/jit-test/tests/debug/class-06.js
new file mode 100644
index 0000000000..fc6ebdbaa9
--- /dev/null
+++ b/js/src/jit-test/tests/debug/class-06.js
@@ -0,0 +1,22 @@
+// |jit-test| error: TypeError
+
+let g = newGlobal();
+let dbg = Debugger(g);
+
+let forceException = g.eval(`
+ (class extends class {} {
+ // Calling this will return a primitive immediately.
+ constructor() { return {}; }
+ })
+`);
+
+dbg.onEnterFrame = function(f) {
+ f.onStep = function() {
+ return {
+ // Force the return of an illegal value.
+ return: 1
+ }
+ }
+}
+
+new forceException;
diff --git a/js/src/jit-test/tests/debug/class-07.js b/js/src/jit-test/tests/debug/class-07.js
new file mode 100644
index 0000000000..a6a69cb79f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/class-07.js
@@ -0,0 +1,21 @@
+// |jit-test| error: ReferenceError
+
+let g = newGlobal({newCompartment: true});
+let dbg = Debugger(g);
+
+let forceException = g.eval(`
+ (class extends class {} {
+ // Calling this will return a primitive immediately.
+ constructor() { return {}; }
+ })
+`);
+
+dbg.onEnterFrame = function() {
+ return {
+ // Force the return undefined, which will throw for returning
+ // while |this| is still undefined.
+ return: undefined
+ }
+}
+print("break here");
+new forceException;
diff --git a/js/src/jit-test/tests/debug/class-08.js b/js/src/jit-test/tests/debug/class-08.js
new file mode 100644
index 0000000000..000f9ae67c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/class-08.js
@@ -0,0 +1,13 @@
+let g = newGlobal({newCompartment: true});
+let dbg = Debugger(g);
+dbg.onDebuggerStatement = function() {
+ // Force the constructor to return undefined, which should be replaced with
+ // |this| if the latter has been initialized.
+ return { return: undefined };
+}
+
+assertEq(g.eval(`
+ new (class extends class {} {
+ constructor() { super(); this.foo = 42; debugger; }
+ })
+`).foo, 42);
diff --git a/js/src/jit-test/tests/debug/class-default-constructor-01.js b/js/src/jit-test/tests/debug/class-default-constructor-01.js
new file mode 100644
index 0000000000..0c12f29545
--- /dev/null
+++ b/js/src/jit-test/tests/debug/class-default-constructor-01.js
@@ -0,0 +1,34 @@
+// We should be able to retrieve the script of a class's default constructor.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+var gDO = dbg.addDebuggee(g);
+
+// Class definitions go in the global's lexical environment, so we can't use
+// getOwnPropertyDescriptor or g.X to retrieve their constructor.
+//
+// Derived clasess use a different script from the self-hosted compartment, so
+// check those too.
+gDO.executeInGlobal(` // 1729
+ class X {}; // 1730
+ // 1731
+ // 1732
+ class Y extends X {}; // 1733
+`, { lineNumber: 1729 });
+
+function check(name, text, startLine) {
+ print(`checking ${name}`);
+ var desc = gDO.executeInGlobal(name).return;
+ assertEq(desc.class, 'Function');
+ assertEq(desc.name, name);
+ var script = desc.script;
+ assertEq(script instanceof Debugger.Script, true,
+ "default constructor's script should be available");
+ assertEq(script.startLine, startLine,
+ "default constructor's starting line should be set");
+ var source = script.source;
+ assertEq(source.text.substr(script.sourceStart, script.sourceLength), text);
+}
+
+check('X', 'class X {}', 1730);
+check('Y', 'class Y extends X {}', 1733);
diff --git a/js/src/jit-test/tests/debug/clear-old-analyses-01.js b/js/src/jit-test/tests/debug/clear-old-analyses-01.js
new file mode 100644
index 0000000000..4b580563ce
--- /dev/null
+++ b/js/src/jit-test/tests/debug/clear-old-analyses-01.js
@@ -0,0 +1,38 @@
+// |jit-test| error:AllDone
+// When we enter debug mode in a compartment, we must throw away all
+// analyses in that compartment (debug mode affects the results of
+// analysis, so they become out of date). This is true even when we would
+// otherwise be retaining jit code and its related data structures for
+// animation timing.
+
+if (typeof gcPreserveCode != "function")
+ throw('AllDone');
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+
+g.eval("" +
+ function fib(n) {
+ var a = 0, b = 1;
+ while (n-- > 0)
+ b = b+a, a = b-a;
+ return b;
+ });
+
+g.fib(20); // Cause g.fib to be jitted. This creates an analysis with
+ // debug mode off.
+
+gcPreserveCode(); // Tell the gc to preserve JIT code and analyses by
+ // default. A recent call to js::NotifyAnimationActivity
+ // could have a similar effect in real life.
+
+dbg.addDebuggee(g); // Put g in debug mode. This triggers a GC which must
+ // clear all analyses. In the original buggy code, we also
+ // release all of g's scripts' JIT code, leading to a
+ // recompilation the next time it was called.
+
+g.fib(20); // Run g.fib again, causing it to be re-jitted. If the
+ // original analysis is still present, JM will assert,
+ // because it is not in debug mode.
+
+throw('AllDone');
diff --git a/js/src/jit-test/tests/debug/clear-old-analyses-02.js b/js/src/jit-test/tests/debug/clear-old-analyses-02.js
new file mode 100644
index 0000000000..4a963ec8a5
--- /dev/null
+++ b/js/src/jit-test/tests/debug/clear-old-analyses-02.js
@@ -0,0 +1,39 @@
+// |jit-test| error:AllDone
+// When we leave debug mode in a compartment, we must throw away all
+// analyses in that compartment (debug mode affects the results of
+// analysis, so they become out of date). We cannot skip this step when
+// there are debuggee frames on the stack.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+
+g.eval("" +
+ function fib(n) {
+ var a = 0, b = 1;
+ while (n-- > 0)
+ b = b+a, a = b-a;
+ return b;
+ });
+
+
+// Cause g.fib to be jitted. This creates an analysis with debug mode on.
+g.fib(20);
+
+// Setting a breakpoint in g.f causes us to throw away the jit code, but
+// not the analysis.
+gw.makeDebuggeeValue(g.fib).script.setBreakpoint(0, { hit: function (f) { } });
+
+// Take g out of debug mode, with debuggee code on the stack. In older
+// code, this would not trigger a cleansing GC, so the script will
+// retain its analysis.
+dbg.onDebuggerStatement = function (f) {
+ dbg.removeDebuggee(g);
+};
+g.eval('debugger');
+
+// Run g.fib again, causing it to be re-jitted. If the original analysis is
+// still present, JM will assert, because it is not in debug mode.
+g.fib(20);
+
+throw('AllDone');
diff --git a/js/src/jit-test/tests/debug/dispatch-01.js b/js/src/jit-test/tests/debug/dispatch-01.js
new file mode 100644
index 0000000000..a1f062c3f9
--- /dev/null
+++ b/js/src/jit-test/tests/debug/dispatch-01.js
@@ -0,0 +1,22 @@
+// Test removing hooks during dispatch.
+
+var g = newGlobal({newCompartment: true});
+var log = '';
+
+function addDebug(n) {
+ for (var i = 0; i < n; i++) {
+ var dbg = new Debugger(g);
+ dbg.num = i;
+ dbg.onDebuggerStatement = function (stack) {
+ log += this.num + ', ';
+ this.enabled = false;
+ this.onDebuggerStatement = undefined;
+ gc();
+ };
+ }
+ dbg = null;
+}
+
+addDebug(10);
+g.eval("debugger;");
+assertEq(log, '0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ');
diff --git a/js/src/jit-test/tests/debug/execution-observability-01.js b/js/src/jit-test/tests/debug/execution-observability-01.js
new file mode 100644
index 0000000000..1f6dd13f04
--- /dev/null
+++ b/js/src/jit-test/tests/debug/execution-observability-01.js
@@ -0,0 +1,22 @@
+// For perf reasons we don't recompile all a debuggee global's scripts when
+// Debugger no longer needs to observe all execution for that global. Test that
+// things don't crash if we try to run a script with a BaselineScript that was
+// compiled with debug instrumentation when the global is no longer a debuggee.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var counter = 0;
+dbg.onDebuggerStatement = function (frame) {
+ counter++;
+ if (counter == 15)
+ dbg.onDebuggerStatement = undefined;
+};
+
+g.eval("" + function f() {
+ {
+ let inner = 42;
+ debugger;
+ inner++;
+ }
+});
+g.eval("for (var i = 0; i < 20; i++) f()");
diff --git a/js/src/jit-test/tests/debug/execution-observability-02.js b/js/src/jit-test/tests/debug/execution-observability-02.js
new file mode 100644
index 0000000000..61d943812f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/execution-observability-02.js
@@ -0,0 +1,15 @@
+// Test that baseline frames are marked as debuggee when resuming from
+// throwing.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+var hits = 0;
+dbg.onEnterFrame = function (f) { hits++; };
+
+try {
+ g.eval("for (c in (function*() { yield })()) h");
+} catch (e) {
+}
+
+assertEq(hits, 2);
diff --git a/js/src/jit-test/tests/debug/execution-observability-03.js b/js/src/jit-test/tests/debug/execution-observability-03.js
new file mode 100644
index 0000000000..d330571ec9
--- /dev/null
+++ b/js/src/jit-test/tests/debug/execution-observability-03.js
@@ -0,0 +1,17 @@
+// Tests that bare callVMs (in the delprop below) are patched correctly.
+
+var o = {};
+var global = this;
+var p = new Proxy(o, {
+ "deleteProperty": function (target, key) {
+ var g = newGlobal({newCompartment: true});
+ g.parent = global;
+ g.eval("var dbg = new Debugger(parent); dbg.onEnterFrame = function(frame) {};");
+ return true;
+ }
+});
+function test() {
+ for (var i=0; i<100; i++) {}
+ assertEq(delete p.foo, true);
+}
+test();
diff --git a/js/src/jit-test/tests/debug/execution-observability-04.js b/js/src/jit-test/tests/debug/execution-observability-04.js
new file mode 100644
index 0000000000..0e91e54371
--- /dev/null
+++ b/js/src/jit-test/tests/debug/execution-observability-04.js
@@ -0,0 +1,21 @@
+// Test that we can do debug mode OSR from the interrupt handler.
+
+var global = this;
+var hits = 0;
+setInterruptCallback(function() {
+ print("Interrupt!");
+ hits++;
+ var g = newGlobal({newCompartment: true});
+ g.parent = global;
+ g.eval("var dbg = new Debugger(parent); dbg.onEnterFrame = function(frame) {};");
+ return true;
+});
+
+function f(x) {
+ if (x > 200)
+ return;
+ interruptIf(x == 100);
+ f(x + 1);
+}
+f(0);
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/execution-observability-05.js b/js/src/jit-test/tests/debug/execution-observability-05.js
new file mode 100644
index 0000000000..1ba39f54c1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/execution-observability-05.js
@@ -0,0 +1,23 @@
+// Test that we can do debug mode OSR from the interrupt handler through an
+// on->off->on cycle.
+
+var global = this;
+var hits = 0;
+setInterruptCallback(function() {
+ print("Interrupt!");
+ hits++;
+ var g = newGlobal({newCompartment: true});
+ g.parent = global;
+ g.eval("var dbg = new Debugger(parent); dbg.onEnterFrame = function(frame) {};");
+ g.eval("dbg.removeDebuggee(parent);");
+ return true;
+});
+
+function f(x) {
+ if (x > 200)
+ return;
+ interruptIf(x == 100);
+ f(x + 1);
+}
+f(0);
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/execution-observability-06.js b/js/src/jit-test/tests/debug/execution-observability-06.js
new file mode 100644
index 0000000000..fbc3913002
--- /dev/null
+++ b/js/src/jit-test/tests/debug/execution-observability-06.js
@@ -0,0 +1,24 @@
+// Test that OSR respect debuggeeness.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+g.eval("" + function f(c) {
+ if (c == 0)
+ return;
+ if (c == 2)
+ debugger;
+ f(c-1);
+ acc = 0;
+ for (var i = 0; i < 100; i++)
+ acc += i;
+});
+
+var log = "";
+dbg.onDebuggerStatement = function (frame) {
+ frame.onPop = function f() { log += "p"; }
+};
+
+g.eval("f(2)");
+
+assertEq(log, "p");
diff --git a/js/src/jit-test/tests/debug/gc-01.js b/js/src/jit-test/tests/debug/gc-01.js
new file mode 100644
index 0000000000..949c74b576
--- /dev/null
+++ b/js/src/jit-test/tests/debug/gc-01.js
@@ -0,0 +1,20 @@
+// Debuggers with enabled hooks should not be GC'd even if they are otherwise
+// unreachable.
+
+var g = newGlobal({newCompartment: true});
+var actual = 0;
+var expected = 0;
+
+function f() {
+ for (var i = 0; i < 20; i++) {
+ var dbg = new Debugger(g);
+ dbg.num = i;
+ dbg.onDebuggerStatement = function (stack) { actual += this.num; };
+ expected += i;
+ }
+}
+
+f();
+gc(); gc(); gc();
+g.eval("debugger;");
+assertEq(actual, expected);
diff --git a/js/src/jit-test/tests/debug/gc-02.js b/js/src/jit-test/tests/debug/gc-02.js
new file mode 100644
index 0000000000..0bc05f2d72
--- /dev/null
+++ b/js/src/jit-test/tests/debug/gc-02.js
@@ -0,0 +1,28 @@
+// Dispatching an event to a debugger must keep enough of it gc-alive to avoid
+// crashing.
+
+var g = newGlobal({newCompartment: true});
+var hits;
+
+function addDebug() {
+ // The loop is here to defeat the conservative GC. :-\
+ for (var i = 0; i < 4; i++) {
+ var dbg = new Debugger(g);
+ dbg.onDebuggerStatement = function (stack) {
+ hits++;
+ this.enabled = false;
+ this.onDebuggerStatement = undefined;
+ gc();
+ };
+ if (i > 0) {
+ dbg.enabled = false;
+ dbg.onDebuggerStatement = undefined;
+ dbg = null;
+ }
+ }
+}
+
+addDebug();
+hits = 0;
+g.eval("debugger;");
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/gc-03.js b/js/src/jit-test/tests/debug/gc-03.js
new file mode 100644
index 0000000000..9c1b7c5f21
--- /dev/null
+++ b/js/src/jit-test/tests/debug/gc-03.js
@@ -0,0 +1,24 @@
+// Storing a property on a Debugger.Object protects it from GC as long as the
+// referent is alive.
+
+var g = newGlobal({newCompartment: true});
+var N = g.N = 3;
+var dbg = Debugger(g);
+
+var i = 0;
+dbg.onDebuggerStatement = function (frame) {
+ frame.arguments[0].id = i++;
+};
+g.eval("function f(x) { debugger; }");
+g.eval("var arr = [], j; for (j = 0; j < N; j++) arr[j] = {};");
+g.eval("for (j = 0; j < N; j++) f(arr[j]);");
+assertEq(i, N);
+
+gc(); gc();
+
+i = 0;
+dbg.onDebuggerStatement = function (frame) {
+ assertEq(frame.arguments[0].id, i++)
+}
+g.eval("for (j = 0; j < N; j++) f(arr[j]);");
+assertEq(i, N);
diff --git a/js/src/jit-test/tests/debug/gc-04.js b/js/src/jit-test/tests/debug/gc-04.js
new file mode 100644
index 0000000000..206be25f47
--- /dev/null
+++ b/js/src/jit-test/tests/debug/gc-04.js
@@ -0,0 +1,25 @@
+// Storing a Debugger.Object as a key in a WeakMap protects it from GC as long as
+// the referent is alive.
+
+var g = newGlobal({newCompartment: true});
+var N = g.N = 10;
+var dbg = Debugger(g);
+var cache = new WeakMap;
+
+var i = 0;
+dbg.onDebuggerStatement = function (frame) {
+ cache.set(frame.arguments[0], i++);
+};
+g.eval("function f(x) { debugger; }");
+g.eval("var arr = [], j; for (j = 0; j < N; j++) arr[j] = {};");
+g.eval("for (j = 0; j < N; j++) f(arr[j]);");
+assertEq(i, N);
+
+gc(); gc();
+
+i = 0;
+dbg.onDebuggerStatement = function (frame) {
+ assertEq(cache.get(frame.arguments[0]), i++)
+};
+g.eval("for (j = 0; j < N; j++) f(arr[j]);");
+assertEq(i, N);
diff --git a/js/src/jit-test/tests/debug/gc-05.js b/js/src/jit-test/tests/debug/gc-05.js
new file mode 100644
index 0000000000..e42b2a7272
--- /dev/null
+++ b/js/src/jit-test/tests/debug/gc-05.js
@@ -0,0 +1,41 @@
+// If a Debugger survives its debuggee, its object cache must still be swept.
+
+var g2arr = []; // non-debuggee globals
+var xarr = []; // debuggee objects
+
+var N = 4, M = 4;
+for (var i = 0; i < N; i++) {
+ var g1 = newGlobal({newCompartment: true});
+ g1.M = M;
+ var dbg = new Debugger(g1);
+ var g2 = g1.eval("newGlobal('same-compartment')");
+ g1.x = g2.eval("x = {};");
+
+ dbg.onDebuggerStatement = function (frame) { xarr.push(frame.eval("x").return); };
+ g1.eval("debugger;");
+ g2arr.push(g2);
+
+ g1 = null;
+ gc();
+}
+
+// At least some of the debuggees have probably been collected at this
+// point. It is nondeterministic, though.
+assertEq(g2arr.length, N);
+assertEq(xarr.length, N);
+
+// Try to make g2arr[i].eval eventually allocate a new object in the same
+// location as a previously gc'd object. If the object caches are not being
+// swept, the pointer coincidence will cause a Debugger.Object to be erroneously
+// reused.
+for (var i = 0; i < N; i++) {
+ var obj = xarr[i];
+ for (j = 0; j < M; j++) {
+ assertEq(obj instanceof Debugger.Object, true);
+ g2arr[i].eval("x = x.prop = {};");
+ obj = obj.getOwnPropertyDescriptor("prop").value;;
+ assertEq("seen" in obj, false);
+ obj.seen = true;
+ gc();
+ }
+}
diff --git a/js/src/jit-test/tests/debug/gc-06.js b/js/src/jit-test/tests/debug/gc-06.js
new file mode 100644
index 0000000000..16aaf12bd0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/gc-06.js
@@ -0,0 +1,6 @@
+// Debugger objects do not keep debuggee globals live.
+var dbg = new Debugger;
+for (var i = 0; i < 10; i++)
+ dbg.addDebuggee(newGlobal({newCompartment: true}));
+gc();
+assertEq(dbg.getDebuggees().length < 10, true);
diff --git a/js/src/jit-test/tests/debug/gc-07.js b/js/src/jit-test/tests/debug/gc-07.js
new file mode 100644
index 0000000000..46000d82e1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/gc-07.js
@@ -0,0 +1,9 @@
+// Don't assert with dead Debugger.Object and live cross-compartment wrapper of referent.
+var g = newGlobal({newCompartment: true});
+for (var j = 0; j < 4; j++) {
+ var dbg = new Debugger;
+ dbg.addDebuggee(g);
+ dbg.enabled = false;
+ dbg = null;
+ gc(); gc();
+}
diff --git a/js/src/jit-test/tests/debug/gc-08.js b/js/src/jit-test/tests/debug/gc-08.js
new file mode 100644
index 0000000000..68e323c821
--- /dev/null
+++ b/js/src/jit-test/tests/debug/gc-08.js
@@ -0,0 +1,22 @@
+// Debuggers with enabled onExceptionUnwind hooks should not be GC'd even if
+// they are otherwise unreachable.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var actual = 0;
+var expected = 0;
+
+function f() {
+ for (var i = 0; i < 20; i++) {
+ var dbg = new Debugger(g);
+ dbg.num = i;
+ dbg.onExceptionUnwind = function (stack, exc) { actual += this.num; };
+ expected += i;
+ }
+}
+
+f();
+gc();
+assertThrowsValue(function () { g.eval("throw 'fit';"); }, "fit");
+assertEq(actual, expected);
diff --git a/js/src/jit-test/tests/debug/gc-09.2.js b/js/src/jit-test/tests/debug/gc-09.2.js
new file mode 100644
index 0000000000..0049e32db8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/gc-09.2.js
@@ -0,0 +1,16 @@
+// Bug 717104 - Unreachable debuggee globals should not keep their debuggers
+// alive. The loop is to defeat conservative stack scanning; if the same stack
+// locations are used each time through the loop, at least three of the
+// debuggers should be collected.
+//
+// This is a slight modification of gc-09.js, which contains a cycle.
+
+for (var i = 0; i < 4; i++) {
+ var g = newGlobal({newCompartment: true});
+ var dbg = new Debugger(g);
+ dbg.onDebuggerStatement = function () { throw "FAIL"; };
+ dbg.o = makeFinalizeObserver();
+}
+
+gc();
+assertEq(finalizeCount() > 0, true);
diff --git a/js/src/jit-test/tests/debug/gc-09.js b/js/src/jit-test/tests/debug/gc-09.js
new file mode 100644
index 0000000000..0357a07862
--- /dev/null
+++ b/js/src/jit-test/tests/debug/gc-09.js
@@ -0,0 +1,15 @@
+// Bug 717104 - Unreachable debuggee globals should not keep their debuggers
+// alive. The loop is to defeat conservative stack scanning; if the same stack
+// locations are used each time through the loop, at least three of the
+// debuggers should be collected.
+
+for (var i = 0; i < 4; i++) {
+ var g = newGlobal({newCompartment: true});
+ var dbg = new Debugger(g);
+ dbg.onDebuggerStatement = function () { throw "FAIL"; };
+ dbg.o = makeFinalizeObserver();
+ dbg.loop = g; // make a cycle of strong references with dbg and g
+}
+
+gc();
+assertEq(finalizeCount() > 0, true);
diff --git a/js/src/jit-test/tests/debug/gc-10.js b/js/src/jit-test/tests/debug/gc-10.js
new file mode 100644
index 0000000000..9a3fb00b3d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/gc-10.js
@@ -0,0 +1,28 @@
+// Debugger.Frame objects should not be GC'd when doing so would have observable
+// effects.
+
+var g = newGlobal({ newCompartment: true });
+
+var log = '';
+var saved;
+
+new Debugger(g).onDebuggerStatement = function (frame) {
+
+ // Having a live onDebuggerStatement hook will (correctly) cause a Debugger to
+ // be retained, even if it is otherwise unreachable.
+ this.onDebuggerStatement = undefined;
+
+ // Give this Debugger.Frame an observable effect. It should not be GC'd.
+ frame.onPop = function () {
+ log += 'p';
+ }
+}
+
+g.parent = this;
+
+g.eval(`
+ debugger;
+ gc();
+`);
+
+assertEq(log, 'p');
diff --git a/js/src/jit-test/tests/debug/gc-compartment-01.js b/js/src/jit-test/tests/debug/gc-compartment-01.js
new file mode 100644
index 0000000000..b5d7bc09ab
--- /dev/null
+++ b/js/src/jit-test/tests/debug/gc-compartment-01.js
@@ -0,0 +1,6 @@
+// A debugger can survive per-compartment GC.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+gc(g);
+gc(this);
diff --git a/js/src/jit-test/tests/debug/gc-compartment-02.js b/js/src/jit-test/tests/debug/gc-compartment-02.js
new file mode 100644
index 0000000000..97cbc8c419
--- /dev/null
+++ b/js/src/jit-test/tests/debug/gc-compartment-02.js
@@ -0,0 +1,13 @@
+// Referents of Debugger.Objects in other compartments always survive per-compartment GC.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var arr = [];
+dbg.onDebuggerStatement = function (frame) { arr.push(frame.eval("[]").return); };
+g.eval("for (var i = 0; i < 10; i++) debugger;");
+assertEq(arr.length, 10);
+
+gc(g);
+
+for (var i = 0; i < arr.length; i++)
+ assertEq(arr[i].class, "Array");
diff --git a/js/src/jit-test/tests/debug/initarrayelem-hole-value.js b/js/src/jit-test/tests/debug/initarrayelem-hole-value.js
new file mode 100644
index 0000000000..65629e862c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/initarrayelem-hole-value.js
@@ -0,0 +1,12 @@
+var g = newGlobal({newCompartment: true});
+g.parent = this;
+g.evaluate(`var dbg = new Debugger(parent); dbg.onEnterFrame = function(){};`);
+
+function f() {
+ for (var i = 0; i < 10; i++) {
+ var arr = [1, 2, , i];
+ assertEq(2 in arr, false);
+ assertEq(3 in arr, true);
+ }
+}
+f();
diff --git a/js/src/jit-test/tests/debug/inspect-wrapped-promise.js b/js/src/jit-test/tests/debug/inspect-wrapped-promise.js
new file mode 100644
index 0000000000..2c7a862f2f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/inspect-wrapped-promise.js
@@ -0,0 +1,87 @@
+load(libdir + "asserts.js");
+
+ignoreUnhandledRejections();
+
+let g = newGlobal({newCompartment: true});
+let dbg = new Debugger();
+let gw = dbg.addDebuggee(g);
+
+g.promise1 = new Promise(() => {});
+g.promise2 = Promise.resolve(42);
+g.promise3 = Promise.reject(42);
+g.promise4 = new Object();
+g.promise5 = Promise.prototype;
+
+let promiseDO1 = gw.getOwnPropertyDescriptor('promise1').value;
+let promiseDO2 = gw.getOwnPropertyDescriptor('promise2').value;
+let promiseDO3 = gw.getOwnPropertyDescriptor('promise3').value;
+let promiseDO4 = gw.getOwnPropertyDescriptor('promise4').value;
+let promiseDO5 = gw.getOwnPropertyDescriptor('promise5').value;
+
+assertEq(promiseDO1.isPromise, true);
+assertEq(promiseDO2.isPromise, true);
+assertEq(promiseDO3.isPromise, true);
+assertEq(promiseDO4.isPromise, false);
+assertEq(promiseDO5.isPromise, false);
+
+assertEq(promiseDO1.promiseState, "pending");
+assertEq(promiseDO2.promiseState, "fulfilled");
+assertEq(promiseDO3.promiseState, "rejected");
+assertThrowsInstanceOf(function () { promiseDO4.promiseState }, TypeError);
+assertThrowsInstanceOf(function () { promiseDO5.promiseState }, TypeError);
+
+assertThrowsInstanceOf(function () { promiseDO1.promiseValue }, TypeError);
+assertEq(promiseDO2.promiseValue, 42);
+assertThrowsInstanceOf(function () { promiseDO3.promiseValue }, TypeError);
+assertThrowsInstanceOf(function () { promiseDO4.promiseValue }, TypeError);
+assertThrowsInstanceOf(function () { promiseDO5.promiseValue }, TypeError);
+
+assertThrowsInstanceOf(function () { promiseDO1.promiseReason }, TypeError);
+assertThrowsInstanceOf(function () { promiseDO2.promiseReason }, TypeError);
+assertEq(promiseDO3.promiseReason, 42);
+assertThrowsInstanceOf(function () { promiseDO4.promiseReason }, TypeError);
+assertThrowsInstanceOf(function () { promiseDO5.promiseReason }, TypeError);
+
+// Depending on whether async stacks are activated, this can be null, which
+// has typeof null.
+assertEq(typeof promiseDO1.promiseAllocationSite === "object", true);
+assertEq(typeof promiseDO2.promiseAllocationSite === "object", true);
+assertEq(typeof promiseDO3.promiseAllocationSite === "object", true);
+assertThrowsInstanceOf(function () { promiseDO4.promiseAllocationSite }, TypeError);
+assertThrowsInstanceOf(function () { promiseDO5.promiseAllocationSite }, TypeError);
+
+// Depending on whether async stacks are activated, this can be null, which
+// has typeof null.
+assertThrowsInstanceOf(function () { promiseDO1.promiseResolutionSite }, TypeError);
+assertEq(typeof promiseDO2.promiseResolutionSite === "object", true);
+assertEq(typeof promiseDO3.promiseResolutionSite === "object", true);
+assertThrowsInstanceOf(function () { promiseDO4.promiseResolutionSite }, TypeError);
+assertThrowsInstanceOf(function () { promiseDO5.promiseResolutionSite }, TypeError);
+
+assertEq(promiseDO1.promiseID, 1);
+assertEq(promiseDO2.promiseID, 2);
+assertEq(promiseDO3.promiseID, 3);
+assertThrowsInstanceOf(function () { promiseDO4.promiseID }, TypeError);
+assertThrowsInstanceOf(function () { promiseDO5.promiseID }, TypeError);
+
+assertEq(typeof promiseDO1.promiseDependentPromises, "object");
+assertEq(typeof promiseDO2.promiseDependentPromises, "object");
+assertEq(typeof promiseDO3.promiseDependentPromises, "object");
+assertThrowsInstanceOf(function () { promiseDO4.promiseDependentPromises }, TypeError);
+assertThrowsInstanceOf(function () { promiseDO5.promiseDependentPromises }, TypeError);
+
+assertEq(promiseDO1.promiseDependentPromises.length, 0);
+assertEq(promiseDO2.promiseDependentPromises.length, 0);
+assertEq(promiseDO3.promiseDependentPromises.length, 0);
+
+assertEq(typeof promiseDO1.promiseLifetime, "number");
+assertEq(typeof promiseDO2.promiseLifetime, "number");
+assertEq(typeof promiseDO3.promiseLifetime, "number");
+assertThrowsInstanceOf(function () { promiseDO4.promiseLifetime }, TypeError);
+assertThrowsInstanceOf(function () { promiseDO5.promiseLifetime }, TypeError);
+
+assertThrowsInstanceOf(function () { promiseDO1.promiseTimeToResolution }, TypeError);
+assertEq(typeof promiseDO2.promiseTimeToResolution, "number");
+assertEq(typeof promiseDO3.promiseTimeToResolution, "number");
+assertThrowsInstanceOf(function () { promiseDO4.promiseTimeToResolution }, TypeError);
+assertThrowsInstanceOf(function () { promiseDO5.promiseTimeToResolution }, TypeError);
diff --git a/js/src/jit-test/tests/debug/isAsyncFunction-isGeneratorFunction.js b/js/src/jit-test/tests/debug/isAsyncFunction-isGeneratorFunction.js
new file mode 100644
index 0000000000..fd2df0277b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/isAsyncFunction-isGeneratorFunction.js
@@ -0,0 +1,57 @@
+// Debugger.Script.prototype.isAsyncFunction, Debugger.Object.prototype.isAsyncFunction,
+// Debugger.Script.prototype.isGeneratorFunction, Debugger.Object.prototype.isGeneratorFunction
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gDO = dbg.addDebuggee(g);
+g.non_debuggee = function non_debuggee () {}
+
+function checkExpr(expr, { isAsync, isGenerator })
+{
+ print("Evaluating: " + JSON.stringify(expr));
+ let completion = gDO.executeInGlobal(expr);
+ if (completion.throw)
+ throw completion.throw.unsafeDereference();
+
+ let fn = completion.return;
+ assertEq(fn.isAsyncFunction, isAsync);
+ assertEq(fn.isGeneratorFunction, isGenerator);
+
+ // The Debugger.Object and its Debugger.Script should always agree.
+ if (fn.script) {
+ assertEq(fn.isAsyncFunction, fn.script.isAsyncFunction);
+ assertEq(fn.isGeneratorFunction, fn.script.isGeneratorFunction);
+ }
+}
+
+checkExpr('({})', { isAsync: undefined, isGenerator: undefined });
+checkExpr('non_debuggee', { isAsync: undefined, isGenerator: undefined });
+checkExpr('(function(){})', { isAsync: false, isGenerator: false });
+checkExpr('(function*(){})', { isAsync: false, isGenerator: true });
+checkExpr('(async function snerf(){})', { isAsync: true, isGenerator: false });
+checkExpr('(async function* omlu(){})', { isAsync: true, isGenerator: true });
+
+checkExpr('new Function("1+2")', { isAsync: false, isGenerator: false });
+checkExpr('Object.getPrototypeOf(function*(){}).constructor("1+2")',
+ { isAsync: false, isGenerator: true });
+checkExpr('Object.getPrototypeOf(async function(){}).constructor("1+2")',
+ { isAsync: true, isGenerator: false });
+checkExpr('Object.getPrototypeOf(async function*(){}).constructor("1+2")',
+ { isAsync: true, isGenerator: true });
+
+// Check eval scripts.
+function checkFrame(expr, type)
+{
+ var log = '';
+ dbg.onDebuggerStatement = function(frame) {
+ log += 'd';
+ assertEq(frame.type, type);
+ assertEq(frame.script.isAsyncFunction, false);
+ assertEq(frame.script.isGeneratorFunction, false);
+ }
+ gDO.executeInGlobal(expr);
+ assertEq(log, 'd');
+}
+
+checkFrame('debugger;', 'global');
+checkFrame('eval("debugger;")', 'eval');
diff --git a/js/src/jit-test/tests/debug/isError.js b/js/src/jit-test/tests/debug/isError.js
new file mode 100644
index 0000000000..253d2c76d4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/isError.js
@@ -0,0 +1,19 @@
+let g = newGlobal({newCompartment: true});
+let dbg = new Debugger();
+let gw = dbg.addDebuggee(g);
+
+g.error1 = new Error()
+g.error2 = new g.Error()
+g.error3 = new g.TypeError();
+
+let error1DO = gw.getOwnPropertyDescriptor('error1').value;
+let error2DO = gw.getOwnPropertyDescriptor('error2').value;
+let error3DO = gw.getOwnPropertyDescriptor('error3').value;
+
+assertEq(error1DO.isError, true);
+assertEq(error2DO.isError, true);
+assertEq(error3DO.isError, true);
+
+g.nonError = new Array();
+let nonErrorDO = gw.getOwnPropertyDescriptor('nonError').value;
+assertEq(nonErrorDO.isError, false);
diff --git a/js/src/jit-test/tests/debug/job-queue-01.js b/js/src/jit-test/tests/debug/job-queue-01.js
new file mode 100644
index 0000000000..259d0c2baf
--- /dev/null
+++ b/js/src/jit-test/tests/debug/job-queue-01.js
@@ -0,0 +1,122 @@
+// Debuggee promise reaction jobs should not run from debugger callbacks.
+// This covers:
+// - onDebuggerStatement
+// - onStep
+// - onEnterFrame
+// - onPop
+// - onExceptionUnwind
+// - breakpoint handlers
+// - uncaughtExceptionHook
+
+var g = newGlobal({ newCompartment: true });
+g.parent = this;
+var dbg = new Debugger;
+var gDO = dbg.addDebuggee(g);
+var log = '';
+
+// Exercise the promise machinery: resolve a promise and drain the job queue (or
+// in HTML terms, perform a microtask checkpoint). When called from a debugger
+// hook, the debuggee's microtasks should not run.
+function exercise(name) {
+ log += `${name}-handler`;
+ Promise.resolve(42).then(v => {
+ assertEq(v, 42);
+ log += `${name}-react`;
+ });
+ log += `(`;
+ drainJobQueue();
+ log += `)`;
+
+ // This should be run by the implicit microtask checkpoint after each Debugger
+ // hook call.
+ Promise.resolve(42).then(v => {
+ assertEq(v, 42);
+ log += `(${name}-tail)`;
+ });
+}
+
+dbg.onDebuggerStatement = function (frame) {
+ exercise('debugger');
+
+ frame.onStep = function () {
+ this.onStep = undefined;
+ exercise('step');
+ };
+
+ dbg.onEnterFrame = function (frame) {
+ dbg.onEnterFrame = undefined;
+ frame.onPop = function(completion) {
+ assertEq(completion.return, 'recompense');
+ exercise('pop');
+ }
+
+ exercise('enter');
+ }
+
+ dbg.onExceptionUnwind = function(frame, value) {
+ dbg.onExceptionUnwind = undefined;
+ assertEq(value, 'recidivism');
+ exercise('exception');
+ return { return: 'recompense' };
+ };
+
+ // Set a breakpoint on entry to g.breakpoint_here.
+ const script = gDO.getOwnPropertyDescriptor('breakpoint_here').value.script;
+ const handler = {
+ hit(frame) {
+ script.clearAllBreakpoints();
+ exercise('bp');
+ }
+ };
+ script.setBreakpoint(0, handler);
+
+ dbg.uncaughtExceptionHook = function (ex) {
+ assertEq(ex, 'turncoat');
+ exercise('uncaught');
+ };
+
+ // Throw an uncaught exception from the Debugger handler. This should reach
+ // uncaughtExceptionHook, but shouldn't affect the debuggee.
+ throw 'turncoat';
+};
+
+g.eval(`
+ function breakpoint_here() {
+ throw 'recidivism';
+ }
+
+ parent.log += 'eval(';
+
+ // DebuggeeWouldRun detection may prevent this callback from running at all if
+ // bug 1145201 is present. SpiderMonkey will try to run the promise reaction
+ // job from the Debugger hook's microtask checkpoint, triggering
+ // DebuggeeWouldRun. This is a little difficult to observe, since the callback
+ // never even begins execution. But it should cause the 'then' promise to be
+ // rejected, which the shell will report (if the assertEq(log, ...) doesn't
+ // kill the test first).
+
+ Promise.resolve(84).then(function(v) {
+ assertEq(v, 84);
+ parent.log += 'eval-react';
+ });
+ debugger;
+ parent.log += '...';
+ breakpoint_here();
+ parent.log += ')';
+`);
+
+log += 'main-drain('
+drainJobQueue();
+log += ')';
+
+assertEq(log, `eval(\
+debugger-handler(debugger-react)\
+uncaught-handler((debugger-tail)uncaught-react)(uncaught-tail)\
+step-handler(step-react)(step-tail)\
+...\
+enter-handler(enter-react)(enter-tail)\
+bp-handler(bp-react)(bp-tail)\
+exception-handler(exception-react)(exception-tail)\
+pop-handler(pop-react)(pop-tail)\
+)\
+main-drain(eval-react)`);
diff --git a/js/src/jit-test/tests/debug/job-queue-02.js b/js/src/jit-test/tests/debug/job-queue-02.js
new file mode 100644
index 0000000000..5f09a818af
--- /dev/null
+++ b/js/src/jit-test/tests/debug/job-queue-02.js
@@ -0,0 +1,82 @@
+// |jit-test| error: "async tests completed successfully"
+// Test that the shell's job queue doesn't skip calls to JS::JobQueueMayNotBeEmpty.
+
+// For expressions like `await 1`, or `await P` for some already-resolved
+// promise P, there's no need to suspend the async call to determine the
+// expression's value. Suspension and resumption are expensive, so it would be
+// nice if we could avoid them.
+//
+// But of course, even when the value is known, the act of suspension itself is
+// visible: an async call's first suspension returns to its (synchronous)
+// caller; subsequent suspensions let other jobs run. So in general, we can't
+// short-circuit such `await` expressions.
+//
+// However, if an async call has been resumed from the job queue (that is, this
+// isn't the initial execution, with a synchronous caller expecting a promise of
+// the call's final return value), and there are no other jobs following that,
+// then the `await`'s reaction job would run immediately following this job ---
+// which *is* indistinguishable from skipping the suspension altogether.
+//
+// A JS::JobQueue implementation may call JS::JobQueueIsEmpty to indicate to the
+// engine that the currently running job is the last job in the queue, so this
+// optimization may be considered (there are further conditions that must be met
+// as well). If the JobQueue calls JobQueueIsEmpty, then it must also call
+// JS::JobQueueMayNotBeEmpty when jobs are enqueued, to indicate when the
+// opportunity has passed.
+
+var log = '';
+async function f(label, k) {
+ log += label + '1';
+ await 1;
+ log += label + '2';
+ await 1;
+ log += label + '3';
+
+ return k();
+}
+
+// Call `f` with `label` and `k`. If `skippable` is true, exercise the path that
+// skips the suspension and resumption; otherwise exercise the
+// non-short-circuited path.
+function test(skippable, label, k) {
+ var resolve;
+ (new Promise(r => { resolve = r; }))
+ .then(v => { log += v + 't'; });
+ assertEq(log, '');
+ f(label, k);
+ // job queue now: f(label)'s first await's continuation
+ assertEq(log, label + '1');
+
+ if (!skippable) {
+ resolve('p');
+ assertEq(log, label + '1');
+ // job queue now: f(label)'s first await's continuation, explicit promise's reaction
+ }
+
+ // Resuming f(label) will reach the second await, which should skip suspension
+ // or not, depending on whether we resolved that promise.
+}
+
+// SpiderMonkey's internal 'queue is empty' flag is initially false, even though
+// the queue is initially empty, because we don't yet know whether the embedding
+// is going to participate in the optimization by calling
+// JS::JobQueueMayNotBeEmpty and JS::JobQueueIsEmpty. But since the shell uses
+// SpiderMonkey's internal job queue implementation, this call to
+// `drainJobQueue` calls `JS::JobQueueIsEmpty`, and we are ready to play.
+Promise.resolve(42).then(v => assertEq(v, 42));
+drainJobQueue();
+
+log = '';
+test(true, 'b', continuation1);
+
+function continuation1() {
+ assertEq(log, 'b1b2b3');
+
+ log = '';
+ test(false, 'c', continuation2);
+}
+
+function continuation2() {
+ assertEq(log, 'c1c2ptc3');
+ throw "async tests completed successfully"; // proof that we actually finished
+}
diff --git a/js/src/jit-test/tests/debug/job-queue-03.js b/js/src/jit-test/tests/debug/job-queue-03.js
new file mode 100644
index 0000000000..7e3ce27ea4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/job-queue-03.js
@@ -0,0 +1,173 @@
+// Multiple debuggers get their job queues drained after each hook.
+// This covers:
+// - onDebuggerStatement
+// - onStep
+// - onEnterFrame
+// - onPop
+// - onExceptionUnwind
+// - breakpoint handlers
+// - uncaughtExceptionHook
+
+const g = newGlobal({ newCompartment: true });
+g.parent = this;
+
+var log = '';
+let expected_throws = 0;
+
+function setup(global, label) {
+ const dbg = new Debugger;
+ dbg.gDO = dbg.addDebuggee(global);
+ dbg.log = '';
+
+ dbg.onDebuggerStatement = function (frame) {
+ // Exercise the promise machinery: resolve a promise and perform a microtask
+ // checkpoint. When called from a debugger hook, the debuggee's microtasks
+ // should not run.
+ function exercise(name) {
+ dbg.log += name + ',';
+ log += `${label}-${name}-handler\n`;
+ Promise.resolve(42).then(v => {
+ assertEq(v, 42);
+ log += `${label}-${name}-tail\n`;
+ });
+ }
+
+ exercise('debugger');
+
+ frame.onStep = function () {
+ this.onStep = undefined;
+ exercise('step');
+ };
+
+ dbg.onEnterFrame = function (frame) {
+ dbg.onEnterFrame = undefined;
+ frame.onPop = function(completion) {
+ assertEq(completion.return, 'escutcheon');
+ exercise('pop');
+ }
+
+ exercise('enter');
+ }
+
+ expected_throws++;
+ dbg.onExceptionUnwind = function(frame, value) {
+ dbg.onExceptionUnwind = undefined;
+ assertEq(value, 'myrmidon');
+ exercise('exception');
+ if (--expected_throws > 0) {
+ return undefined;
+ } else {
+ return { return: 'escutcheon' };
+ }
+ };
+
+ // Set a breakpoint on entry to g.breakpoint_here.
+ const script = dbg.gDO.getOwnPropertyDescriptor('breakpoint_here').value.script;
+ const handler = {
+ hit(frame) {
+ script.clearAllBreakpoints();
+ exercise('bp');
+ }
+ };
+ script.setBreakpoint(0, handler);
+
+ dbg.uncaughtExceptionHook = function (ex) {
+ assertEq(ex, 'turncoat');
+ exercise('uncaught');
+ };
+
+ // Throw an uncaught exception from the Debugger handler. This should reach
+ // uncaughtExceptionHook, but shouldn't affect the debuggee.
+ throw 'turncoat';
+ };
+
+ return dbg;
+}
+
+const dbg1 = setup(g, '1');
+const dbg2 = setup(g, '2');
+const dbg3 = setup(g, '3');
+
+g.eval(`
+ function breakpoint_here() {
+ throw 'myrmidon';
+ }
+
+ parent.log += 'eval-start\\n';
+
+ // DebuggeeWouldRun detection may prevent this callback from running at all if
+ // bug 1145201 is present. SpiderMonkey will try to run the promise reaction
+ // job from the Debugger hook's microtask checkpoint, triggering
+ // DebuggeeWouldRun. This is a little difficult to observe, since the callback
+ // never even begins execution. But it should cause the 'then' promise to be
+ // rejected, which the shell will report (if the assertEq(log, ...) doesn't
+ // kill the test first).
+
+ Promise.resolve(84).then(function(v) {
+ assertEq(v, 84);
+ parent.log += 'eval-react';
+ });
+ debugger;
+ parent.log += 'stuff to step over\\n';
+ breakpoint_here();
+ parent.log += 'eval-end\\n';
+`);
+
+log += 'main-drain\n'
+drainJobQueue();
+log += 'main-drain-done\n';
+
+const regex = new RegExp(`eval-start
+.-debugger-handler
+.-uncaught-handler
+.-debugger-tail
+.-uncaught-tail
+.-debugger-handler
+.-uncaught-handler
+.-debugger-tail
+.-uncaught-tail
+.-debugger-handler
+.-uncaught-handler
+.-debugger-tail
+.-uncaught-tail
+.-step-handler
+.-step-tail
+.-step-handler
+.-step-tail
+.-step-handler
+.-step-tail
+stuff to step over
+.-enter-handler
+.-enter-tail
+.-enter-handler
+.-enter-tail
+.-enter-handler
+.-enter-tail
+.-bp-handler
+.-bp-tail
+.-bp-handler
+.-bp-tail
+.-bp-handler
+.-bp-tail
+.-exception-handler
+.-exception-tail
+.-exception-handler
+.-exception-tail
+.-exception-handler
+.-exception-tail
+.-pop-handler
+.-pop-tail
+.-pop-handler
+.-pop-tail
+.-pop-handler
+.-pop-tail
+eval-end
+main-drain
+eval-reactmain-drain-done
+`);
+
+assertEq(!!log.match(regex), true)
+
+assertEq(dbg1.log, 'debugger,uncaught,step,enter,bp,exception,pop,');
+assertEq(dbg2.log, 'debugger,uncaught,step,enter,bp,exception,pop,');
+assertEq(dbg3.log, 'debugger,uncaught,step,enter,bp,exception,pop,');
diff --git a/js/src/jit-test/tests/debug/job-queue-04.js b/js/src/jit-test/tests/debug/job-queue-04.js
new file mode 100644
index 0000000000..76cf241e8d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/job-queue-04.js
@@ -0,0 +1,24 @@
+// |jit-test| skip-if: !('oomTest' in this)
+// Bug 1527862: Don't assert that the Debugger drained its job queue unless we
+// actually saved the debuggee's queue.
+
+// Put a job in the queue.
+Promise.resolve(42).then(() => {});
+
+var g = newGlobal({ newCompartment: true });
+var dbg = new Debugger(g);
+dbg.onNewScript = script => {};
+
+// Cause an OOM while initializing the AutoDebuggerJobQueueInterruption, so that
+// the destructor is run on an uninitialized instance.
+//
+// A properly initialized AutoDebuggerJobQueueInterruption asserts that the
+// debugger left its job queue entry, before restoring the debuggee's job queue
+// that it saved when it was initialized. But if OOM interrupts initialization,
+// the job queue left on the JSContext is still the debuggee's, which we have no
+// reason to expect is empty, so we shouldn't make any assertions about its
+// state. The assertion must be conditional on proper initialization (and use
+// the correct condition).
+oomTest(() => {
+ g.eval("(function() {})");
+}, {expectExceptionOnFailure: false});
diff --git a/js/src/jit-test/tests/debug/makeGlobalObjectReference-01.js b/js/src/jit-test/tests/debug/makeGlobalObjectReference-01.js
new file mode 100644
index 0000000000..922b8c108c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/makeGlobalObjectReference-01.js
@@ -0,0 +1,26 @@
+// Debugger.prototype.makeGlobalObjectReference returns a D.O for a global
+// without adding it as a debuggee.
+
+let g1 = newGlobal({newCompartment: true});
+let dbg = new Debugger;
+assertEq(dbg.hasDebuggee(g1), false);
+
+let g1w = dbg.makeGlobalObjectReference(g1);
+assertEq(dbg.hasDebuggee(g1), false);
+assertEq(g1w.unsafeDereference(), g1);
+assertEq(g1, g1w.makeDebuggeeValue(g1).unsafeDereference());
+
+assertEq(dbg.addDebuggee(g1w), g1w);
+assertEq(dbg.hasDebuggee(g1), true);
+assertEq(dbg.hasDebuggee(g1w), true);
+assertEq(g1w.unsafeDereference(), g1);
+assertEq(g1, g1w.makeDebuggeeValue(g1).unsafeDereference());
+
+// makeGlobalObjectReference dereferences CCWs.
+let g2 = newGlobal({newCompartment: true});
+g2.g1 = g1;
+let g2w = dbg.addDebuggee(g2);
+let g2g1w = g2w.getOwnPropertyDescriptor('g1').value;
+assertEq(g2g1w !== g1w, true);
+assertEq(g2g1w.unwrap(), g1w.makeDebuggeeValue(g1));
+assertEq(dbg.makeGlobalObjectReference(g2g1w), g1w);
diff --git a/js/src/jit-test/tests/debug/makeGlobalObjectReference-02.js b/js/src/jit-test/tests/debug/makeGlobalObjectReference-02.js
new file mode 100644
index 0000000000..a3db8e45e4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/makeGlobalObjectReference-02.js
@@ -0,0 +1,13 @@
+// Debugger.prototype.makeGlobalObjectReference only accepts actual global objects.
+
+load(libdir + 'asserts.js');
+
+var dbg = new Debugger;
+
+assertThrowsInstanceOf(() => dbg.makeGlobalObjectReference(true), TypeError);
+assertThrowsInstanceOf(() => dbg.makeGlobalObjectReference("foo"), TypeError);
+assertThrowsInstanceOf(() => dbg.makeGlobalObjectReference(12), TypeError);
+assertThrowsInstanceOf(() => dbg.makeGlobalObjectReference(undefined), TypeError);
+assertThrowsInstanceOf(() => dbg.makeGlobalObjectReference(null), TypeError);
+assertThrowsInstanceOf(() => dbg.makeGlobalObjectReference({ xlerb: "sbot" }), TypeError);
+assertEq(dbg.makeGlobalObjectReference(this) instanceof Debugger.Object, true);
diff --git a/js/src/jit-test/tests/debug/makeGlobalObjectReference-03.js b/js/src/jit-test/tests/debug/makeGlobalObjectReference-03.js
new file mode 100644
index 0000000000..b20cadb8ec
--- /dev/null
+++ b/js/src/jit-test/tests/debug/makeGlobalObjectReference-03.js
@@ -0,0 +1,8 @@
+// Debugger.prototype.makeGlobalObjectReference should not accept invisible-to-debugger globals.
+load(libdir + 'asserts.js');
+
+var g = newGlobal({ newCompartment: true, invisibleToDebugger: true });
+
+assertThrowsInstanceOf(function () {
+ (new Debugger).makeGlobalObjectReference(g)
+}, TypeError);
diff --git a/js/src/jit-test/tests/debug/noExecute-01.js b/js/src/jit-test/tests/debug/noExecute-01.js
new file mode 100644
index 0000000000..09f6ba41a7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/noExecute-01.js
@@ -0,0 +1,29 @@
+// Tests that NX disallows debuggee execution for all the hooks.
+
+load(libdir + "asserts.js");
+load(libdir + "debuggerNXHelper.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+// Attempts to call g.f without going through an invocation function should
+// throw.
+g.eval(`
+ function f() { }
+ var o = {
+ get p() { },
+ set p(x) { }
+ };
+ `);
+
+var handlers = [() => { g.f(); },
+ () => { g.o.p } ,
+ () => { g.o.p = 42; }];
+
+function testHook(hookName) {
+ for (var h of handlers) {
+ assertThrowsInstanceOf(h, Debugger.DebuggeeWouldRun);
+ }
+}
+
+testDebuggerHooksNX(dbg, g, testHook);
diff --git a/js/src/jit-test/tests/debug/noExecute-02.js b/js/src/jit-test/tests/debug/noExecute-02.js
new file mode 100644
index 0000000000..d1565b0c3f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/noExecute-02.js
@@ -0,0 +1,39 @@
+// Tests that invocation functions work.
+
+load(libdir + "asserts.js");
+load(libdir + "debuggerNXHelper.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+
+g.eval(`
+ function d() { debugger; }
+ function f() { return 42; }
+ var o = {
+ get p() { return 42; },
+ set p(x) { }
+ };
+ `);
+
+var strs = ["f();", "o.p", "o.p = 42"];
+
+var fw;
+dbg.onDebuggerStatement = (frame) => {
+ fw = frame.arguments[0];
+};
+gw.executeInGlobal("d(f)");
+dbg.onDebuggerStatement = undefined;
+
+function testHook(hookName) {
+ var newestFrame = dbg.getNewestFrame();
+ for (var s of strs) {
+ if (newestFrame) {
+ assertEq(newestFrame.eval(s).return, 42);
+ }
+ assertEq(gw.executeInGlobal(s).return, 42);
+ assertEq(fw.apply(null).return, 42);
+ }
+}
+
+testDebuggerHooksNX(dbg, g, testHook);
diff --git a/js/src/jit-test/tests/debug/noExecute-03.js b/js/src/jit-test/tests/debug/noExecute-03.js
new file mode 100644
index 0000000000..7b8f138503
--- /dev/null
+++ b/js/src/jit-test/tests/debug/noExecute-03.js
@@ -0,0 +1,28 @@
+// Tests that invocation functions work outside of Debugger code.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+
+g.eval(`
+ function f() { debugger; return 42; }
+ function f2() { return 42; }
+ var o = {
+ get p() { return 42; },
+ set p(x) { }
+ };
+ `);
+
+var strs = ["f(f2);", "o.p", "o.p = 42"];
+
+var f2w;
+dbg.onDebuggerStatement = (frame) => {
+ f2w = frame.arguments[0];
+};
+
+for (var s of strs) {
+ assertEq(gw.executeInGlobal(s).return, 42);
+}
+assertEq(f2w.apply(null).return, 42);
diff --git a/js/src/jit-test/tests/debug/noExecute-04.js b/js/src/jit-test/tests/debug/noExecute-04.js
new file mode 100644
index 0000000000..628a425bad
--- /dev/null
+++ b/js/src/jit-test/tests/debug/noExecute-04.js
@@ -0,0 +1,39 @@
+// Tests that NX works through the enabled toggle and adding/removing the
+// global.
+
+load(libdir + "asserts.js");
+load(libdir + "debuggerNXHelper.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+g.eval(`
+ function f() { }
+ var o = {
+ get p() { },
+ set p(x) { }
+ };
+ `);
+
+var handlers = [() => { g.f(); },
+ () => { g.o.p } ,
+ () => { g.o.p = 42; }];
+
+function testHookEnabled(hookName, trigger) {
+ for (var h of handlers) {
+ assertThrowsInstanceOf(h, Debugger.DebuggeeWouldRun);
+ }
+}
+
+function testHookRemoval(hookName, trigger) {
+ for (var h of handlers) {
+ assertThrowsInstanceOf(h, Debugger.DebuggeeWouldRun);
+ dbg.removeDebuggee(g);
+ h();
+ dbg.addDebuggee(g);
+ assertThrowsInstanceOf(h, Debugger.DebuggeeWouldRun);
+ }
+}
+
+testDebuggerHooksNX(dbg, g, testHookEnabled);
+testDebuggerHooksNX(dbg, g, testHookRemoval);
diff --git a/js/src/jit-test/tests/debug/noExecute-05.js b/js/src/jit-test/tests/debug/noExecute-05.js
new file mode 100644
index 0000000000..3bbed22263
--- /dev/null
+++ b/js/src/jit-test/tests/debug/noExecute-05.js
@@ -0,0 +1,43 @@
+// Tests that NX disallows debuggee execution for all debuggees.
+
+load(libdir + "asserts.js");
+load(libdir + "debuggerNXHelper.js");
+
+var g1 = newGlobal({newCompartment: true});
+var g2 = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+
+dbg.addDebuggee(g1);
+dbg.addDebuggee(g2);
+
+g1.eval(`
+ function f() { }
+ var o = {
+ get p() { },
+ set p(x) { }
+ };
+ `);
+
+g2.eval(`
+ function f() { }
+ var o = {
+ get p() { },
+ set p(x) { }
+ };
+ `);
+
+var handlers = [() => { g1.f(); },
+ () => { g1.o.p } ,
+ () => { g1.o.p = 42; },
+ () => { g2.f(); },
+ () => { g2.o.p } ,
+ () => { g2.o.p = 42; } ];
+
+function testHook(hookName) {
+ for (var h of handlers) {
+ assertThrowsInstanceOf(h, Debugger.DebuggeeWouldRun);
+ }
+}
+
+testDebuggerHooksNX(dbg, g1, testHook);
+testDebuggerHooksNX(dbg, g2, testHook);
diff --git a/js/src/jit-test/tests/debug/noExecute-06.js b/js/src/jit-test/tests/debug/noExecute-06.js
new file mode 100644
index 0000000000..43186c9e2d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/noExecute-06.js
@@ -0,0 +1,81 @@
+// Tests that NX disallows debuggee execution for multiple debuggers and
+// multiple debuggees.
+
+load(libdir + "asserts.js");
+load(libdir + "debuggerNXHelper.js");
+
+var g1 = newGlobal({newCompartment: true});
+var g2 = newGlobal({newCompartment: true});
+var dbg1 = new Debugger;
+var dbg2 = new Debugger;
+
+g1w1 = dbg1.addDebuggee(g1);
+
+g1w2 = dbg2.addDebuggee(g1);
+g2w = dbg2.addDebuggee(g2);
+
+g1.eval(`
+ function d(f) { debugger; return f; }
+ function f() { return 42; }
+ var o = {
+ get p() { return 42; },
+ set p(x) { }
+ };
+ `);
+
+g2.eval(`
+ function d(f) { debugger; return f; }
+ function f() { return 42; }
+ var o = {
+ get p() { return 42; },
+ set p(x) { }
+ };
+ `);
+
+var strs = ["f();", "o.p", "o.p = 42"];
+
+var fw1;
+dbg1.onDebuggerStatement = (frame) => {
+ fw1 = frame.arguments[0];
+}
+g1.eval('d(f)');
+dbg1.onDebuggerStatement = undefined;
+var fw2;
+dbg2.onDebuggerStatement = (frame) => {
+ fw2 = frame.arguments[0];
+}
+g2.eval('d(f)');
+dbg2.onDebuggerStatement = undefined;
+
+function testHook(hookName) {
+ var newestG1Frame = dbg1.getNewestFrame();
+ if (hookName != 'onNewGlobalObject' &&
+ hookName != 'onNewScript' &&
+ hookName != 'onNewPromise' &&
+ hookName != 'onPromiseSettled')
+ {
+ var newestG2Frame = dbg2.getNewestFrame();
+ }
+
+ for (var s of strs) {
+ // When this hook is called, g1 has been locked twice, so even invocation
+ // functions do not work.
+ assertEq(g1w1.executeInGlobal(s).throw.unsafeDereference() instanceof Debugger.DebuggeeWouldRun, true);
+ assertEq(g1w2.executeInGlobal(s).throw.unsafeDereference() instanceof Debugger.DebuggeeWouldRun, true);
+ if (newestG1Frame) {
+ assertEq(newestG1Frame.eval(s).throw.unsafeDereference() instanceof Debugger.DebuggeeWouldRun, true);
+ }
+ assertEq(fw1.apply(null).throw.unsafeDereference() instanceof Debugger.DebuggeeWouldRun, true);
+
+ // But g2 has only been locked once and so should work.
+ assertEq(g2w.executeInGlobal(s).throw, undefined);
+ if (newestG2Frame) {
+ assertEq(newestG2Frame.eval(s).throw, undefined);
+ }
+ assertEq(fw2.apply(null).return, 42);
+ }
+}
+
+testDebuggerHooksNX(dbg1, g1, () => {
+ testDebuggerHooksNX(dbg2, g2, testHook);
+});
diff --git a/js/src/jit-test/tests/debug/noExecute-07.js b/js/src/jit-test/tests/debug/noExecute-07.js
new file mode 100644
index 0000000000..a5b4247351
--- /dev/null
+++ b/js/src/jit-test/tests/debug/noExecute-07.js
@@ -0,0 +1,36 @@
+// Tests provenance of Debugger.DebuggeeWouldRun errors.
+
+load(libdir + "asserts.js");
+load(libdir + "debuggerNXHelper.js");
+
+var g1 = newGlobal({newCompartment: true});
+var g2 = newGlobal({newCompartment: true});
+var g3 = newGlobal({newCompartment: true});
+var dbg = new Debugger(g1);
+
+g3.eval(`var dbg = new Debugger`);
+var g1w = g3.dbg.addDebuggee(g1);
+g3.dbg.addDebuggee(g2);
+
+g1.eval(`function f() {}`);
+
+function testHook(hookName) {
+ // The stack is like so:
+ // g1 -> dbg (locks g1) -> g2 -> g3.dbg (locks g1 and g2)
+ //
+ // The DebuggeeWouldRun error is always allocated in the topmost locked
+ // Debugger's compartment.
+
+ // If we try to run script in g1 without going through one of g3.dbg's
+ // invocation functions, we should get an error allocated in
+ // g3.Debugger.DebuggeeWouldRun.
+ assertThrowsInstanceOf(() => { g1.eval(`f()`); }, g3.Debugger.DebuggeeWouldRun);
+
+ // If we try to run script in g1 via one of g3.dbg's invocation functions,
+ // we should get an error allocated in Debugger.DebuggeeWouldRun.
+ assertEq(g1w.executeInGlobal(`f()`).throw.unsafeDereference() instanceof Debugger.DebuggeeWouldRun, true);
+}
+
+testDebuggerHooksNX(dbg, g1, () => {
+ testDebuggerHooksNX(g3.dbg, g2, testHook);
+});
diff --git a/js/src/jit-test/tests/debug/onDebuggerStatement-01.js b/js/src/jit-test/tests/debug/onDebuggerStatement-01.js
new file mode 100644
index 0000000000..bc5b39f94d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onDebuggerStatement-01.js
@@ -0,0 +1,7 @@
+var g = newGlobal({newCompartment: true});
+g.log = '';
+
+var dbg = Debugger(g);
+dbg.onDebuggerStatement = function (stack) { g.log += '!'; };
+assertEq(g.eval("log += '1'; debugger; log += '2'; 3;"), 3);
+assertEq(g.log, '1!2');
diff --git a/js/src/jit-test/tests/debug/onDebuggerStatement-02.js b/js/src/jit-test/tests/debug/onDebuggerStatement-02.js
new file mode 100644
index 0000000000..3ed64830c0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onDebuggerStatement-02.js
@@ -0,0 +1,22 @@
+// Activity in the debugger compartment should not trigger debug hooks.
+
+var g = newGlobal({newCompartment: true});
+var hit = false;
+
+var dbg = Debugger(g);
+dbg.onDebuggerStatement = function (stack) { hit = true; };
+
+debugger;
+assertEq(hit, false, "raw debugger statement in debugger compartment should not hit");
+
+g.f = function () { debugger; };
+g.eval("f();");
+assertEq(hit, false, "debugger statement in debugger compartment function should not hit");
+
+g.outerEval = eval;
+g.eval("outerEval('debugger;');");
+assertEq(hit, false, "debugger statement in debugger compartment eval code should not hit");
+
+var g2 = newGlobal({newCompartment: true});
+g2.eval("debugger;");
+assertEq(hit, false, "debugger statement in unrelated non-debuggee compartment should not hit");
diff --git a/js/src/jit-test/tests/debug/onDebuggerStatement-03.js b/js/src/jit-test/tests/debug/onDebuggerStatement-03.js
new file mode 100644
index 0000000000..5350952477
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onDebuggerStatement-03.js
@@ -0,0 +1,13 @@
+// A debugger statement in an onDebuggerStatement hook should not reenter.
+
+var g = newGlobal({newCompartment: true});
+var calls = 0;
+
+var dbg = Debugger(g);
+dbg.onDebuggerStatement = function (stack) {
+ calls++;
+ debugger;
+};
+
+assertEq(g.eval("debugger; 7;"), 7);
+assertEq(calls, 1);
diff --git a/js/src/jit-test/tests/debug/onDebuggerStatement-04.js b/js/src/jit-test/tests/debug/onDebuggerStatement-04.js
new file mode 100644
index 0000000000..306f7919e5
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onDebuggerStatement-04.js
@@ -0,0 +1,10 @@
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+ var code = "assertEq(c, 'ok');\n";
+ assertEq(frame.evalWithBindings("eval(s)", {s: code, a: 1234}).return, undefined);
+};
+g.eval("function first() { return second(); }");
+g.eval("function second() { return eval('third()'); }");
+g.eval("function third() { debugger; }");
+g.evaluate("first();");
diff --git a/js/src/jit-test/tests/debug/onDebuggerStatement-05.js b/js/src/jit-test/tests/debug/onDebuggerStatement-05.js
new file mode 100644
index 0000000000..1f6dd13f04
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onDebuggerStatement-05.js
@@ -0,0 +1,22 @@
+// For perf reasons we don't recompile all a debuggee global's scripts when
+// Debugger no longer needs to observe all execution for that global. Test that
+// things don't crash if we try to run a script with a BaselineScript that was
+// compiled with debug instrumentation when the global is no longer a debuggee.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var counter = 0;
+dbg.onDebuggerStatement = function (frame) {
+ counter++;
+ if (counter == 15)
+ dbg.onDebuggerStatement = undefined;
+};
+
+g.eval("" + function f() {
+ {
+ let inner = 42;
+ debugger;
+ inner++;
+ }
+});
+g.eval("for (var i = 0; i < 20; i++) f()");
diff --git a/js/src/jit-test/tests/debug/onDebuggerStatement-async-generator-resumption-01.js b/js/src/jit-test/tests/debug/onDebuggerStatement-async-generator-resumption-01.js
new file mode 100644
index 0000000000..20a9ecf964
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onDebuggerStatement-async-generator-resumption-01.js
@@ -0,0 +1,60 @@
+// A Debugger can {return:} from onDebuggerStatement in an async generator.
+// A resolved promise for a {value: _, done: true} object is returned.
+
+load(libdir + "asserts.js");
+
+let g = newGlobal({newCompartment: true});
+g.eval(`
+ async function* f(x) {
+ debugger; // when==0 to force return here
+ await x;
+ yield 1;
+ debugger; // when==1 to force return here
+ }
+`);
+
+let exc = null;
+let dbg = new Debugger;
+let gw = dbg.addDebuggee(g);
+function test(when) {
+ let hits = 0;
+ let outcome = "FAIL";
+ dbg.onDebuggerStatement = frame => {
+ if (hits++ == when)
+ return {return: "ponies"};
+ };
+
+ let iter = g.f(0);
+
+ // At the initial suspend.
+ assertEq(hits, 0);
+ iter.next().then(result => {
+ // At the yield point, unless we already force-returned from the first
+ // debugger statement.
+ assertEq(hits, 1);
+ if (when == 0)
+ return result;
+ assertEq(result.value, 1);
+ assertEq(result.done, false);
+ return iter.next();
+ }).then(result => {
+ // After forced return.
+ assertEq(hits, when + 1);
+ assertEq(result.value, "ponies");
+ assertEq(result.done, true);
+ outcome = "pass";
+ }).catch(e => {
+ // An assertion failed.
+ exc = e;
+ });
+
+ assertEq(hits, 1);
+ drainJobQueue();
+ if (exc !== null)
+ throw exc;
+ assertEq(outcome, "pass");
+}
+
+for (let i = 0; i < 2; i++) {
+ test(i);
+}
diff --git a/js/src/jit-test/tests/debug/onDebuggerStatement-async-resumption-01.js b/js/src/jit-test/tests/debug/onDebuggerStatement-async-resumption-01.js
new file mode 100644
index 0000000000..e8607217f6
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onDebuggerStatement-async-resumption-01.js
@@ -0,0 +1,34 @@
+// A Debugger can {return:} from onDebuggerStatement in an async function.
+// The async function's promise is resolved with the returned value.
+
+load(libdir + "asserts.js");
+
+let g = newGlobal({newCompartment: true});
+g.eval(`
+ async function f(x) {
+ debugger; // when==0 to force return here
+ await x;
+ debugger; // when==1 to force return here
+ }
+`);
+
+let dbg = new Debugger;
+let gw = dbg.addDebuggee(g);
+function test(when, what, expected) {
+ let hits = 0;
+ let result = "FAIL";
+ dbg.onDebuggerStatement = frame => {
+ if (hits++ == when)
+ return {return: gw.makeDebuggeeValue(what)};
+ };
+ g.f(0).then(x => { result = x; });
+ assertEq(hits, 1);
+ drainJobQueue();
+ assertEq(hits, when + 1);
+ assertEq(result, expected);
+}
+
+for (let i = 0; i < 2; i++) {
+ test(i, "ok", "ok");
+ test(i, g.Promise.resolve(37), 37);
+}
diff --git a/js/src/jit-test/tests/debug/onDebuggerStatement-async-resumption-02.js b/js/src/jit-test/tests/debug/onDebuggerStatement-async-resumption-02.js
new file mode 100644
index 0000000000..67ff8fdf65
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onDebuggerStatement-async-resumption-02.js
@@ -0,0 +1,31 @@
+// |jit-test| error:all-jobs-completed-successfully
+// Verifiy that onDebuggerStatement's force-return queues the promise
+// microtask to run in the debuggee's job queue, not the debugger's
+// AutoDebuggerJobQueueInterruption.
+
+let g = newGlobal({ newCompartment: true });
+g.eval(`
+ async function asyncFn(x) {
+ await Promise.resolve();
+ debugger;
+ }
+ function enterDebuggee(){}
+`);
+const dbg = new Debugger(g);
+
+(async function() {
+ let it = g.asyncFn();
+
+ // Force-return when the debugger runs after await resume.
+ dbg.onDebuggerStatement = () => {
+ return { return: "exit" };
+ };
+
+ const result = await it;
+ assertEq(result, "exit");
+ // If execution here is resumed from the debugger's queue, this call will
+ // trigger DebuggeeWouldRun exception.
+ g.enterDebuggee();
+
+ throw "all-jobs-completed-successfully";
+})();
diff --git a/js/src/jit-test/tests/debug/onDebuggerStatement-generator-resumption-01.js b/js/src/jit-test/tests/debug/onDebuggerStatement-generator-resumption-01.js
new file mode 100644
index 0000000000..c4e1ade96e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onDebuggerStatement-generator-resumption-01.js
@@ -0,0 +1,32 @@
+// A Debugger can {return:} from onDebuggerStatement in a generator.
+
+load(libdir + "asserts.js");
+
+let g = newGlobal({newCompartment: true});
+g.eval(`
+ function* f1() {
+ debugger;
+ yield 1;
+ }
+
+ function* f2() {
+ yield 1;
+ debugger;
+ yield 2;
+ }
+`);
+
+let dbg = Debugger(g);
+dbg.onDebuggerStatement = frame => ({return: "Ronja"});
+
+let genObj = g.f1();
+assertDeepEq(genObj.next(), {value: "Ronja", done: true});
+// Forced return closes the generator. Closed generators behave like this:
+assertDeepEq(genObj.next(), {value: undefined, done: true});
+
+// It works the same after resuming from a yield.
+genObj = g.f2();
+assertDeepEq(genObj.next(), {value: 1, done: false});
+assertDeepEq(genObj.next(), {value: "Ronja", done: true});
+assertDeepEq(genObj.next(), {value: undefined, done: true}); // closed
+
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-01.js b/js/src/jit-test/tests/debug/onEnterFrame-01.js
new file mode 100644
index 0000000000..e9df9131e9
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-01.js
@@ -0,0 +1,29 @@
+// Basic enterFrame hook tests.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var type;
+dbg.onEnterFrame = function (frame) {
+ try {
+ assertEq(frame instanceof Debugger.Frame, true);
+ assertEq(frame.onStack, true);
+ type = frame.type;
+ } catch (exc) {
+ type = "Exception thrown: " + exc;
+ }
+};
+
+function test(f, expected) {
+ type = undefined;
+ f();
+ assertEq(type, expected);
+}
+
+// eval triggers the hook
+test(function () { g.eval("function h() { return 1; }"); }, "eval");
+
+// function calls trigger it
+test(function () { assertEq(g.h(), 1); }, "call");
+
+// global scripts trigger it
+test(function () { g.evaluate("var x = 5;"); assertEq(g.x, 5); }, "global");
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-02.js b/js/src/jit-test/tests/debug/onEnterFrame-02.js
new file mode 100644
index 0000000000..efb6cf8e9a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-02.js
@@ -0,0 +1,22 @@
+// enterFrame test with recursive debuggee function.
+
+var g = newGlobal({newCompartment: true});
+var N = g.N = 9;
+g.eval("function f(i) { if (i < N) f(i + 1); }");
+
+var dbg = Debugger(g);
+var arr = [];
+dbg.onEnterFrame = function (frame) {
+ var i;
+ for (i = 0; i < arr.length; i++)
+ assertEq(frame !== arr[i], true);
+ arr[i] = frame;
+
+ // Check that the whole stack is as expected.
+ var j = i;
+ for (; frame; frame = frame.older)
+ assertEq(arr[j--], frame);
+};
+
+g.f(0);
+assertEq(arr.length, N + 1);
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-03.js b/js/src/jit-test/tests/debug/onEnterFrame-03.js
new file mode 100644
index 0000000000..dcb78e971c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-03.js
@@ -0,0 +1,23 @@
+// frame.eval works in the enterFrame hook.
+// It triggers the enterFrame hook again, recursively. (!)
+
+var g = newGlobal({newCompartment: true});
+g.a = ".";
+
+var dbg = Debugger(g);
+var nestCount = 0, N = 9;
+var log = "";
+dbg.onEnterFrame = function (frame) {
+ assertEq(frame.type, "eval");
+ if (nestCount < N) {
+ log += '(';
+ nestCount++;
+ var a = frame.eval("a").return;
+ log += a;
+ nestCount--;
+ log += ')';
+ }
+};
+
+assertEq(g.eval("a"), ".");
+assertEq(log, Array(N + 1).join("(") + Array(N + 1).join(".)"));
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-05.js b/js/src/jit-test/tests/debug/onEnterFrame-05.js
new file mode 100644
index 0000000000..c78e92e2c3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-05.js
@@ -0,0 +1,15 @@
+// The tracejit does not prevent onEnterFrame from being called.
+
+var g = newGlobal({newCompartment: true});
+g.eval("function f() { return 1; }\n");
+var N = g.N = 11;
+g.eval("function h() {\n" +
+ " for (var i = 0; i < N; i += f()) {}\n" +
+ "}");
+g.h(); // record loop
+
+var dbg = Debugger(g);
+var log = '';
+dbg.onEnterFrame = function (frame) { log += frame.callee.name; };
+g.h();
+assertEq(log, 'h' + Array(N + 1).join('f'));
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-06.js b/js/src/jit-test/tests/debug/onEnterFrame-06.js
new file mode 100644
index 0000000000..de3b10799a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-06.js
@@ -0,0 +1,19 @@
+// The tracejit does not prevent onEnterFrame from being called after entering
+// a debuggee compartment from a non-debuggee compartment.
+
+var g1 = newGlobal({newCompartment: true});
+var g2 = newGlobal({newCompartment: true});
+var dbg = Debugger(g1, g2);
+dbg.removeDebuggee(g2); // turn off debug mode in g2
+
+g1.eval("function f() { return 1; }\n");
+var N = g1.N = 11;
+g1.eval("function h() {\n" +
+ " for (var i = 0; i < N; i += f()) {}\n" +
+ "}");
+g1.h(); // record loop
+
+var log = '';
+dbg.onEnterFrame = function (frame) { log += frame.callee.name; };
+g1.h();
+assertEq(log, 'h' + Array(N + 1).join('f'));
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-07.js b/js/src/jit-test/tests/debug/onEnterFrame-07.js
new file mode 100644
index 0000000000..52e95c3892
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-07.js
@@ -0,0 +1,15 @@
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var visibleFrames = 0;
+dbg.onEnterFrame = function (frame) {
+ print("> " + frame.script.url);
+ visibleFrames++;
+}
+
+g.eval("(" + function iife() {
+ [1].forEach(function noop() {});
+ for (let x of [1]) {}
+} + ")()");
+
+// 1 for eval, 1 for iife(), 1 for noop()
+assertEq(visibleFrames, 3);
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-async-01.js b/js/src/jit-test/tests/debug/onEnterFrame-async-01.js
new file mode 100644
index 0000000000..299516264e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-async-01.js
@@ -0,0 +1,32 @@
+// async functions fire onEnterFrame each time they resume, like generators
+
+let g = newGlobal({newCompartment: true});
+g.eval(`
+ async function timeout(n) {
+ for (let i = 0; i < n; i++) {
+ await Promise.resolve(i);
+ }
+ }
+ async function job() {
+ let racer = timeout(5);
+ await timeout(3);
+ await racer;
+ }
+`);
+
+let dbg = Debugger(g);
+let log = "";
+let nicknames = ["job", "t5", "t3"];
+dbg.onEnterFrame = frame => {
+ if (!("nickname" in frame))
+ frame.nickname = nicknames.shift() || "FAIL";
+ log += "(" + frame.nickname;
+ frame.onPop = completion => { log += ")"; };
+};
+
+g.job();
+drainJobQueue();
+assertEq(log,
+ "(job(t5)(t3))" +
+ "(t5)(t3)".repeat(3) +
+ "(t5)(job)(t5)(job)");
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-01.js b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-01.js
new file mode 100644
index 0000000000..1f4612147a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-01.js
@@ -0,0 +1,35 @@
+// A Debugger can {return:} from the first onEnterFrame for an async function.
+// (The exact behavior is undocumented; we're testing that it doesn't crash.)
+
+ignoreUnhandledRejections();
+
+let g = newGlobal({newCompartment: true});
+g.hit2 = false;
+g.eval(`async function f(x) { await x; return "ponies"; }`);
+
+let dbg = new Debugger;
+let gw = dbg.addDebuggee(g);
+let hits = 0;
+let resumption = undefined;
+dbg.onEnterFrame = frame => {
+ if (frame.type == "call" && frame.callee.name === "f") {
+ frame.onPop = completion => {
+ assertEq(completion.return.isPromise, true);
+ hits++;
+ };
+
+ // If we force-return a generator object here, the caller will receive
+ // a promise object resolved with that generator.
+ resumption = frame.eval(`(function* f2() { hit2 = true; })()`);
+ assertEq(resumption.return.class, "Generator");
+ return resumption;
+ }
+};
+
+let p = g.f(0);
+assertEq(hits, 1);
+assertEq(g.hit2, false);
+let pw = gw.makeDebuggeeValue(p);
+assertEq(pw.isPromise, true);
+assertEq(pw.promiseState, "fulfilled");
+assertEq(pw.promiseValue, resumption.return);
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-02.js b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-02.js
new file mode 100644
index 0000000000..15986741be
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-02.js
@@ -0,0 +1,35 @@
+// A Debugger can {throw:} from onEnterFrame in an async function.
+// The resulting promise (if any) is rejected with the thrown error value.
+
+load(libdir + "asserts.js");
+
+let g = newGlobal({newCompartment: true});
+g.eval(`
+ async function f() { await 1; }
+ var err = new TypeError("object too hairy");
+`);
+
+let dbg = new Debugger;
+let gw = dbg.addDebuggee(g);
+let errw = gw.makeDebuggeeValue(g.err);
+
+// Repeat the test for each onEnterFrame event.
+// It fires up to two times:
+// - when the async function g.f is called;
+// - when we resume after the await to run to the end.
+for (let when = 0; when < 2; when++) {
+ let hits = 0;
+ dbg.onEnterFrame = frame => {
+ return hits++ < when ? undefined : {throw: errw};
+ };
+
+ let result = undefined;
+ g.f()
+ .then(value => { result = {returned: value}; })
+ .catch(err => { result = {threw: err}; });
+
+ drainJobQueue();
+
+ assertEq(hits, when + 1);
+ assertDeepEq(result, {threw: g.err});
+}
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-03.js b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-03.js
new file mode 100644
index 0000000000..6567a61d67
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-03.js
@@ -0,0 +1,29 @@
+// A Debugger can {return:} from onEnterFrame at any resume point in an async function.
+// The async function's promise is resolved with the returned value.
+
+let g = newGlobal({newCompartment: true});
+g.eval(`async function f(x) { await x; }`);
+
+let dbg = new Debugger(g);
+function test(when) {
+ let hits = 0;
+ dbg.onEnterFrame = frame => {
+ if (frame.type == "call" && frame.callee.name === "f") {
+ if (hits++ == when) {
+ return {return: "exit"};
+ }
+ }
+ };
+
+ let result = undefined;
+ let finished = false;
+ g.f("hello").then(value => { result = value; finished = true; });
+ drainJobQueue();
+ assertEq(finished, true);
+ assertEq(hits, when + 1);
+ assertEq(result, "exit");
+}
+
+// onEnterFrame with hits==0 is not a resume point; {return:} behaves differently there
+// (see onEnterFrame-async-resumption-02.js).
+test(1); // force return from first resume point, immediately after the await instruction
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-04.js b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-04.js
new file mode 100644
index 0000000000..9f67d1f286
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-04.js
@@ -0,0 +1,36 @@
+// Returning {throw:} from onEnterFrame when resuming inside a try block in an
+// async function causes control to jump to the catch block.
+
+let g = newGlobal({newCompartment: true});
+g.eval(`
+ async function af() {
+ try {
+ return await Promise.resolve("fail");
+ } catch (exc) {
+ assertEq(exc, "fit");
+ return "ok";
+ }
+ }
+`)
+
+let dbg = new Debugger(g);
+dbg.onEnterFrame = frame => {
+ if (!("hits" in frame)) {
+ frame.hits = 1;
+ } else if (++frame.hits === 2) {
+ // First hit happens when g.af() is called;
+ // second hit is resuming at the `await` inside the try block.
+ return {throw: "fit"};
+ }
+};
+
+let p = g.af();
+let hits = 0;
+p.then(value => {
+ result = value;
+ hits++;
+});
+drainJobQueue();
+assertEq(hits, 1);
+assertEq(result, "ok");
+
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-05.js b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-05.js
new file mode 100644
index 0000000000..a0d9887c80
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-05.js
@@ -0,0 +1,41 @@
+// A Debugger can't force-return from the first onEnterFrame for an async generator.
+
+ignoreUnhandledRejections();
+
+let g = newGlobal({newCompartment: true});
+g.eval(`async function* f(x) { await x; return "ponies"; }`);
+
+let dbg = new Debugger;
+let gw = dbg.addDebuggee(g);
+let log = "";
+let completion = undefined;
+let resumption = undefined;
+dbg.uncaughtExceptionHook = exc => {
+ log += "2";
+ assertEq(exc.message, "can't force return from a generator before the initial yield");
+ assertEq(exc.constructor, TypeError);
+ return undefined; // Squelch the error and let the debuggee continue.
+};
+dbg.onEnterFrame = frame => {
+ if (frame.type == "call" && frame.callee.name === "f") {
+ frame.onPop = c => {
+ // We get here after the uncaughtExcpetionHook fires
+ // and the debuggee frame has run to the first await.
+ completion = c;
+ assertEq(completion.return.class, "AsyncGenerator");
+ assertEq(completion.return !== resumption.return, true);
+ log += "3";
+ };
+
+ // Try force-returning an actual object of the expected type.
+ dbg.onEnterFrame = undefined; // don't recurse
+ resumption = frame.eval('f(0)');
+ assertEq(resumption.return.class, "AsyncGenerator");
+ log += "1";
+ return resumption;
+ }
+};
+
+let it = g.f(0);
+assertEq(log, "123");
+assertEq(gw.makeDebuggeeValue(it), completion.return);
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-06.js b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-06.js
new file mode 100644
index 0000000000..e7ecab1754
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-06.js
@@ -0,0 +1,49 @@
+// A Debugger can {return:} from the first onEnterFrame for an async function.
+// (The exact behavior is undocumented; we're testing that it doesn't crash.)
+
+ignoreUnhandledRejections();
+
+let g = newGlobal({newCompartment: true});
+g.eval(`async function f(x) { await x; return "ponies"; }`);
+g.eval(`async function f2(x) { await x; return "moar ponies"; }`);
+
+let dbg = new Debugger;
+let gw = dbg.addDebuggee(g);
+let hits = 0;
+let resumption = undefined;
+let savedAsyncGen = undefined;
+dbg.onEnterFrame = frame => {
+ if (frame.type == "call" && frame.callee.name === "f2") {
+ frame.onPop = completion => {
+ if (savedAsyncGen === undefined) {
+ savedAsyncGen = completion.return;
+ }
+ };
+ }
+ if (frame.type == "call" && frame.callee.name === "f") {
+ frame.onPop = completion => {
+ hits++;
+ };
+
+ return {return: savedAsyncGen};
+ }
+};
+
+let p2 = g.f2(0);
+let p = g.f(0);
+
+assertEq(hits, 1);
+
+drainJobQueue();
+
+assertEq(hits, 1);
+
+let pw2 = gw.makeDebuggeeValue(p2);
+assertEq(pw2.isPromise, true);
+assertEq(pw2.promiseState, "fulfilled");
+assertEq(pw2.promiseValue, "moar ponies");
+
+let pw = gw.makeDebuggeeValue(p);
+assertEq(pw.isPromise, true);
+assertEq(pw.promiseState, "fulfilled");
+assertEq(pw.promiseValue, "moar ponies");
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-07.js b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-07.js
new file mode 100644
index 0000000000..578d93ff99
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-07.js
@@ -0,0 +1,62 @@
+// A Debugger can't force-return from the first onEnterFrame for an async generator.
+
+ignoreUnhandledRejections();
+
+let g = newGlobal({newCompartment: true});
+g.eval(`async function* f(x) { await x; return "ponies"; }`);
+
+let dbg = new Debugger;
+dbg.uncaughtExceptionHook = exc => {}; // ignore errors
+let gw = dbg.addDebuggee(g);
+let hits = 0;
+let resumption = undefined;
+
+dbg.onEnterFrame = frame => {
+ dbg.onEnterFrame = undefined;
+ assertEq(frame.type, "call");
+ assertEq(frame.callee.name, "f");
+ frame.onPop = completion => {
+ hits++;
+ };
+
+ // Try to force-return. It's too early. This results in a call to the
+ // uncaughtExceptionHook but is otherwise ignored.
+ return {return: "rainbows"};
+};
+let it = g.f(0); // onPop #1: the initial yield
+assertEq(hits, 1);
+let p = it.next(); // onPop #2: await x
+assertEq(hits, 2);
+drainJobQueue(); // onPop #3: return "ponies", #4: the final yield
+assertEq(hits, 4);
+
+let pw = gw.makeDebuggeeValue(p);
+assertEq(pw.isPromise, true);
+assertEq(pw.promiseState, "fulfilled");
+assertEq(pw.promiseValue.getProperty("value").return, "ponies");
+assertEq(pw.promiseValue.getProperty("done").return, true);
+
+// ----
+
+g.eval(`async function* f2(x) { await x; return "moar ponies"; }`);
+
+let savedAsyncGen = undefined;
+dbg.onEnterFrame = frame => {
+ dbg.onEnterFrame = undefined;
+ assertEq(frame.type, "call");
+ assertEq(frame.callee.name, "f2");
+ frame.onPop = completion => {
+ if (savedAsyncGen === undefined) {
+ savedAsyncGen = completion.return;
+ }
+ };
+};
+let it2 = g.f2(123);
+let p2 = it2.next();
+drainJobQueue();
+
+let pw2 = gw.makeDebuggeeValue(p2);
+assertEq(pw2.isPromise, true);
+assertEq(pw2.promiseState, "fulfilled");
+assertEq(pw2.promiseValue.getProperty("value").return, "moar ponies");
+assertEq(pw2.promiseValue.getProperty("done").return, true);
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-08.js b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-08.js
new file mode 100644
index 0000000000..f9760da7b7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-08.js
@@ -0,0 +1,53 @@
+// Terminate execution from within the onPop handler, the result promise will
+// never be resolved.
+
+load(libdir + "array-compare.js");
+
+let g = newGlobal({newCompartment: true});
+let dbg = new Debugger(g);
+
+let log = [];
+g.log = log;
+
+g.eval(`
+ async function f() {
+ log.push("START");
+ await 0;
+ log.push("MIDDLE");
+ await 0;
+ log.push("END");
+ }
+`);
+
+const expectedTick1 = ["START"];
+const expectedTickN = [
+ "START",
+ "enter: f",
+ "MIDDLE",
+ "pop: f",
+];
+
+Promise.resolve(0)
+.then(() => assertEq(arraysEqual(log, expectedTick1), true))
+.then(() => assertEq(arraysEqual(log, expectedTickN), true))
+.then(() => assertEq(arraysEqual(log, expectedTickN), true))
+.then(() => assertEq(arraysEqual(log, expectedTickN), true))
+.then(() => assertEq(arraysEqual(log, expectedTickN), true))
+.catch(e => {
+ // Quit with non-zero exit code to ensure a test suite error is shown,
+ // even when this function is called within promise handlers which normally
+ // swallow any exceptions.
+ print("Error: " + e + "\nstack:\n" + e.stack);
+ quit(1);
+});
+
+g.f();
+
+dbg.onEnterFrame = frame => {
+ log.push(`enter: ${frame.callee.name}`);
+
+ frame.onPop = () => {
+ log.push(`pop: ${frame.callee.name}`);
+ return null; // Terminate execution.
+ };
+};
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-09.js b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-09.js
new file mode 100644
index 0000000000..4a3bd52c3b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-09.js
@@ -0,0 +1,33 @@
+// Resume execution of async generator when initially yielding.
+
+let g = newGlobal({newCompartment: true});
+let dbg = new Debugger();
+let gw = dbg.addDebuggee(g);
+
+g.eval(`
+ async function* f() {
+ await 123;
+ return "ponies";
+ }
+`);
+
+let counter = 0;
+dbg.onEnterFrame = frame => {
+ frame.onPop = completion => {
+ if (counter++ === 0) {
+ let genObj = completion.return.unsafeDereference();
+
+ genObj.next().then(({value, done}) => {
+ assertEq(value, "ponies");
+ assertEq(done, true);
+ });
+ }
+ };
+};
+
+let it = g.f();
+
+it.next().then(({value, done}) => {
+ assertEq(value, undefined);
+ assertEq(done, true);
+});
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-10.js b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-10.js
new file mode 100644
index 0000000000..3dfeb112c2
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-10.js
@@ -0,0 +1,24 @@
+// Resolve async function promise when initially awaiting.
+
+let g = newGlobal({newCompartment: true});
+let dbg = new Debugger();
+let gw = dbg.addDebuggee(g);
+
+g.eval(`
+ var resolve;
+ var p = new Promise(r => {
+ resolve = r;
+ });
+
+ async function f() {
+ await p;
+ }
+`);
+
+dbg.onEnterFrame = frame => {
+ frame.onPop = completion => {
+ g.resolve(0);
+ drainJobQueue();
+ };
+};
+g.f();
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-11.js b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-11.js
new file mode 100644
index 0000000000..32adf64d85
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-11.js
@@ -0,0 +1,28 @@
+// If the async function's promise is already resolved, any attempt to return
+// a differerent return value gets ignored.
+
+let g = newGlobal({newCompartment: true});
+g.eval(`
+ async function f() {
+ throw "ok";
+ }
+`);
+
+let dbg = new Debugger(g);
+
+let hits = 0;
+dbg.onEnterFrame = frame => {
+ frame.onPop = () => {
+ hits += 1;
+
+ // Normal functions can override the return value, but async functions
+ // have already resolved their promise, so this return request will get
+ // ignored.
+ return {return: "FAIL"};
+ };
+};
+
+g.f().catch(x => {
+ assertEq(hits, 1);
+ assertEq(x, "ok");
+});
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-12.js b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-12.js
new file mode 100644
index 0000000000..28ad0c4c44
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-12.js
@@ -0,0 +1,28 @@
+// If the async function's promise is already resolved, any attempt to return
+// a differerent return value gets ignored.
+
+let g = newGlobal({newCompartment: true});
+g.eval(`
+ async function f() {
+ return "ok";
+ }
+`);
+
+let dbg = new Debugger(g);
+
+let hits = 0;
+dbg.onEnterFrame = frame => {
+ frame.onPop = () => {
+ hits += 1;
+
+ // Normal functions can override the return value, but async functions
+ // have already resolved their promise, so this return request will get
+ // ignored.
+ return {return: "FAIL"};
+ };
+};
+
+g.f().then(x => {
+ assertEq(hits, 1);
+ assertEq(x, "ok");
+});
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-13.js b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-13.js
new file mode 100644
index 0000000000..f9e856a74c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-13.js
@@ -0,0 +1,21 @@
+// An onPop handler can return its input argument for async generators. The
+// debugger correctly adjusts the state of the async generator object.
+
+let g = newGlobal({newCompartment: true});
+g.eval(`
+ async function* f() {}
+`);
+
+let hits = 0;
+let dbg = new Debugger(g);
+dbg.onEnterFrame = frame => {
+ frame.onPop = completion => {
+ hits++;
+ return completion;
+ };
+};
+
+let it = g.f();
+let p = it.next();
+
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-async-tryskipawait-01.js b/js/src/jit-test/tests/debug/onEnterFrame-async-tryskipawait-01.js
new file mode 100644
index 0000000000..84bc5b63f7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-async-tryskipawait-01.js
@@ -0,0 +1,41 @@
+// CanSkipAwait with resolved Promises when attaching onEnterFrame
+// after the initial call into the async function.
+
+load(libdir + "array-compare.js");
+
+let g = newGlobal({ newCompartment: true });
+let dbg = new Debugger(g);
+
+let log = [];
+g.log = log;
+
+g.eval(`
+ async function f() {
+ log.push("START");
+ await Promise.resolve(0);
+ log.push("MIDDLE");
+ await Promise.resolve(0);
+ log.push("END");
+ }
+`);
+
+function neverCalled(e) {
+ // Quit with non-zero exit code to ensure a test suite error is shown,
+ // even when this function is called within promise handlers which normally
+ // swallow any exceptions.
+ print("Error: " + e + "\nstack:\n" + e.stack);
+ quit(1);
+}
+
+g.f().then(() => {
+ assertEq(arraysEqual(log, [
+ "START",
+ "enter: f",
+ "MIDDLE",
+ "END",
+ ]), true);
+}).catch(neverCalled);
+
+dbg.onEnterFrame = frame => {
+ log.push(`enter: ${frame.callee.name}`);
+};
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-async-tryskipawait-02.js b/js/src/jit-test/tests/debug/onEnterFrame-async-tryskipawait-02.js
new file mode 100644
index 0000000000..a99b96a480
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-async-tryskipawait-02.js
@@ -0,0 +1,41 @@
+// CanSkipAwait with primitive values when attaching onEnterFrame
+// after the initial call into the async function.
+
+load(libdir + "array-compare.js");
+
+let g = newGlobal({ newCompartment: true });
+let dbg = new Debugger(g);
+
+let log = [];
+g.log = log;
+
+g.eval(`
+ async function f() {
+ log.push("START");
+ await 0;
+ log.push("MIDDLE");
+ await 0;
+ log.push("END");
+ }
+`);
+
+function neverCalled(e) {
+ // Quit with non-zero exit code to ensure a test suite error is shown,
+ // even when this function is called within promise handlers which normally
+ // swallow any exceptions.
+ print("Error: " + e + "\nstack:\n" + e.stack);
+ quit(1);
+}
+
+g.f().then(() => {
+ assertEq(arraysEqual(log, [
+ "START",
+ "enter: f",
+ "MIDDLE",
+ "END",
+ ]), true);
+}).catch(neverCalled);
+
+dbg.onEnterFrame = frame => {
+ log.push(`enter: ${frame.callee.name}`);
+};
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-async-tryskipawait-03.js b/js/src/jit-test/tests/debug/onEnterFrame-async-tryskipawait-03.js
new file mode 100644
index 0000000000..cff1edd115
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-async-tryskipawait-03.js
@@ -0,0 +1,42 @@
+// CanSkipAwait with plain objects when attaching onEnterFrame
+// after the initial call into the async function.
+
+load(libdir + "array-compare.js");
+
+let g = newGlobal({ newCompartment: true });
+let dbg = new Debugger(g);
+
+let log = [];
+g.log = log;
+
+g.eval(`
+ async function f() {
+ log.push("START");
+ await {};
+ log.push("MIDDLE");
+ await {};
+ log.push("END");
+ }
+`);
+
+function neverCalled(e) {
+ // Quit with non-zero exit code to ensure a test suite error is shown,
+ // even when this function is called within promise handlers which normally
+ // swallow any exceptions.
+ print("Error: " + e + "\nstack:\n" + e.stack);
+ quit(1);
+}
+
+g.f().then(() => {
+ assertEq(arraysEqual(log, [
+ "START",
+ "enter: f",
+ "MIDDLE",
+ "enter: f", // Await not optimised away in JSOP_TRYSKIPAWAIT!
+ "END",
+ ]), true);
+}).catch(neverCalled);
+
+dbg.onEnterFrame = frame => {
+ log.push(`enter: ${frame.callee.name}`);
+};
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-generator-01.js b/js/src/jit-test/tests/debug/onEnterFrame-generator-01.js
new file mode 100644
index 0000000000..6f94496eaf
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-generator-01.js
@@ -0,0 +1,82 @@
+// Frame properties and methods work in generator-resuming onEnterFrame events.
+// Also tests onPop events, for good measure.
+
+let g = newGlobal({newCompartment: true});
+g.eval(`\
+ function* gen(lo, hi) {
+ var a = 1/2;
+ yield a;
+ yield a * a;
+ }
+`);
+let dbg = new Debugger;
+let gw = dbg.addDebuggee(g);
+
+let hits = 0;
+let savedScript = null;
+let savedEnv = null;
+let savedOffsets = new Set;
+
+function check(frame) {
+ assertEq(frame.type, "call");
+ assertEq(frame.constructing, false);
+ assertEq(frame.callee, gw.makeDebuggeeValue(g.gen));
+
+ // `arguments` elements don't work in resumed generator frames,
+ // because generators don't keep the arguments around.
+ // However, some of this is initialized when the frame.arguments object is
+ // created, so if they are created during the first onEnterFrame or onPop
+ // event, the properties exist, and those events can also see the values.
+ assertEq(frame.arguments.length, args.length);
+ for (var i = 0; i < args.length; i++) {
+ assertEq(frame.arguments.hasOwnProperty(i), true);
+
+ if (hits < 2)
+ assertEq(frame.arguments[i], gw.makeDebuggeeValue(args[i]), `arguments[${i}]`);
+ else
+ assertEq(frame.arguments[i], undefined);
+ }
+
+ if (savedEnv === null) {
+ savedEnv = frame.environment;
+ assertEq(savedScript, null);
+ savedScript = frame.script;
+ } else {
+ assertEq(frame.environment, savedEnv);
+ assertEq(frame.script, savedScript);
+ }
+ let a_expected = hits < 3 ? undefined : 1/2;
+ assertEq(savedEnv.getVariable("a"), a_expected);
+
+ assertEq(frame.onStack, true);
+
+ let pc = frame.offset;
+ assertEq(savedOffsets.has(pc), false);
+ savedOffsets.add(pc);
+
+ assertEq(frame.older, null);
+ assertEq(frame.this, gw.makeDebuggeeValue(g));
+ assertEq(typeof frame.implementation, "string");
+
+ // And the moment of truth:
+ assertEq(frame.eval("2 + 2").return, 4);
+ assertEq(frame.eval("a").return, a_expected);
+ assertEq(frame.eval("if (a !== undefined) { assertEq(a < (lo + hi) / 2, true); } 7;").return, 7);
+}
+
+dbg.onEnterFrame = frame => {
+ if (frame.type === "eval")
+ return;
+ check(frame);
+ hits++;
+ frame.onPop = completion => {
+ check(frame);
+ hits++;
+ };
+};
+
+// g.gen ignores the arguments passed to it, but we use them to test
+// frame.arguments.
+let args = [0, 10, g, dbg];
+for (let v of g.gen(...args)) {}
+assertEq(hits, 8);
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-generator-02.js b/js/src/jit-test/tests/debug/onEnterFrame-generator-02.js
new file mode 100644
index 0000000000..5aa49a580d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-generator-02.js
@@ -0,0 +1,27 @@
+// onEnterFrame fires after the [[GeneratorState]] is set to "executing".
+//
+// This test checks that Debugger doesn't accidentally make it possible to
+// reenter a generator frame that's already on the stack. (Also tests a fun
+// corner case in baseline debug-mode OSR.)
+
+load(libdir + "asserts.js");
+
+let g = newGlobal({newCompartment: true});
+g.eval('function* f() { yield 1; yield 2; }');
+let dbg = Debugger(g);
+let genObj = null;
+let hits = 0;
+dbg.onEnterFrame = frame => {
+ // The first time onEnterFrame fires, there is no generator object, so
+ // there's nothing to test. The generator object doesn't exist until
+ // JSOP_GENERATOR is reached, right before the initial yield.
+ if (genObj !== null) {
+ dbg.removeDebuggee(g); // avoid the DebuggeeWouldRun exception
+ assertThrowsInstanceOf(() => genObj.next(), g.TypeError);
+ dbg.addDebuggee(g);
+ hits++;
+ }
+};
+genObj = g.f();
+for (let x of genObj) {}
+assertEq(hits, 3);
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-generator-03.js b/js/src/jit-test/tests/debug/onEnterFrame-generator-03.js
new file mode 100644
index 0000000000..ac8adce373
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-generator-03.js
@@ -0,0 +1,25 @@
+// If onEnterFrame terminates a generator, the Frame is left in a sane but inactive state.
+
+load(libdir + "asserts.js");
+
+let g = newGlobal({newCompartment: true});
+g.eval("function* f(x) { yield x; }");
+let dbg = new Debugger;
+let gw = dbg.addDebuggee(g);
+
+let genFrame = null;
+dbg.onDebuggerStatement = frame => {
+ dbg.onEnterFrame = frame => {
+ if (frame.callee == gw.getOwnPropertyDescriptor("f").value) {
+ genFrame = frame;
+ return null;
+ }
+ };
+ assertEq(frame.eval("f(0);"), null);
+};
+
+g.eval("debugger;");
+
+assertEq(genFrame instanceof Debugger.Frame, true);
+assertEq(genFrame.onStack, false);
+assertThrowsInstanceOf(() => genFrame.callee, Error);
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-generator-04.js b/js/src/jit-test/tests/debug/onEnterFrame-generator-04.js
new file mode 100644
index 0000000000..00f9d0f3b4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-generator-04.js
@@ -0,0 +1,44 @@
+// When a generator frame is resumed, the onEnterFrame fires again.
+// The same Frame object is passed.
+
+let g = newGlobal({newCompartment: true});
+g.eval(`
+ function* easyMode() {}
+
+ function* f() { yield* "XYZ"; }
+ function* hardMode() {
+ for (let c1 of "AB")
+ for (let c2 of f())
+ yield c1 + c2;
+ }
+`);
+
+function test(mode, expected) {
+ let dbg = new Debugger(g);
+ let nextid = 1;
+ dbg.onEnterFrame = frame => {
+ if (frame.type == "call") {
+ if (!("id" in frame))
+ frame.id = nextid++;
+ g.log += frame.id + "(";
+
+ frame.onPop = rv => {
+ g.log += ")";
+ };
+ }
+ };
+
+ g.log = "";
+ g.eval(`
+ for (let x of ${mode}())
+ log += x;
+ `);
+ assertEq(g.log, expected);
+ dbg.removeDebuggee(g);
+}
+
+// We fire onEnterFrame for the initial activation when a generator is first
+// called, even though the initial yield happens before any body code. This is
+// weird but at least it's consistent.
+test("easyMode", "1()1()");
+test("hardMode", "1()1(2()2())AX1(2())AY1(2())AZ1(2()3()3())BX1(3())BY1(3())BZ1(3())");
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-generator-05.js b/js/src/jit-test/tests/debug/onEnterFrame-generator-05.js
new file mode 100644
index 0000000000..cacb1b825b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-generator-05.js
@@ -0,0 +1,34 @@
+// When resuming a generator triggers one Debugger's onEnterFrame handler,
+// all Debuggers' Debugger.Frames become usable at once.
+
+load(libdir + "asserts.js");
+
+let g = newGlobal({newCompartment: true});
+g.eval(`
+ function* f() {
+ yield 1;
+ }
+`);
+let dbg1 = new Debugger(g);
+let dbg2 = new Debugger(g);
+
+let hits = 0;
+let savedFrame1;
+let savedFrame2;
+dbg1.onEnterFrame = frame => {
+ if (savedFrame1 === undefined) {
+ savedFrame1 = frame;
+ savedFrame2 = dbg2.getNewestFrame();
+ } else {
+ hits++;
+ assertEq(savedFrame1, frame);
+ for (let f of [savedFrame2, savedFrame1]) {
+ assertEq(f.type, "call");
+ assertEq(f.callee.name, "f");
+ }
+ }
+};
+
+let values = [...g.f()];
+assertEq(hits, 2);
+assertDeepEq(values, [1]);
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-generator-06.js b/js/src/jit-test/tests/debug/onEnterFrame-generator-06.js
new file mode 100644
index 0000000000..a19fdf7787
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-generator-06.js
@@ -0,0 +1,24 @@
+// Bug 1561935. Test interaction between debug traps for step mode and
+// DebugAfterYield.
+
+let g = newGlobal({newCompartment: true});
+g.eval('function* f() { yield 1; yield 2; }');
+let dbg = Debugger(g);
+let genObj = null;
+let hits = 0;
+dbg.onEnterFrame = frame => {
+ // The first time onEnterFrame fires, there is no generator object, so
+ // there's nothing to test. The generator object doesn't exist until
+ // JSOP_GENERATOR is reached, right before the initial yield.
+ if (genObj === null) {
+ // Trigger step mode.
+ frame.onStep = function() {};
+ } else {
+ dbg.removeDebuggee(g);
+ dbg.addDebuggee(g);
+ hits++;
+ }
+};
+genObj = g.f();
+for (let x of genObj) {}
+assertEq(hits, 3);
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-generator-07.js b/js/src/jit-test/tests/debug/onEnterFrame-generator-07.js
new file mode 100644
index 0000000000..d6ac18d993
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-generator-07.js
@@ -0,0 +1,23 @@
+// Bug 1561935. Test interaction between debug traps for step mode and
+// DebugAfterYield.
+
+let g = newGlobal({newCompartment: true});
+g.eval('function* f() { yield 1; yield 2; }');
+let dbg = Debugger(g);
+let genObj = null;
+let hits = 0;
+dbg.onEnterFrame = frame => {
+ // The first time onEnterFrame fires, there is no generator object, so
+ // there's nothing to test. The generator object doesn't exist until
+ // JSOP_GENERATOR is reached, right before the initial yield.
+ if (genObj === null) {
+ // Trigger step mode.
+ frame.onStep = function() {};
+ } else {
+ dbg.removeDebuggee(g);
+ hits++;
+ }
+};
+genObj = g.f();
+for (let x of genObj) {}
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-generator-08.js b/js/src/jit-test/tests/debug/onEnterFrame-generator-08.js
new file mode 100644
index 0000000000..493b718155
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-generator-08.js
@@ -0,0 +1,17 @@
+// Bug 1556033. Test behavior of onEnterFrame "return" completion
+// values during explicit .throw() calls.
+
+let g = newGlobal({newCompartment: true});
+g.eval(`function* f(x) { }`);
+let dbg = new Debugger(g);
+
+let it = g.f();
+
+dbg.onEnterFrame = () => ({ return: "exit" });
+const result = it.throw();
+assertEq(result.value, "exit");
+assertEq(result.done, true);
+
+const result2 = it.next();
+assertEq(result2.value, undefined);
+assertEq(result2.done, true);
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-generator-09.js b/js/src/jit-test/tests/debug/onEnterFrame-generator-09.js
new file mode 100644
index 0000000000..20bee8d35a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-generator-09.js
@@ -0,0 +1,17 @@
+// Bug 1556033. Test behavior of onEnterFrame "return" completion
+// values during explicit .return() calls.
+
+let g = newGlobal({newCompartment: true});
+g.eval(`function* f(x) { }`);
+let dbg = new Debugger(g);
+
+let it = g.f();
+
+dbg.onEnterFrame = () => ({ return: "exit" });
+const result = it.return();
+assertEq(result.value, "exit");
+assertEq(result.done, true);
+
+const result2 = it.next();
+assertEq(result2.value, undefined);
+assertEq(result2.done, true);
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-generator-10.js b/js/src/jit-test/tests/debug/onEnterFrame-generator-10.js
new file mode 100644
index 0000000000..32546dc4d1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-generator-10.js
@@ -0,0 +1,17 @@
+// Bug 1556033. Test behavior of onEnterFrame "return" completion
+// values during explicit .next() calls.
+
+let g = newGlobal({newCompartment: true});
+g.eval(`function* f(x) { }`);
+let dbg = new Debugger(g);
+
+let it = g.f();
+
+dbg.onEnterFrame = () => ({ return: "exit" });
+const result = it.next();
+assertEq(result.value, "exit");
+assertEq(result.done, true);
+
+const result2 = it.next();
+assertEq(result2.value, undefined);
+assertEq(result2.done, true);
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-generator-resumption-01.js b/js/src/jit-test/tests/debug/onEnterFrame-generator-resumption-01.js
new file mode 100644
index 0000000000..e768b91777
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-generator-resumption-01.js
@@ -0,0 +1,36 @@
+// A debugger can {throw:} from onEnterFrame at any resume point in a generator.
+// It closes the generator.
+
+load(libdir + "asserts.js");
+
+let g = newGlobal({newCompartment: true});
+g.eval(`
+ function* f() { yield 1; }
+ var exn = new TypeError("object too hairy");
+`);
+
+let dbg = new Debugger;
+let gw = dbg.addDebuggee(g);
+
+// Repeat the test for each onEnterFrame event.
+// It fires up to three times:
+// - when the generator g.f is called;
+// - when we enter it to run to `yield 1`;
+// - when we resume after the yield to run to the end.
+for (let i = 0; i < 3; i++) {
+ let hits = 0;
+ dbg.onEnterFrame = frame => {
+ return hits++ < i ? undefined : {throw: gw.makeDebuggeeValue(g.exn)};
+ };
+ let genObj;
+ assertThrowsValue(
+ () => {
+ genObj = g.f();
+ for (let x of genObj) {}
+ },
+ g.exn
+ );
+ assertEq(hits, i + 1);
+ if (hits > 1)
+ assertEq(genObj.next().done, true);
+}
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-generator-resumption-02.js b/js/src/jit-test/tests/debug/onEnterFrame-generator-resumption-02.js
new file mode 100644
index 0000000000..fa1a8f233b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-generator-resumption-02.js
@@ -0,0 +1,39 @@
+// A Debugger can {return:} from onEnterFrame at any resume point in a generator.
+// Force-returning closes the generator.
+
+load(libdir + "asserts.js");
+
+let g = newGlobal({newCompartment: true});
+g.values = [1, 2, 3];
+g.eval(`function* f() { yield* values; }`);
+
+let dbg = Debugger(g);
+
+// onEnterFrame will fire up to 5 times.
+// - once for the initial call to g.f();
+// - four times at resume points:
+// - initial resume at the top of the generator body
+// - resume after yielding 1
+// - resume after yielding 2
+// - resume after yielding 3 (this resumption will run to the end).
+// This test ignores the initial call and focuses on resume points.
+for (let i = 1; i < 5; i++) {
+ let hits = 0;
+ dbg.onEnterFrame = frame => {
+ return hits++ < i ? undefined : {return: "we're done here"};
+ };
+
+ let genObj = g.f();
+ let actual = [];
+ while (true) {
+ let r = genObj.next();
+ if (r.done) {
+ assertDeepEq(r, {value: "we're done here", done: true});
+ break;
+ }
+ actual.push(r.value);
+ }
+ assertEq(hits, i + 1);
+ assertDeepEq(actual, g.values.slice(0, i - 1));
+ assertDeepEq(genObj.next(), {value: undefined, done: true});
+}
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-generator-resumption-03.js b/js/src/jit-test/tests/debug/onEnterFrame-generator-resumption-03.js
new file mode 100644
index 0000000000..b5461f2b8c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-generator-resumption-03.js
@@ -0,0 +1,35 @@
+// Returning {throw:} from onEnterFrame when resuming inside a try block in a
+// generator causes control to jump to the catch block.
+
+let g = newGlobal({newCompartment: true});
+g.eval(`
+ function* gen() {
+ try {
+ yield 0;
+ return "fail";
+ } catch (exc) {
+ assertEq(exc, "fit");
+ return "ok";
+ }
+ }
+`)
+
+let dbg = new Debugger(g);
+let hits = 0;
+dbg.onEnterFrame = frame => {
+ assertEq(frame.callee.name, "gen");
+ if (++hits == 3) {
+ // First hit is when calling gen();
+ // second hit is resuming at the implicit initial yield;
+ // third hit is resuming inside the try block.
+ return {throw: "fit"};
+ }
+};
+
+let it = g.gen();
+let result = it.next();
+assertEq(result.done, false);
+assertEq(result.value, 0);
+result = it.next();
+assertEq(result.done, true);
+assertEq(result.value, "ok");
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-generator-resumption-04.js b/js/src/jit-test/tests/debug/onEnterFrame-generator-resumption-04.js
new file mode 100644
index 0000000000..969cb0c70b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-generator-resumption-04.js
@@ -0,0 +1,34 @@
+// Returning {throw:} from onEnterFrame when resuming inside a try block in a
+// generator causes control to jump to the finally block.
+
+let g = newGlobal({newCompartment: true});
+g.eval(`
+ function* gen() {
+ try {
+ yield 0;
+ return "fail";
+ } finally {
+ return "ok"; // preempts exception unwinding
+ }
+ }
+`)
+
+let dbg = new Debugger(g);
+dbg.onEnterFrame = frame => {
+ if (!("hits" in frame)) {
+ frame.hits = 1;
+ } else if (++frame.hits == 3) {
+ // First hit is when calling gen();
+ // second hit is resuming at the implicit initial yield;
+ // third hit is resuming inside the try block.
+ return {throw: "fit"};
+ }
+};
+
+let it = g.gen();
+let result = it.next();
+assertEq(result.done, false);
+assertEq(result.value, 0);
+result = it.next();
+assertEq(result.done, true);
+assertEq(result.value, "ok");
diff --git a/js/src/jit-test/tests/debug/onEnterFrame-generator-resumption-05.js b/js/src/jit-test/tests/debug/onEnterFrame-generator-resumption-05.js
new file mode 100644
index 0000000000..f1f5a3860a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-generator-resumption-05.js
@@ -0,0 +1,22 @@
+// {return:} from the initial onEnterFrame for a generator is an error.
+
+load(libdir + "asserts.js");
+
+let g = newGlobal({newCompartment: true});
+g.values = [1, 2, 3];
+g.eval(`function* f(arr=values) { yield* arr; }`);
+
+let dbg = Debugger(g);
+
+let hits = 0;
+dbg.onEnterFrame = frame => {
+ assertEq(frame.callee.name, "f");
+ hits++;
+ return {return: 123};
+};
+dbg.uncaughtExceptionHook = exc => {
+ assertEq(exc instanceof TypeError, true);
+ return {throw: "REJECTED"};
+}
+assertThrowsValue(g.f, "REJECTED");
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-01.js b/js/src/jit-test/tests/debug/onExceptionUnwind-01.js
new file mode 100644
index 0000000000..ac2734c5e8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-01.js
@@ -0,0 +1,24 @@
+// Basic onExceptionUnwind hook test.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var hit = false;
+dbg.onExceptionUnwind = function (frame, exc) {
+ // onExceptionUnwind is called multiple times as the stack is unwound.
+ // Only check the first hit.
+ assertEq(arguments.length, 2);
+ assertEq(frame instanceof Debugger.Frame, true);
+ if (!hit) {
+ assertEq(exc, 101);
+ assertEq(frame.type, "call");
+ assertEq(frame.callee.name, "f");
+ assertEq(frame.older.type, "eval");
+ hit = true;
+ }
+};
+
+g.eval("function f() { throw 101; }");
+assertThrowsValue(function () { g.eval("f();"); }, 101);
+assertEq(hit, true);
diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-02.js b/js/src/jit-test/tests/debug/onExceptionUnwind-02.js
new file mode 100644
index 0000000000..b66f32316c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-02.js
@@ -0,0 +1,47 @@
+// The onExceptionUnwind hook is called multiple times as the stack unwinds.
+
+var g = newGlobal({newCompartment: true});
+g.debuggeeGlobal = this;
+g.dbg = null;
+g.eval("(" + function () {
+ dbg = new Debugger(debuggeeGlobal);
+ dbg.onExceptionUnwind = function (frame, exc) {
+ assertEq(frame instanceof Debugger.Frame, true);
+ assertEq(exc instanceof Debugger.Object, true);
+ var s = '!';
+ for (var f = frame; f; f = f.older)
+ if (f.type === "call")
+ s += f.callee.name;
+ s += ', ';
+ debuggeeGlobal.log += s;
+ };
+ } + ")();");
+
+var log;
+
+function k() {
+ throw new Error("oops"); // hook call 1
+}
+
+function j() {
+ k(); // hook call 2
+ log += 'j-unreached, ';
+}
+
+function h() {
+ j(); // hook call 3
+ log += 'h-unreached, ';
+}
+
+function f() {
+ try {
+ h(); // hook call 4
+ } catch (exc) {
+ log += 'f-catch';
+ }
+}
+
+log = '';
+f();
+g.dbg.enabled = false;
+assertEq(log, '!kjhf, !jhf, !hf, !f, f-catch');
diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-03.js b/js/src/jit-test/tests/debug/onExceptionUnwind-03.js
new file mode 100644
index 0000000000..aa77b473fd
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-03.js
@@ -0,0 +1,57 @@
+// The onExceptionUnwind hook is called multiple times as the stack unwinds.
+
+var g = newGlobal({newCompartment: true});
+g.debuggeeGlobal = this;
+g.dbg = null;
+g.eval("(" + function () {
+ dbg = new Debugger(debuggeeGlobal);
+ dbg.onExceptionUnwind = function (frame, exc) {
+ assertEq(frame instanceof Debugger.Frame, true);
+ assertEq(exc instanceof Debugger.Object, true);
+ var s = '!';
+ for (var f = frame; f; f = f.older)
+ if (f.type === "call")
+ s += f.callee.name;
+ s += ', ';
+ debuggeeGlobal.log += s;
+ };
+ } + ")();");
+
+var log;
+
+function k() {
+ try {
+ throw new Error("oops"); // hook call 1
+ } finally {
+ log += 'k-finally, ';
+ } // hook call 2
+}
+
+function j() {
+ k(); // hook call 3
+ log += 'j-unreached, ';
+}
+
+function h() {
+ try {
+ j(); // hook call 4
+ log += 'h-unreached, ';
+ } catch (exc) {
+ log += 'h-catch, ';
+ throw exc; // hook call 5
+ }
+}
+
+function f() {
+ try {
+ h(); // hook call 6
+ } catch (exc) {
+ log += 'f-catch, ';
+ }
+ log += 'f-after, ';
+}
+
+log = '';
+f();
+g.dbg.enabled = false;
+assertEq(log, '!kjhf, k-finally, !kjhf, !jhf, !hf, h-catch, !hf, !f, f-catch, f-after, ');
diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-04.js b/js/src/jit-test/tests/debug/onExceptionUnwind-04.js
new file mode 100644
index 0000000000..678d0bf4bb
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-04.js
@@ -0,0 +1,17 @@
+// onExceptionUnwind is not called for exceptions thrown and handled in the debugger.
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+g.log = '';
+dbg.onDebuggerStatement = function (frame) {
+ try {
+ throw new Error("oops");
+ } catch (exc) {
+ g.log += exc.message;
+ }
+};
+dbg.onExceptionUnwind = function (frame) {
+ g.log += 'BAD';
+};
+
+g.eval("debugger; log += ' ok';");
+assertEq(g.log, 'oops ok');
diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-05.js b/js/src/jit-test/tests/debug/onExceptionUnwind-05.js
new file mode 100644
index 0000000000..6138e89634
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-05.js
@@ -0,0 +1,12 @@
+// onExceptionUnwind returning undefined does not affect the thrown exception.
+
+var g = newGlobal({newCompartment: true});
+g.parent = this;
+g.eval("new Debugger(parent).onExceptionUnwind = function () {};");
+
+var obj = new Error("oops");
+try {
+ throw obj;
+} catch (exc) {
+ assertEq(exc, obj);
+}
diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-06.js b/js/src/jit-test/tests/debug/onExceptionUnwind-06.js
new file mode 100644
index 0000000000..19dd33d02a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-06.js
@@ -0,0 +1,13 @@
+// onExceptionUnwind assigning to argv[1] does not affect the thrown exception.
+
+var g = newGlobal({newCompartment: true});
+g.parent = this;
+g.eval("function f(frame, exc) { f2 = function () { return exc; }; exc = 123; }");
+g.eval("new Debugger(parent).onExceptionUnwind = f;");
+
+var obj = new Error("oops");
+try {
+ throw obj;
+} catch (exc) {
+ assertEq(exc, obj);
+}
diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-07.js b/js/src/jit-test/tests/debug/onExceptionUnwind-07.js
new file mode 100644
index 0000000000..c34e687d35
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-07.js
@@ -0,0 +1,15 @@
+// Unwinding due to uncatchable errors does not trigger onExceptionUnwind.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var hits = 0;
+dbg.onExceptionUnwind = function (frame, value) { hits = 'BAD'; };
+dbg.onDebuggerStatement = function (frame) {
+ if (hits++ === 0)
+ assertEq(frame.eval("debugger;"), null);
+ else
+ return null;
+}
+
+assertEq(g.eval("debugger; 2"), 2);
+assertEq(hits, 2);
diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-08.js b/js/src/jit-test/tests/debug/onExceptionUnwind-08.js
new file mode 100644
index 0000000000..de804c5e3f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-08.js
@@ -0,0 +1,18 @@
+// Ensure that uncaught exceptions thrown in onExceptionUnwind do not
+// propagate outward into debuggee execution.
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var frame;
+dbg.onExceptionUnwind = function (f, x) {
+ frame = f;
+ assertEq(frame.onStack, true);
+ throw 'unhandled';
+};
+dbg.onDebuggerStatement = function(f) {
+ assertEq(f.eval('throw 42').throw, 42);
+ assertEq(frame.onStack, false);
+};
+g.eval('debugger');
+
+// Don't fail just because we reported an uncaught exception.
+quit(0);
diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-09.js b/js/src/jit-test/tests/debug/onExceptionUnwind-09.js
new file mode 100644
index 0000000000..b220ce8c51
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-09.js
@@ -0,0 +1,15 @@
+// Ensure that ScriptDebugEpilogue gets called when onExceptionUnwind
+// terminates execution.
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var frame;
+dbg.onExceptionUnwind = function (f, x) {
+ frame = f;
+ assertEq(frame.onStack, true);
+ return null;
+};
+dbg.onDebuggerStatement = function(f) {
+ assertEq(f.eval('throw 42'), null);
+ assertEq(frame.onStack, false);
+};
+g.eval('debugger');
diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-10.js b/js/src/jit-test/tests/debug/onExceptionUnwind-10.js
new file mode 100644
index 0000000000..18f23e57b5
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-10.js
@@ -0,0 +1,16 @@
+// Ensure that ScriptDebugEpilogue gets called when onExceptionUnwind
+// terminates execution.
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var frame;
+dbg.onExceptionUnwind = function (f, x) {
+ frame = f;
+ assertEq(frame.type, 'eval');
+ assertEq(frame.onStack, true);
+ terminate();
+};
+dbg.onDebuggerStatement = function(f) {
+ assertEq(f.eval('throw 42'), null);
+ assertEq(frame.onStack, false);
+};
+g.eval('debugger');
diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-12.js b/js/src/jit-test/tests/debug/onExceptionUnwind-12.js
new file mode 100644
index 0000000000..c11bf74217
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-12.js
@@ -0,0 +1,14 @@
+var g = newGlobal({newCompartment: true});
+g.parent = this;
+g.hits = 0;
+g.eval("new Debugger(parent).onExceptionUnwind = function () { hits++; };");
+function f() {
+ var x = f();
+}
+try {
+ f();
+} catch (e) {
+ assertEq(e instanceof InternalError, true);
+} finally {
+ assertEq(g.hits, 0);
+}
diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-13.js b/js/src/jit-test/tests/debug/onExceptionUnwind-13.js
new file mode 100644
index 0000000000..ec12fa2a18
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-13.js
@@ -0,0 +1,16 @@
+// |jit-test| error: 4
+//
+// Test that we can handle doing debug mode OSR from onExceptionUnwind when
+// settling on a pc without a Baseline ICEntry.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+dbg.onExceptionUnwind = function () {};
+
+g.eval("" + function f(y) {
+ if (y > 0) {
+ throw 4;
+ }
+});
+g.eval("f(0)");
+g.eval("f(1)");
diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-14.js b/js/src/jit-test/tests/debug/onExceptionUnwind-14.js
new file mode 100644
index 0000000000..0322469283
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-14.js
@@ -0,0 +1,23 @@
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+g.eval("" + function f() {
+ throw 42;
+});
+
+g.eval("" + function g() {
+ throw new Error("42");
+});
+
+// Call the functions once. This will compile them in Ion under --ion-eager.
+g.eval("try { f(); } catch (e) { }");
+g.eval("try { g(); } catch (e) { }");
+
+// Now set an onExceptionUnwind hook so that the Ion-compiled functions will
+// try to bail out. The tail of the bytecode for f and g looks like 'throw;
+// retrval', with 'retrval' being unreachable. Since 'throw' is resumeAfter,
+// bailing out for debug mode will attempt to resume at 'retrval'. Test that
+// this case is handled.
+dbg.onExceptionUnwind = function f() { };
+g.eval("try { f(); } catch (e) { }");
+g.eval("try { g(); } catch (e) { }");
diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-15.js b/js/src/jit-test/tests/debug/onExceptionUnwind-15.js
new file mode 100644
index 0000000000..4e4361abe8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-15.js
@@ -0,0 +1,25 @@
+// Test that Ion->Baseline in-place debug mode bailout can recover the iterator
+// from the snapshot in a for-of loop.
+
+g = newGlobal({newCompartment: true});
+g.parent = this;
+g.eval("Debugger(parent).onExceptionUnwind=(function() {})");
+function* throwInNext() {
+ yield 1;
+ yield 2;
+ yield 3;
+ throw 42;
+}
+
+function f() {
+ for (var o of throwInNext());
+}
+
+var log = "";
+try {
+ f();
+} catch (e) {
+ log += e;
+}
+
+assertEq(log, "42");
diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-generators-01.js b/js/src/jit-test/tests/debug/onExceptionUnwind-generators-01.js
new file mode 100644
index 0000000000..ec6b846df1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-generators-01.js
@@ -0,0 +1,39 @@
+// Generator/async frames can be created and revived by the onExceptionUnwind hook.
+//
+// Modified copy of Frame-older-generators-01.js.
+
+let g = newGlobal({newCompartment: true});
+g.eval(`
+ function* gen() {
+ try { throw new Error("bad"); } catch { }
+ yield 1;
+ try { throw new Error("bad"); } catch { }
+ }
+ async function af() {
+ try { throw new Error("bad"); } catch { }
+ await 1;
+ try { throw new Error("bad"); } catch { }
+ }
+`);
+
+function test(expected, code) {
+ let dbg = Debugger(g);
+ let hits = 0;
+ let genFrame = null;
+ dbg.onExceptionUnwind = frame => {
+ hits++;
+ if (genFrame === null) {
+ genFrame = frame;
+ } else {
+ assertEq(frame, genFrame);
+ }
+ assertEq(genFrame.callee.name, expected);
+ }
+
+ g.eval(code);
+ assertEq(hits, 2);
+ dbg.removeDebuggee(g);
+}
+
+test("gen", "for (var x of gen()) {}");
+test("af", "af(); drainJobQueue();");
diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-01.js b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-01.js
new file mode 100644
index 0000000000..cccc444d79
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-01.js
@@ -0,0 +1,9 @@
+// Check that an onExceptionUnwind hook can force a frame to return a value early.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+dbg.onExceptionUnwind = function (frame, exc) {
+ return { return:"sproon" };
+};
+g.eval("function f() { throw 'ksnife'; }");
+assertEq(g.f(), "sproon");
diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-02.js b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-02.js
new file mode 100644
index 0000000000..9faaa886c6
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-02.js
@@ -0,0 +1,10 @@
+// Check that if an onExceptionUnwind hook forces a constructor frame to
+// return a primitive value, it still gets wrapped up in an object.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+dbg.onExceptionUnwind = function (frame, exc) {
+ return { return:"sproon" };
+};
+g.eval("function f() { throw 'ksnife'; }");
+assertEq(typeof new g.f, "object");
diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-03.js b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-03.js
new file mode 100644
index 0000000000..f18290f15f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-03.js
@@ -0,0 +1,11 @@
+// Check that an onExceptionUnwind hook can force a frame to throw a different exception.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+dbg.onExceptionUnwind = function (frame, exc) {
+ return { throw:"sproon" };
+};
+g.eval("function f() { throw 'ksnife'; }");
+assertThrowsValue(g.f, "sproon");
diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-04.js b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-04.js
new file mode 100644
index 0000000000..3b5f777952
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-04.js
@@ -0,0 +1,17 @@
+// Check that an onExceptionUnwind hook can force a frame to terminate.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+g.eval("function f() { throw 'ksnife'; }");
+var log = '';
+dbg.onDebuggerStatement = function (frame) {
+ log += 'd1';
+ assertEq(frame.eval("f();"), null);
+ log += 'd2';
+};
+dbg.onExceptionUnwind = function (frame, exc) {
+ log += 'u';
+ return null;
+};
+g.eval("debugger;");
+assertEq(log, "d1ud2");
diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-05.js b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-05.js
new file mode 100644
index 0000000000..00fe2aae44
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-05.js
@@ -0,0 +1,32 @@
+// Make sure that stacks in completion values are not lost when an exception
+// unwind hook returns undefined.
+
+let g = newGlobal({ newCompartment: true });
+g.eval(`
+ function foo() {
+ bar();
+ }
+ function bar() {
+ throw new Error();
+ }
+`);
+
+let dbg = Debugger(g);
+let unwindHits = 0, popHits = 0;
+dbg.onExceptionUnwind = frame => {
+ unwindHits++;
+ return undefined;
+}
+dbg.onEnterFrame = frame => {
+ frame.onPop = completion => {
+ assertEq(completion.stack.functionDisplayName, "bar");
+ popHits++;
+ };
+};
+
+try {
+ g.eval("foo()");
+} catch (e) {}
+assertEq(unwindHits, 3);
+assertEq(popHits, 3);
+dbg.removeDebuggee(g);
diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-async-02.js b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-async-02.js
new file mode 100644
index 0000000000..00a05d545a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-async-02.js
@@ -0,0 +1,31 @@
+// |jit-test| error:all-jobs-completed-successfully
+// Verifiy that onExceptionUnwind's force-return queues the promise
+// microtask to run in the debuggee's job queue, not the debugger's
+// AutoDebuggerJobQueueInterruption.
+
+let g = newGlobal({ newCompartment: true });
+g.eval(`
+ async function asyncFn(x) {
+ await Promise.resolve();
+ throw new Error();
+ }
+ function enterDebuggee(){}
+`);
+const dbg = new Debugger(g);
+
+(async function() {
+ let it = g.asyncFn();
+
+ // Force-return when the exception throws after await resume.
+ dbg.onExceptionUnwind = () => {
+ return { return: "exit" };
+ };
+
+ const result = await it;
+ assertEq(result, "exit");
+ // If execution here is resumed from the debugger's queue, this call will
+ // trigger DebuggeeWouldRun exception.
+ g.enterDebuggee();
+
+ throw "all-jobs-completed-successfully";
+})();
diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-async.js b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-async.js
new file mode 100644
index 0000000000..1c74d56c89
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-async.js
@@ -0,0 +1,35 @@
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+
+g.eval(`
+async function f() {
+ return e;
+}
+`);
+
+// Just continue
+dbg.onExceptionUnwind = function(frame) {
+ return undefined;
+};
+g.eval(`
+var E;
+f().catch(e => { exc = e });
+drainJobQueue();
+assertEq(exc instanceof ReferenceError, true);
+`);
+
+// Return with resumption value.
+dbg.onExceptionUnwind = function(frame) {
+ return {
+ return: 10
+ };
+};
+var val = g.eval(`
+var val;
+f().then(v => { val = v });
+drainJobQueue();
+val;
+`);
+assertEq(val, 10);
diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-generator.js b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-generator.js
new file mode 100644
index 0000000000..5824016c6c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-generator.js
@@ -0,0 +1,52 @@
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+
+g.eval(`
+function* f() {
+ e;
+}
+`);
+
+// To continue testing after uncaught exception, remember the exception and
+// return normal completion.
+var currentFrame;
+var uncaughtException;
+dbg.uncaughtExceptionHook = function(e) {
+ uncaughtException = e;
+ return {
+ return: "uncaught"
+ };
+};
+function testUncaughtException() {
+ uncaughtException = undefined;
+ var obj = g.eval(`f().next()`);
+ assertEq(obj.done, true);
+ assertEq(obj.value, 'uncaught');
+ assertEq(uncaughtException instanceof TypeError, true);
+}
+
+// Just continue
+dbg.onExceptionUnwind = function(frame) {
+ return undefined;
+};
+assertThrowsInstanceOf(() => g.eval(`f().next();`), g.ReferenceError);
+
+// Forced early return
+dbg.onExceptionUnwind = function(frame) {
+ currentFrame = frame;
+ return {
+ return: "foo"
+ };
+};
+var obj = g.eval(`f().next()`);
+assertEq(obj.done, true);
+assertEq(obj.value, "foo");
+
+// Bad resumption value
+dbg.onExceptionUnwind = function(frame) {
+ currentFrame = frame;
+ return {declaim: "gadzooks"};
+};
+testUncaughtException();
diff --git a/js/src/jit-test/tests/debug/onNewScript-01.js b/js/src/jit-test/tests/debug/onNewScript-01.js
new file mode 100644
index 0000000000..7c820ae58b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onNewScript-01.js
@@ -0,0 +1,44 @@
+// Basic newScript hook tests.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var seen = new WeakMap();
+var hits = 0;
+dbg.onNewScript = function (s) {
+ // Exceptions thrown from onNewScript are swept under the rug, but they
+ // will at least prevent hits from being the expected number.
+ assertEq(s instanceof Debugger.Script, true);
+ assertEq(!seen.has(s), true);
+ seen.set(s, true);
+ hits++;
+};
+
+dbg.uncaughtExceptionHook = function () { hits = -999; };
+
+// eval code
+hits = 0;
+assertEq(g.eval("2 + 2"), 4);
+assertEq(hits, 1);
+
+hits = 0;
+assertEq(g.eval("eval('2 + 3')"), 5);
+assertEq(hits, 2);
+
+// global code
+hits = 0;
+g.evaluate("3 + 4");
+assertEq(hits, 1);
+
+// function code
+hits = 0;
+var fn = g.Function("a", "return 5 + a;");
+assertEq(hits, 1);
+assertEq(fn(8), 13);
+assertEq(hits, 1);
+
+// cloning functions across compartments
+var g2 = newGlobal({newCompartment: true});
+dbg.addDebuggee(g2, dbg);
+hits = 0;
+cloneAndExecuteScript("(function(a) { return 5 + a; })", g2);
+assertEq(hits, 1);
diff --git a/js/src/jit-test/tests/debug/onNewScript-02.js b/js/src/jit-test/tests/debug/onNewScript-02.js
new file mode 100644
index 0000000000..1f284c48c3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onNewScript-02.js
@@ -0,0 +1,62 @@
+// Creating a new script with any number of subscripts triggers the newScript hook exactly once.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var seen = new WeakMap();
+var hits;
+dbg.onNewScript = function (s) {
+ assertEq(s instanceof Debugger.Script, true);
+ assertEq(!seen.has(s), true);
+ seen.set(s, true);
+ hits++;
+};
+
+dbg.uncaughtExceptionHook = function () { hits = -999; };
+
+function test(f) {
+ hits = 0;
+ f();
+ assertEq(hits, 1);
+}
+
+// eval declaring a function
+test(function () { g.eval("function A(m, n) { return m===0?n+1:n===0?A(m-1,1):A(m-1,A(m,n-1)); }"); });
+
+// evaluate declaring a function
+test(function () { g.eval("function g(a, b) { return b===0?a:g(b,a%b); }"); });
+
+// eval declaring multiple functions
+test(function () {
+ g.eval("function e(i) { return i===0||o(i-1); }\n" +
+ "function o(i) { return i!==0&&e(i-1); }\n");
+});
+
+// eval declaring nested functions
+test(function () { g.eval("function plus(x) { return function plusx(y) { return x + y; }; }"); });
+
+// eval with a function-expression
+test(function () { g.eval("[3].map(function (i) { return -i; });"); });
+
+// eval with getters and setters
+test(function () { g.eval("var obj = {get x() { return 1; }, set x(v) { print(v); }};"); });
+
+// Function with nested functions
+test(function () { return g.Function("a", "b", "return b - a;"); });
+
+// cloning a function with nested functions
+test(function () { cloneAndExecuteScript("(function(x) { return x + 1; })", g); });
+
+// eval declaring a star generator
+test(function () { g.eval("function* sg(n) { for (var i=0;i<n;i++) yield i; }"); });
+
+// eval creating several instances of a closure
+test(function () { g.eval("for (var i = 0; i < 7; i++)\n" +
+ " obj = function () { return obj; };\n"); });
+
+// non-strict-mode direct eval
+g.eval("function e(s) { eval(s); }");
+test(function () { g.e("function f(x) { return -x; }"); });
+
+// strict-mode direct eval
+g.eval("function E(s) { 'use strict'; eval(s); }");
+test(function () { g.E("function g(x) { return -x; }"); });
diff --git a/js/src/jit-test/tests/debug/onNewScript-03.js b/js/src/jit-test/tests/debug/onNewScript-03.js
new file mode 100644
index 0000000000..a725430395
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onNewScript-03.js
@@ -0,0 +1,7 @@
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+dbg.onNewScript = function (s) {
+ eval(longScript);
+}
+const longScript = "var x = 1;\n" + new Array(5000).join("x + ") + "x";
+g.eval(longScript);
diff --git a/js/src/jit-test/tests/debug/onNewScript-CloneAndExecuteScript.js b/js/src/jit-test/tests/debug/onNewScript-CloneAndExecuteScript.js
new file mode 100644
index 0000000000..abc417c4a8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onNewScript-CloneAndExecuteScript.js
@@ -0,0 +1,28 @@
+// Debugger should be notified of scripts created with cloneAndExecuteScript.
+
+var g = newGlobal({newCompartment: true});
+var g2 = newGlobal({newCompartment: true});
+var dbg = new Debugger(g, g2);
+var log = '';
+
+dbg.onNewScript = function (evalScript) {
+ log += 'e';
+
+ dbg.onNewScript = function (clonedScript) {
+ log += 'c';
+ clonedScript.setBreakpoint(0, {
+ hit(frame) {
+ log += 'b';
+ assertEq(frame.script, clonedScript);
+ }
+ });
+ };
+};
+
+dbg.onDebuggerStatement = function (frame) {
+ log += 'd';
+};
+
+assertEq(log, '');
+g.cloneAndExecuteScript("debugger; // nee", g2);
+assertEq(log, 'ecbd');
diff --git a/js/src/jit-test/tests/debug/onNewScript-ExecuteInGlobalAndReturnScope.js b/js/src/jit-test/tests/debug/onNewScript-ExecuteInGlobalAndReturnScope.js
new file mode 100644
index 0000000000..96439ea2a0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onNewScript-ExecuteInGlobalAndReturnScope.js
@@ -0,0 +1,32 @@
+// Debugger should be notified of scripts created with ExecuteInGlobalAndReturnScope.
+
+var g = newGlobal({newCompartment: true});
+var g2 = newGlobal({newCompartment: true});
+var dbg = new Debugger(g, g2);
+var log = '';
+var canary = 42;
+
+dbg.onNewScript = function (evalScript) {
+ log += 'e';
+
+ dbg.onNewScript = function (clonedScript) {
+ log += 'c';
+ clonedScript.setBreakpoint(0, {
+ hit(frame) {
+ log += 'b';
+ assertEq(frame.script, clonedScript);
+ }
+ });
+ };
+};
+
+dbg.onDebuggerStatement = function (frame) {
+ log += 'd';
+};
+
+assertEq(log, '');
+var evalScopes = g.evalReturningScope("canary = 'dead'; let lex = 42; debugger; // nee", g2);
+assertEq(log, 'ecbd');
+assertEq(canary, 42);
+assertEq(evalScopes.vars.canary, 'dead');
+assertEq(evalScopes.lexicals.lex, 42);
diff --git a/js/src/jit-test/tests/debug/onNewScript-off-main-thread-01.js b/js/src/jit-test/tests/debug/onNewScript-off-main-thread-01.js
new file mode 100644
index 0000000000..c88e4e0402
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onNewScript-off-main-thread-01.js
@@ -0,0 +1,17 @@
+// |jit-test| skip-if: helperThreadCount() === 0
+
+// We still get onNewScript notifications for code compiled off the main thread.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+var log;
+dbg.onNewScript = function (s) {
+ log += 's';
+ assertEq(s.source.text, '"t" + "wine"');
+}
+
+log = '';
+g.offThreadCompileScript('"t" + "wine"');
+assertEq(g.runOffThreadScript(), 'twine');
+assertEq(log, 's');
diff --git a/js/src/jit-test/tests/debug/onNewScript-off-main-thread-02.js b/js/src/jit-test/tests/debug/onNewScript-off-main-thread-02.js
new file mode 100644
index 0000000000..25552f8bf4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onNewScript-off-main-thread-02.js
@@ -0,0 +1,12 @@
+// |jit-test| skip-if: helperThreadCount() === 0
+
+var global = newGlobal({newCompartment: true});
+var dbg = new Debugger(global);
+
+dbg.onNewScript = function (s) {
+ if (s.url === "<string>")
+ assertEq(s.getChildScripts().length, 1);
+};
+
+global.eval('offThreadCompileScript("function inner() { \\\"use asm\\\"; function xxx() {} return xxx; }");');
+global.eval('runOffThreadScript();');
diff --git a/js/src/jit-test/tests/debug/onNewScript-wasm-01.js b/js/src/jit-test/tests/debug/onNewScript-wasm-01.js
new file mode 100644
index 0000000000..d169da6285
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onNewScript-wasm-01.js
@@ -0,0 +1,33 @@
+// |jit-test| skip-if: !wasmDebuggingEnabled()
+// Draining the job queue from an onNewScript hook reporting a streamed wasm
+// module should not deadlock.
+
+ignoreUnhandledRejections();
+
+try {
+ WebAssembly.compileStreaming();
+ // Avoid mixing the test's jobs with the debuggee's, so that
+ // automated checks can make sure AutoSaveJobQueue only
+ // suspends debuggee work.
+ drainJobQueue();
+} catch (err) {
+ assertEq(String(err).indexOf("not supported with --no-threads") !== -1, true);
+ quit();
+}
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+
+var source = new g.Uint8Array(wasmTextToBinary('(module (func unreachable))'));
+g.source = source;
+
+var dbg = new Debugger(g);
+dbg.allowWasmBinarySource = true;
+dbg.onNewScript = function (s, g) {
+ drainJobQueue();
+};
+
+g.eval('WebAssembly.instantiateStreaming(source);');
+
+drainJobQueue();
diff --git a/js/src/jit-test/tests/debug/onNewScript-wasm-02.js b/js/src/jit-test/tests/debug/onNewScript-wasm-02.js
new file mode 100644
index 0000000000..3586fe4c2e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onNewScript-wasm-02.js
@@ -0,0 +1,41 @@
+// |jit-test| skip-if: !wasmDebuggingEnabled()
+// Draining the job queue from an onNewScript hook reporting a streamed wasm
+// module should not deadlock.
+
+ignoreUnhandledRejections();
+
+try {
+ WebAssembly.compileStreaming();
+ // Avoid mixing the test's jobs with the debuggee's, so that
+ // automated checks can make sure AutoSaveJobQueue only
+ // suspends debuggee work.
+ drainJobQueue();
+} catch (err) {
+ assertEq(String(err).indexOf("not supported with --no-threads") !== -1, true);
+ quit();
+}
+
+var g = newGlobal({newCompartment: true});
+
+var source = new g.Uint8Array(wasmTextToBinary('(module (func unreachable))'));
+g.source = source;
+
+var dbg = new Debugger(g);
+dbg.allowWasmBinarySource = true;
+dbg.onNewScript = function (s, g) {
+ drainJobQueue();
+};
+
+// For the old code to deadlock, OffThreadPromiseRuntimeState::internalDrain
+// needs to get two Dispatchables at once when it swaps queues. A call to
+// instantiateStreaming enqueues a job that will kick off a helper thread, so to
+// make sure that two helper threads have had time to complete and enqueue their
+// Dispatchables, we put a delay in the job queue after the helper thread
+// launches.
+g.eval(`
+ WebAssembly.instantiateStreaming(source);
+ WebAssembly.instantiateStreaming(source);
+ Promise.resolve().then(() => sleep(0.1));
+`);
+
+drainJobQueue();
diff --git a/js/src/jit-test/tests/debug/optimized-out-01.js b/js/src/jit-test/tests/debug/optimized-out-01.js
new file mode 100644
index 0000000000..cb0210dcbf
--- /dev/null
+++ b/js/src/jit-test/tests/debug/optimized-out-01.js
@@ -0,0 +1,47 @@
+// Tests that we can reflect optimized out values.
+//
+// Unfortunately these tests are brittle. They depend on opaque JIT heuristics
+// kicking in.
+
+// Use gczeal 0 to keep CGC from invalidating Ion code and causing test failures.
+gczeal(0);
+
+load(libdir + "jitopts.js");
+
+if (!jitTogglesMatch(Opts_Ion2NoOffthreadCompilation))
+ quit(0);
+
+withJitOptions(Opts_Ion2NoOffthreadCompilation, function () {
+ var g = newGlobal({newCompartment: true});
+ var dbg = new Debugger;
+
+ // Note that this *depends* on CCW scripted functions being opaque to Ion
+ // optimization and not deoptimizing the frames below the call to toggle.
+ g.toggle = function toggle(d) {
+ if (d) {
+ dbg.addDebuggee(g);
+ var frame = dbg.getNewestFrame();
+ assertEq(frame.implementation, "ion");
+ // x is unused and should be elided.
+ assertEq(frame.environment.getVariable("x").optimizedOut, true);
+ assertEq(frame.arguments[1].optimizedOut, true);
+ }
+ };
+
+ g.eval("" + function f(d, x) {
+ "use strict";
+ eval("g(d, x)"); // `eval` to avoid inlining g.
+ });
+
+ g.eval("" + function g(d, x) {
+ "use strict";
+ for (var i = 0; i < 100; i++);
+ toggle(d);
+ });
+
+ g.eval("(" + function test() {
+ for (i = 0; i < 15; i++)
+ f(false, 42);
+ f(true, 42);
+ } + ")();");
+});
diff --git a/js/src/jit-test/tests/debug/optimized-out-02.js b/js/src/jit-test/tests/debug/optimized-out-02.js
new file mode 100644
index 0000000000..c7690cca7a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/optimized-out-02.js
@@ -0,0 +1,38 @@
+// Test that prevUpToDate on frames are cleared.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+g.eval(`
+function outer(unaliasedArg) {
+ var unaliasedVar = unaliasedArg + 42;
+ var aliasedVar = unaliasedArg;
+
+ inner();
+ return unaliasedVar; // To prevent the JIT from optimizing out unaliasedVar.
+
+ function inner() {
+ aliasedVar++;
+ }
+}
+`);
+
+var log = "";
+for (var script of dbg.findScripts()) {
+ if (script.displayName === "inner") {
+ script.setBreakpoint(0, { hit: function(frame) {
+ // Force updateLiveScopes.
+ var outerEnv = frame.environment;
+
+ // Get the environment of outer's frame on the stack, so that we may
+ // recover unaliased bindings in the debug scope.
+ outerEnv = frame.older.environment;
+ log += outerEnv.getVariable('unaliasedArg'); // 42
+ log += outerEnv.getVariable('unaliasedVar'); // 84
+ log += outerEnv.getVariable('aliasedVar'); // 42
+ }});
+ }
+}
+
+g.outer(42);
+assertEq(log, "428442");
diff --git a/js/src/jit-test/tests/debug/optimized-out-03.js b/js/src/jit-test/tests/debug/optimized-out-03.js
new file mode 100644
index 0000000000..aada963326
--- /dev/null
+++ b/js/src/jit-test/tests/debug/optimized-out-03.js
@@ -0,0 +1,31 @@
+// Test that eval-in-frame throws on accessing optimized out values.
+
+load(libdir + "jitopts.js");
+
+if (!jitTogglesMatch(Opts_IonEagerNoOffthreadCompilation))
+ quit(0);
+
+withJitOptions(Opts_IonEagerNoOffthreadCompilation, function() {
+ var dbgGlobal = newGlobal({newCompartment: true});
+ var dbg = new dbgGlobal.Debugger();
+ dbg.addDebuggee(this);
+
+ function f() {
+ assertEq(dbg.getNewestFrame().older.eval("print(a)").throw.unsafeDereference().toString(),
+ "Error: variable 'a' has been optimized out");
+ }
+
+ // Test optimized out binding in function scope.
+ (function () {
+ var a = 1;
+ for (var i = 0; i < 1; i++) { f(); a = 2; }
+ })();
+
+ // Test optimized out binding in block scope.
+ (function () {
+ {
+ let a = 1;
+ for (var i = 0; i < 1; i++) { f(); a = 2; }
+ }
+ })();
+});
diff --git a/js/src/jit-test/tests/debug/optimized-out-arrow-this.js b/js/src/jit-test/tests/debug/optimized-out-arrow-this.js
new file mode 100644
index 0000000000..adc1702fa1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/optimized-out-arrow-this.js
@@ -0,0 +1,38 @@
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+g.eval(`
+function f() {
+ // |this| in arrow-functions refers to the |this| binding in outer functions.
+ // So when |frame.eval("this")| is executed, the outer |this| binding should
+ // be returned, unless it has been optimised out.
+ (() => {})();
+
+ // Ensure a |this| binding is created for |f|.
+ return this;
+}
+`);
+
+var errors = [];
+
+function enterFrame(frame) {
+ // Disable the handler so we don't call it recursively through |frame.eval|.
+ dbg.onEnterFrame = undefined;
+
+ // Store the error when resolving |this| was unsuccessful.
+ var r = frame.eval("this");
+ if (r.throw) {
+ errors.push(r.throw);
+ }
+
+ // Re-enable the handler.
+ dbg.onEnterFrame = enterFrame;
+};
+
+dbg.onEnterFrame = enterFrame;
+
+g.f();
+
+assertEq(errors.length, 1);
+assertEq(errors[0].unsafeDereference().toString(),
+ "Error: variable 'this' has been optimized out");
diff --git a/js/src/jit-test/tests/debug/private-methods-eval-in-frame.js b/js/src/jit-test/tests/debug/private-methods-eval-in-frame.js
new file mode 100644
index 0000000000..a8fd1d9583
--- /dev/null
+++ b/js/src/jit-test/tests/debug/private-methods-eval-in-frame.js
@@ -0,0 +1,25 @@
+// |jit-test| --enable-private-fields; --enable-private-methods;
+load(libdir + 'asserts.js');
+load(libdir + 'evalInFrame.js');
+
+class B {
+ #priv() {
+ return 12;
+ }
+
+ #val = '';
+ set #x(x) {
+ this.#val = x + ' haha';
+ }
+ get #x() {
+ return this.#val;
+ }
+
+ ef(str) {
+ return evalInFrame(0, str);
+ }
+}
+
+var b = new B();
+assertEq(b.ef(`this.#priv();`), 12);
+assertEq(b.ef(`this.#x = 'Hi'; this.#x`), 'Hi haha');
diff --git a/js/src/jit-test/tests/debug/prologueFailure-01.js b/js/src/jit-test/tests/debug/prologueFailure-01.js
new file mode 100644
index 0000000000..369941e432
--- /dev/null
+++ b/js/src/jit-test/tests/debug/prologueFailure-01.js
@@ -0,0 +1,32 @@
+g = newGlobal({newCompartment: true});
+g.parent = this;
+
+function installHook() {
+ let calledTimes = 0;
+ function hook() {
+ calledTimes++;
+
+ // Allow the new.target.prototype get to throw.
+ if (calledTimes === 1)
+ return undefined;
+
+ return {
+ return: undefined
+ };
+ }
+
+ Debugger(parent).onExceptionUnwind = hook;
+}
+
+
+g.eval("(" + installHook + ")()");
+
+var handler = {
+ get(t, p) {
+ throw new TypeError;
+ }
+};
+
+
+var f = new Proxy(function(){}, handler);
+new f();
diff --git a/js/src/jit-test/tests/debug/prologueFailure-02.js b/js/src/jit-test/tests/debug/prologueFailure-02.js
new file mode 100644
index 0000000000..54bde11d05
--- /dev/null
+++ b/js/src/jit-test/tests/debug/prologueFailure-02.js
@@ -0,0 +1,49 @@
+g = newGlobal({newCompartment: true});
+g.parent = this;
+
+function installHook() {
+ let calledTimes = 0;
+ function hook(frame) {
+ calledTimes++;
+ switch (calledTimes) {
+ case 1:
+ // Proxy get trap
+ assertEq(frame.type, "call");
+ assertEq(frame.script.displayName.includes("get"), true);
+ break;
+ case 2:
+ // wrapper function. There is no entry for notRun
+ assertEq(frame.type, "call");
+ assertEq(frame.script.displayName.includes("wrapper"), true);
+ break;
+ case 3:
+ assertEq(frame.type, "global");
+ // Force the top-level to return cleanly, so that we can tell
+ // assertion failures from the intended throwing.
+ return { return: undefined };
+
+ default:
+ // that's the whole chain.
+ assertEq(false, true);
+ }
+ }
+
+ Debugger(parent).onExceptionUnwind = hook;
+}
+
+
+g.eval("(" + installHook + ")()");
+
+var handler = {
+ get(t, p) {
+ throw new TypeError;
+ }
+};
+
+function notRun() {}
+
+function wrapper() {
+ var f = new Proxy(notRun, handler);
+ new f();
+}
+wrapper();
diff --git a/js/src/jit-test/tests/debug/prologueFailure-03.js b/js/src/jit-test/tests/debug/prologueFailure-03.js
new file mode 100644
index 0000000000..d404428c0a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/prologueFailure-03.js
@@ -0,0 +1,26 @@
+g = newGlobal({newCompartment: true});
+g.parent = this;
+g.eval("(" + function() {
+ let calledTimes = 0;
+ Debugger(parent).onExceptionUnwind = function(frame) {
+ switch (calledTimes++) {
+ case 0:
+ assertEq(frame.older.type, "global");
+ break;
+ case 1:
+ // Force toplevel to return placidly so that we can tell assertions
+ // from the throwing in the test.
+ assertEq(frame.older, null);
+ return { return: undefined };
+ default:
+ assertEq(false, true);
+ }
+ }
+} + ")()");
+
+var handler = {
+ get() {
+ r;
+ }
+};
+new(new Proxy(function() {}, handler));
diff --git a/js/src/jit-test/tests/debug/relazify-debugee-script-01.js b/js/src/jit-test/tests/debug/relazify-debugee-script-01.js
new file mode 100644
index 0000000000..1a16046b8b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/relazify-debugee-script-01.js
@@ -0,0 +1,14 @@
+var g = newGlobal({ newCompartment: true });
+var dbg = Debugger(g);
+
+dbg.collectCoverageInfo = true;
+
+g.eval(`
+ function fn() {}
+ fn();
+`);
+
+dbg = null;
+gc();
+
+relazifyFunctions();
diff --git a/js/src/jit-test/tests/debug/resumption-01.js b/js/src/jit-test/tests/debug/resumption-01.js
new file mode 100644
index 0000000000..7fc9857ac7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/resumption-01.js
@@ -0,0 +1,12 @@
+// Simple {throw:} resumption.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+dbg.onDebuggerStatement = function (stack) { return {throw: "oops"}; };
+
+assertThrowsValue(function () { g.eval("debugger;"); }, "oops");
+
+g.eval("function f() { debugger; }");
+assertThrowsValue(function () { g.f(); }, "oops");
diff --git a/js/src/jit-test/tests/debug/resumption-02.js b/js/src/jit-test/tests/debug/resumption-02.js
new file mode 100644
index 0000000000..1762d61767
--- /dev/null
+++ b/js/src/jit-test/tests/debug/resumption-02.js
@@ -0,0 +1,9 @@
+// Simple {return:} resumption.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+dbg.onDebuggerStatement = function (stack) { return {return: 1234}; };
+
+assertEq(g.eval("debugger; false;"), 1234);
+g.eval("function f() { debugger; return 'bad'; }");
+assertEq(g.f(), 1234);
diff --git a/js/src/jit-test/tests/debug/resumption-03.js b/js/src/jit-test/tests/debug/resumption-03.js
new file mode 100644
index 0000000000..f48992ced6
--- /dev/null
+++ b/js/src/jit-test/tests/debug/resumption-03.js
@@ -0,0 +1,35 @@
+// Returning and throwing objects.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+g.debuggeeGlobal = this;
+g.eval("(" + function () {
+ var how, what;
+ var dbg = new Debugger(debuggeeGlobal);
+ dbg.onDebuggerStatement = function (frame) {
+ if (frame.callee.name === "configure") {
+ how = frame.arguments[0];
+ what = frame.arguments[1];
+ } else {
+ var resume = {};
+ resume[how] = what;
+ return resume;
+ }
+ };
+ } + ")();");
+
+function configure(how, what) { debugger; }
+function fire() { debugger; }
+
+var d = new Date;
+configure('return', d);
+assertEq(fire(), d);
+configure('return', Math);
+assertEq(fire(), Math);
+
+var x = new Error('oh no what are you doing');
+configure('throw', x);
+assertThrowsValue(fire, x);
+configure('throw', parseInt);
+assertThrowsValue(fire, parseInt);
diff --git a/js/src/jit-test/tests/debug/resumption-05.js b/js/src/jit-test/tests/debug/resumption-05.js
new file mode 100644
index 0000000000..9c3f7cd54a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/resumption-05.js
@@ -0,0 +1,35 @@
+// null resumption value means terminate the debuggee
+
+var g = newGlobal({newCompartment: true});
+g.debuggeeGlobal = this;
+g.eval("(" + function () {
+ var dbg = new Debugger(debuggeeGlobal);
+ dbg.onDebuggerStatement = function (frame) {
+ if (frame.callee === null) {
+ // The first debugger statement below.
+ debuggeeGlobal.log += "1";
+ var cv = frame.eval("f();");
+ assertEq(cv, null);
+ debuggeeGlobal.log += "2";
+ } else {
+ // The second debugger statement.
+ debuggeeGlobal.log += "3";
+ assertEq(frame.callee.name, "f");
+ return null;
+ }
+ };
+ } + ")()");
+
+var log = "";
+debugger;
+
+function f() {
+ log += "4";
+ try {
+ debugger; // the debugger terminates us here
+ } finally {
+ log += "5"; // this should not execute
+ }
+}
+
+assertEq(log, "1432");
diff --git a/js/src/jit-test/tests/debug/resumption-07.js b/js/src/jit-test/tests/debug/resumption-07.js
new file mode 100644
index 0000000000..2ba239e81a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/resumption-07.js
@@ -0,0 +1,34 @@
+// Return resumption values to non-debuggee frames.
+
+load(libdir + 'asserts.js');
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+
+var log;
+
+function handlerWithResumption(resumption) {
+ return function (frame) {
+ log += 'd';
+ dbg.removeDebuggee(g);
+ return resumption;
+ };
+}
+
+log = '';
+dbg.onDebuggerStatement = handlerWithResumption(undefined);
+dbg.addDebuggee(g);
+assertEq(g.eval('debugger; 42;'), 42);
+assertEq(log, 'd');
+
+log = '';
+dbg.onDebuggerStatement = handlerWithResumption({ return: 1729 });
+dbg.addDebuggee(g);
+assertEq(g.eval('debugger; 42;'), 1729);
+assertEq(log, 'd');
+
+log = '';
+dbg.onDebuggerStatement = handlerWithResumption(null);
+dbg.addDebuggee(g);
+assertEq(g.evaluate('debugger; 42;', { catchTermination: true }), 'terminated');
+assertEq(log, 'd');
diff --git a/js/src/jit-test/tests/debug/resumption-08.js b/js/src/jit-test/tests/debug/resumption-08.js
new file mode 100644
index 0000000000..044029584e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/resumption-08.js
@@ -0,0 +1,93 @@
+// Check whether we respect resumption values when toggling debug mode on->off
+// from various points with live scripts on the stack.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger;
+
+function reset() {
+ dbg.onEnterFrame = undefined;
+ dbg.onDebuggerStatement = undefined;
+ dbg.addDebuggee(g);
+ g.eval("(" + function test() {
+ for (i = 0; i < 5; i++)
+ f(42);
+ } + ")();");
+}
+
+g.eval("" + function f(d) {
+ return g(d);
+});
+
+g.eval("" + function g(d) {
+ debugger;
+ return d;
+});
+
+function testResumptionValues(handlerSetter) {
+ // Test normal return.
+ reset();
+ handlerSetter(undefined);
+ assertEq(g.eval("(" + function test() { return f(42); } + ")();"), 42);
+
+ // Test forced return.
+ reset();
+ handlerSetter({ return: "not 42" });
+ assertEq(g.eval("(" + function test() { return f(42); } + ")();"), "not 42");
+
+ // Test throw.
+ reset();
+ handlerSetter({ throw: "thrown 42" });
+ try {
+ g.eval("(" + function test() { return f(42); } + ")();");;
+ } catch (e) {
+ assertEq(e, "thrown 42");
+ }
+}
+
+// Turn off from within the prologue.
+testResumptionValues(function (resumptionVal) {
+ dbg.onEnterFrame = function (frame) {
+ if (frame.older) {
+ if (frame.older.older) {
+ dbg.removeDebuggee(g);
+ return resumptionVal;
+ }
+ }
+ };
+});
+
+// Turn off from within the epilogue.
+testResumptionValues(function (resumptionVal) {
+ dbg.onEnterFrame = function (frame) {
+ if (frame.older) {
+ if (frame.older.older) {
+ frame.onPop = function () {
+ dbg.removeDebuggee(g);
+ return resumptionVal;
+ };
+ }
+ }
+ };
+});
+
+// Turn off from within debugger statement handler.
+testResumptionValues(function (resumptionVal) {
+ dbg.onDebuggerStatement = function (frame) {
+ dbg.removeDebuggee(g);
+ return resumptionVal;
+ };
+});
+
+// Turn off from within debug trap handler.
+testResumptionValues(function (resumptionVal) {
+ dbg.onEnterFrame = function (frame) {
+ if (frame.older) {
+ if (frame.older.older) {
+ frame.onStep = function () {
+ dbg.removeDebuggee(g);
+ return resumptionVal;
+ }
+ }
+ }
+ };
+});
diff --git a/js/src/jit-test/tests/debug/resumption-09.js b/js/src/jit-test/tests/debug/resumption-09.js
new file mode 100644
index 0000000000..5a04a32aea
--- /dev/null
+++ b/js/src/jit-test/tests/debug/resumption-09.js
@@ -0,0 +1,41 @@
+// Test exception stack behavior when reusing completion values as resumption
+// values.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+g.eval(`
+ function foo() {
+ bar();
+ }
+ function bar() {
+ debugger;
+ }
+ function baz() {
+ throw new Error();
+ }
+`);
+
+var dbg = Debugger(g);
+dbg.onDebuggerStatement = frame => {
+ return frame.eval("baz()");
+};
+
+let popHits = 0;
+dbg.onEnterFrame = frame => {
+ frame.onPop = completion => {
+ popHits++;
+ // Resumption values ignore any 'stack' property, and the script location of
+ // the place where the hook was called will be used when throwing.
+ if (popHits <= 2) {
+ assertEq(completion.stack.functionDisplayName, "baz");
+ } else {
+ assertEq(completion.stack.functionDisplayName, "bar");
+ }
+ };
+};
+
+try {
+ g.eval("foo()");
+} catch (e) {}
+assertEq(popHits, 5);
diff --git a/js/src/jit-test/tests/debug/resumption-error-01.js b/js/src/jit-test/tests/debug/resumption-error-01.js
new file mode 100644
index 0000000000..65cf81abb5
--- /dev/null
+++ b/js/src/jit-test/tests/debug/resumption-error-01.js
@@ -0,0 +1,7 @@
+// A resumption value can't have both {return:} and {throw:} properties.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+dbg.onDebuggerStatement = stack => ({return: 1, throw: 2});
+dbg.uncaughtExceptionHook = exc => ({return: "corrected"});
+assertEq(g.eval("debugger; false;"), "corrected");
diff --git a/js/src/jit-test/tests/debug/resumption-error-02.js b/js/src/jit-test/tests/debug/resumption-error-02.js
new file mode 100644
index 0000000000..0b943d900e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/resumption-error-02.js
@@ -0,0 +1,16 @@
+// Error handling if parsing a resumption value throws.
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var rv;
+dbg.onDebuggerStatement = stack => rv;
+dbg.uncaughtExceptionHook = function (exc) {
+ assertEq(exc, "BANG");
+ return {return: "recovered"};
+};
+
+rv = {get throw() { throw "BANG"; }};
+assertEq(g.eval("debugger; false;"), "recovered");
+
+rv = new Proxy({}, {has() { throw "BANG"; }});
+assertEq(g.eval("debugger; false;"), "recovered");
diff --git a/js/src/jit-test/tests/debug/save-queue-resets-draining.js b/js/src/jit-test/tests/debug/save-queue-resets-draining.js
new file mode 100644
index 0000000000..3d63c09bf8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/save-queue-resets-draining.js
@@ -0,0 +1,18 @@
+// The draining state is reset when saving the job queue.
+
+let g = newGlobal({newCompartment: true});
+
+let dbg = new Debugger();
+let gw = dbg.addDebuggee(g);
+
+dbg.onDebuggerStatement = frame => {
+ // Enqueue a new job from within the debugger while executing another job
+ // from outside of the debugger.
+ enqueueJob(function() {});
+};
+
+g.eval(`
+ enqueueJob(function() {
+ debugger;
+ });
+`);
diff --git a/js/src/jit-test/tests/debug/setter-argc.js b/js/src/jit-test/tests/debug/setter-argc.js
new file mode 100644
index 0000000000..f56195d00d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/setter-argc.js
@@ -0,0 +1,52 @@
+// Check that setters throw TypeError when passed no arguments, instead of crashing.
+
+function check(obj) {
+ let proto = Object.getPrototypeOf(obj);
+ let props = Object.getOwnPropertyNames(proto);
+ for (let prop of props) {
+ let desc = Object.getOwnPropertyDescriptor(proto, prop);
+ if (desc.set) {
+ print("bleah: " + JSON.stringify(prop));
+ assertEq(typeof desc.set, 'function');
+ try {
+ desc.set.call(obj);
+ assertEq("should have thrown TypeError", false);
+ } catch (e) {
+ assertEq(e instanceof TypeError, true);
+ }
+ }
+ }
+}
+
+var dbg = new Debugger;
+var g = newGlobal({newCompartment: true});
+var gw = dbg.addDebuggee(g);
+
+// Debugger
+check(dbg);
+
+// Debugger.Memory
+check(dbg.memory);
+
+// Debugger.Object
+g.eval('function f() { debugger; }');
+var fw = gw.getOwnPropertyDescriptor('f').value;
+check(fw);
+
+// Debugger.Script
+check(fw.script);
+
+// Debugger.Source
+check(fw.script.source);
+
+// Debugger.Environment
+check(fw.environment);
+
+// Debugger.Frame
+var log = '';
+dbg.onDebuggerStatement = function(frame) {
+ log += 'd';
+ check(frame);
+}
+g.eval('f()');
+assertEq(log, 'd');
diff --git a/js/src/jit-test/tests/debug/surfaces-01.js b/js/src/jit-test/tests/debug/surfaces-01.js
new file mode 100644
index 0000000000..0907916a4d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/surfaces-01.js
@@ -0,0 +1,17 @@
+// Check superficial characteristics of functions and properties (not functionality).
+
+function checkFunction(obj, name, nargs) {
+ var desc = Object.getOwnPropertyDescriptor(obj, name);
+ assertEq(desc.configurable, true, name + " should be configurable");
+ assertEq(desc.writable, true, name + " should be writable");
+ assertEq(desc.enumerable, false, name + " should be non-enumerable");
+ assertEq(desc.value, obj[name]); // well obviously
+ assertEq(typeof desc.value, 'function', name + " should be a function");
+ assertEq(desc.value.length, nargs, name + " should have .length === " + nargs);
+}
+
+checkFunction(this, "Debugger", 1);
+
+assertEq(Debugger.prototype.constructor, Debugger);
+assertEq(Object.prototype.toString.call(Debugger.prototype), "[object Debugger]");
+assertEq(Object.getPrototypeOf(Debugger.prototype), Object.prototype);
diff --git a/js/src/jit-test/tests/debug/surfaces-02.js b/js/src/jit-test/tests/debug/surfaces-02.js
new file mode 100644
index 0000000000..66e95d0ec9
--- /dev/null
+++ b/js/src/jit-test/tests/debug/surfaces-02.js
@@ -0,0 +1,31 @@
+// Debugger.prototype.onDebuggerStatement
+
+load(libdir + 'asserts.js');
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+gc(); // don't assert marking debug hooks
+assertEq(dbg.onDebuggerStatement, undefined);
+
+function f() {}
+
+assertThrowsInstanceOf(function () { dbg.onDebuggerStatement = null; }, TypeError);
+assertThrowsInstanceOf(function () { dbg.onDebuggerStatement = "bad"; }, TypeError);
+assertThrowsInstanceOf(function () { dbg.onDebuggerStatement = {}; }, TypeError);
+dbg.onDebuggerStatement = f;
+assertEq(dbg.onDebuggerStatement, f);
+
+assertEq(Object.getOwnPropertyNames(dbg).length, 0);
+var desc = Object.getOwnPropertyDescriptor(Debugger.prototype, "onDebuggerStatement");
+assertEq(desc.configurable, true);
+assertEq(desc.enumerable, false);
+
+assertThrowsInstanceOf(function () { desc.get(); }, TypeError);
+assertThrowsInstanceOf(function () { desc.get.call(undefined); }, TypeError);
+assertThrowsInstanceOf(function () { desc.get.call(Debugger.prototype); }, TypeError);
+assertEq(desc.get.call(dbg), f);
+
+assertThrowsInstanceOf(function () { desc.set(); }, TypeError);
+assertThrowsInstanceOf(function () { desc.set.call(dbg); }, TypeError);
+assertThrowsInstanceOf(function () { desc.set.call({}, f); }, TypeError);
+assertThrowsInstanceOf(function () { desc.set.call(Debugger.prototype, f); }, TypeError);
diff --git a/js/src/jit-test/tests/debug/surfaces-03.js b/js/src/jit-test/tests/debug/surfaces-03.js
new file mode 100644
index 0000000000..0c440c6836
--- /dev/null
+++ b/js/src/jit-test/tests/debug/surfaces-03.js
@@ -0,0 +1,19 @@
+// dumb basics of uncaughtExceptionHook
+
+load(libdir + 'asserts.js');
+
+var desc = Object.getOwnPropertyDescriptor(Debugger.prototype, "uncaughtExceptionHook");
+assertEq(typeof desc.get, 'function');
+assertEq(typeof desc.set, 'function');
+
+assertThrowsInstanceOf(function () { Debugger.prototype.uncaughtExceptionHook = null; }, TypeError);
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+assertEq(desc.get.call(dbg), null);
+assertThrowsInstanceOf(function () { dbg.uncaughtExceptionHook = []; }, TypeError);
+assertThrowsInstanceOf(function () { dbg.uncaughtExceptionHook = 3; }, TypeError);
+dbg.uncaughtExceptionHook = Math.sin;
+assertEq(dbg.uncaughtExceptionHook, Math.sin);
+dbg.uncaughtExceptionHook = null;
+assertEq(dbg.uncaughtExceptionHook, null);
diff --git a/js/src/jit-test/tests/debug/surfaces-offsets.js b/js/src/jit-test/tests/debug/surfaces-offsets.js
new file mode 100644
index 0000000000..418361a924
--- /dev/null
+++ b/js/src/jit-test/tests/debug/surfaces-offsets.js
@@ -0,0 +1,37 @@
+// Invalid offsets result in exceptions, not bogus results.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = Debugger(g);
+var hits;
+dbg.onDebuggerStatement = function (frame) {
+ assertEq(frame.script.getOffsetLocation(frame.offset).lineNumber, g.line);
+ assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(String(frame.offset)).lineNumber; }, Error);
+ assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(Object(frame.offset)).lineNumber; }, Error);
+
+ assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(-1).lineNumber; }, Error);
+ assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(1000000).lineNumber; }, Error);
+ assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(0.25).lineNumber; }, Error);
+ assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(+Infinity).lineNumber; }, Error);
+ assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(-Infinity).lineNumber; }, Error);
+ assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(NaN).lineNumber; }, Error);
+
+ assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(false).lineNumber; }, Error);
+ assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(true).lineNumber; }, Error);
+ assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(undefined).lineNumber; }, Error);
+ assertThrowsInstanceOf(function () { frame.script.getOffsetLocation().lineNumber; }, Error);
+
+ // We assume that at least one whole number between 0 and frame.offset is invalid.
+ assertThrowsInstanceOf(
+ function () {
+ for (var i = 0; i < frame.offset; i++)
+ frame.script.getOffsetLocation(i).lineNumber;
+ },
+ Error);
+
+ hits++;
+};
+
+hits = 0;
+g.eval("var line = new Error().lineNumber; debugger;");
diff --git a/js/src/jit-test/tests/debug/testEarlyReturnOnCall.js b/js/src/jit-test/tests/debug/testEarlyReturnOnCall.js
new file mode 100644
index 0000000000..f783f5a1f0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/testEarlyReturnOnCall.js
@@ -0,0 +1,24 @@
+var g = newGlobal({newCompartment: true});
+g.eval("var success = false");
+g.eval("function ponies() {}");
+g.eval("function foo() { ponies(); success = false }");
+
+var dbg = new Debugger(g);
+dbg.onEnterFrame = function(frame) {
+ // The goal here is force an early return on the 'call' instruction,
+ // which should be the 3rd step (callgname, undefined, call)
+ var step = 0;
+ frame.onStep = function() {
+ ++step;
+ if (step == 2) {
+ g.success = true;
+ return;
+ }
+ if (step == 3)
+ return { return: undefined }
+ }
+ frame.onPop = function() { new Error(); /* boom */ }
+}
+
+g.foo();
+assertEq(g.success, true);
diff --git a/js/src/jit-test/tests/debug/uncaughtExceptionHook-01.js b/js/src/jit-test/tests/debug/uncaughtExceptionHook-01.js
new file mode 100644
index 0000000000..2303e2339f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/uncaughtExceptionHook-01.js
@@ -0,0 +1,19 @@
+// Uncaught exceptions in the debugger itself are delivered to the
+// uncaughtExceptionHook.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var log;
+dbg.onDebuggerStatement = function () {
+ log += 'x';
+ throw new TypeError("fail");
+};
+dbg.uncaughtExceptionHook = function (exc) {
+ assertEq(this, dbg);
+ assertEq(exc instanceof TypeError, true);
+ log += '!';
+};
+
+log = '';
+g.eval("debugger");
+assertEq(log, 'x!');
diff --git a/js/src/jit-test/tests/debug/uncaughtExceptionHook-02.js b/js/src/jit-test/tests/debug/uncaughtExceptionHook-02.js
new file mode 100644
index 0000000000..ee58b40182
--- /dev/null
+++ b/js/src/jit-test/tests/debug/uncaughtExceptionHook-02.js
@@ -0,0 +1,12 @@
+// Returning a bad resumption value causes an exception that is reported to the
+// uncaughtExceptionHook.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+dbg.onDebuggerStatement = function () { return {oops: "bad resumption value"}; };
+dbg.uncaughtExceptionHook = function (exc) {
+ assertEq(exc instanceof TypeError, true);
+ return {return: "pass"};
+};
+
+assertEq(g.eval("debugger"), "pass");
diff --git a/js/src/jit-test/tests/debug/uncaughtExceptionHook-03.js b/js/src/jit-test/tests/debug/uncaughtExceptionHook-03.js
new file mode 100644
index 0000000000..0924c8f023
--- /dev/null
+++ b/js/src/jit-test/tests/debug/uncaughtExceptionHook-03.js
@@ -0,0 +1,34 @@
+// |jit-test| error: ReferenceError
+// If uncaughtExceptionHook is absent, the debuggee is terminated.
+
+var g = newGlobal({newCompartment: true});
+g.debuggeeGlobal = this;
+g.eval("(" + function () {
+ var dbg = Debugger(debuggeeGlobal);
+ dbg.onDebuggerStatement = function (frame) {
+ if (frame.callee === null) {
+ debuggeeGlobal.log += '1';
+ var cv = frame.eval("f();");
+ debuggeeGlobal.log += '2';
+ assertEq(cv, null);
+ } else {
+ assertEq(frame.callee.name, "f");
+ debuggeeGlobal.log += '3';
+ throw new ReferenceError("oops");
+ }
+ };
+ } + ")();");
+
+function onerror(msg) {
+}
+
+var log = '';
+debugger;
+function f() {
+ try {
+ debugger;
+ } finally {
+ log += 'x';
+ }
+}
+assertEq(log, '132');
diff --git a/js/src/jit-test/tests/debug/uncaughtExceptionHook-resumption-01.js b/js/src/jit-test/tests/debug/uncaughtExceptionHook-resumption-01.js
new file mode 100644
index 0000000000..5ef62b6f6d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/uncaughtExceptionHook-resumption-01.js
@@ -0,0 +1,25 @@
+// uncaughtExceptionHook returns a resumption value.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var rv;
+dbg.onDebuggerStatement = function () { throw 15; };
+dbg.uncaughtExceptionHook = function (exc) {
+ assertEq(exc, 15);
+ return rv;
+};
+
+// case 1: undefined
+rv = undefined;
+g.eval("debugger");
+
+// case 2: throw
+rv = {throw: 57};
+var result;
+assertThrowsValue(function () { g.eval("debugger"); }, 57);
+
+// case 3: return
+rv = {return: 42};
+assertEq(g.eval("debugger;"), 42);
diff --git a/js/src/jit-test/tests/debug/uncaughtExceptionHook-resumption-02.js b/js/src/jit-test/tests/debug/uncaughtExceptionHook-resumption-02.js
new file mode 100644
index 0000000000..3ba7070a7b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/uncaughtExceptionHook-resumption-02.js
@@ -0,0 +1,25 @@
+// uncaughtExceptionHook resumption value other than undefined does not cause
+// further hooks to be skipped.
+
+var g = newGlobal({newCompartment: true});
+var log;
+
+function makeDebug(g, name) {
+ var dbg = new Debugger(g);
+ dbg.onDebuggerStatement = function (frame) {
+ log += name;
+ throw new Error(name);
+ };
+ dbg.uncaughtExceptionHook = function (exc) {
+ assertEq(exc.message, name);
+ return name == "2" ? {return: 42} : undefined;
+ };
+}
+
+var arr = [];
+for (var i = 0; i < 6; i++)
+ arr[i] = makeDebug(g, "" + i);
+
+log = '';
+assertEq(g.eval("debugger;"), 42);
+assertEq(log, "012345");
diff --git a/js/src/jit-test/tests/debug/uncaughtExceptionHook-resumption-03.js b/js/src/jit-test/tests/debug/uncaughtExceptionHook-resumption-03.js
new file mode 100644
index 0000000000..7835271e3c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/uncaughtExceptionHook-resumption-03.js
@@ -0,0 +1,12 @@
+// After an onExceptionUnwind hook throws, if uncaughtExceptionHook returns
+// undefined, the original exception continues to propagate.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var log = '';
+dbg.onExceptionUnwind = function () { log += "1"; throw new Error("oops"); };
+dbg.uncaughtExceptionHook = function () { log += "2"; };
+
+g.eval("var x = new Error('oops');");
+g.eval("try { throw x; } catch (exc) { assertEq(exc, x); }");
+assertEq(log, "12");
diff --git a/js/src/jit-test/tests/debug/wasm-01.js b/js/src/jit-test/tests/debug/wasm-01.js
new file mode 100644
index 0000000000..1d9396aa1b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-01.js
@@ -0,0 +1,32 @@
+// |jit-test| skip-if: !wasmDebuggingEnabled()
+
+// Tests that wasm module scripts are available via findScripts.
+
+var g = newGlobal({newCompartment: true});
+g.eval(`o = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (func) (export "" (func 0)))')));`);
+
+function isWasm(script) { return script.format === "wasm"; }
+
+var dbg = new Debugger(g);
+var foundScripts1 = dbg.findScripts().filter(isWasm);
+assertEq(foundScripts1.length, 1);
+var found = foundScripts1[0];
+
+// Add another module, we should be able to find it via findScripts.
+g.eval(`o2 = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (func) (export "a" (func 0)))')));`);
+var foundScripts2 = dbg.findScripts().filter(isWasm);
+assertEq(foundScripts2.length, 2);
+
+// The first module should be in the list as wrapping the same wasm module
+// twice gets the same Debugger.Script.
+assertEq(foundScripts2.indexOf(found) !== -1, true);
+
+// The two modules are distinct.
+assertEq(foundScripts2[0] !== foundScripts2[1], true);
+
+// We should be able to find the same script via its source.
+for (var ws of foundScripts2) {
+ var scriptsFromSource = dbg.findScripts({ source: ws.source });
+ assertEq(scriptsFromSource.length, 1);
+ assertEq(scriptsFromSource[0], ws);
+}
diff --git a/js/src/jit-test/tests/debug/wasm-02.js b/js/src/jit-test/tests/debug/wasm-02.js
new file mode 100644
index 0000000000..c0f3c52f3a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-02.js
@@ -0,0 +1,21 @@
+// |jit-test| skip-if: !wasmDebuggingEnabled()
+
+// Tests that wasm module scripts are available via onNewScript.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+var gotScript;
+dbg.onNewScript = (script) => {
+ gotScript = script;
+}
+
+g.eval(`o = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (func) (export "" (func 0)))')));`);
+assertEq(gotScript.format, "wasm");
+
+var gotScript2 = gotScript;
+g.eval(`o = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (func) (export "a" (func 0)))')));`);
+assertEq(gotScript.format, "wasm");
+
+// The two wasm Debugger.Scripts are distinct.
+assertEq(gotScript !== gotScript2, true);
diff --git a/js/src/jit-test/tests/debug/wasm-03.js b/js/src/jit-test/tests/debug/wasm-03.js
new file mode 100644
index 0000000000..99f377f18b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-03.js
@@ -0,0 +1,35 @@
+// |jit-test| skip-if: !wasmDebuggingEnabled()
+
+// Tests that wasm module scripts have synthesized sources.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+var s;
+dbg.onNewScript = (script) => {
+ s = script;
+}
+
+g.eval(`o = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (func) (export "" (func 0)))')));`);
+assertEq(s.format, "wasm");
+
+var source = s.source;
+
+assertEq(s.source, source);
+assertEq(source.introductionType, "wasm");
+assertEq(source.introductionScript, s);
+// Wasm sources shouldn't be considered source mapped.
+assertEq(!source.sourceMapURL, true);
+assertThrowsInstanceOf(() => source.sourceMapURL = 'foo', Error);
+// We must have some text.
+assertEq(!!source.text, true);
+
+// TODOshu: Wasm is moving very fast and what we return for these values is
+// currently not interesting to test. Instead, test that they do not throw.
+source.url;
+source.element;
+source.displayURL;
+source.introductionOffset;
+source.elementAttributeName;
diff --git a/js/src/jit-test/tests/debug/wasm-04.js b/js/src/jit-test/tests/debug/wasm-04.js
new file mode 100644
index 0000000000..3d18e6ce1b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-04.js
@@ -0,0 +1,27 @@
+// |jit-test| skip-if: !wasmDebuggingEnabled()
+
+// Tests that wasm module scripts throw for everything except text.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+var s;
+dbg.onNewScript = (script) => {
+ s = script;
+}
+
+g.eval(`o = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (func) (export "" (func 0)))')));`);
+assertEq(s.format, "wasm");
+
+assertThrowsInstanceOf(() => s.displayName, Error);
+assertThrowsInstanceOf(() => s.parameterNames, Error);
+assertThrowsInstanceOf(() => s.url, Error);
+assertThrowsInstanceOf(() => s.sourceStart, Error);
+assertThrowsInstanceOf(() => s.sourceLength, Error);
+assertThrowsInstanceOf(() => s.global, Error);
+assertThrowsInstanceOf(() => s.getChildScripts(), Error);
+assertThrowsInstanceOf(() => s.getAllOffsets(), Error);
+assertThrowsInstanceOf(() => s.getBreakpoint(0), Error);
+assertThrowsInstanceOf(() => s.getOffsetsCoverage(), Error);
diff --git a/js/src/jit-test/tests/debug/wasm-06-onEnterFrame-null.js b/js/src/jit-test/tests/debug/wasm-06-onEnterFrame-null.js
new file mode 100644
index 0000000000..b4afd9753f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-06-onEnterFrame-null.js
@@ -0,0 +1,17 @@
+// |jit-test| test-also=--wasm-compiler=optimizing; exitstatus: 3; skip-if: !wasmDebuggingEnabled()
+// Checking resumption values for 'null' at onEnterFrame.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal('');
+var dbg = new Debugger();
+dbg.addDebuggee(g);
+sandbox.eval(`
+var wasm = wasmTextToBinary('(module (func (nop)) (export "test" 0))');
+var m = new WebAssembly.Instance(new WebAssembly.Module(wasm));`);
+dbg.onEnterFrame = function (frame) {
+ if (frame.type !== "wasmcall") return;
+ return null;
+};
+g.eval("m.exports.test()");
+assertEq(false, true);
diff --git a/js/src/jit-test/tests/debug/wasm-06-onPop-null.js b/js/src/jit-test/tests/debug/wasm-06-onPop-null.js
new file mode 100644
index 0000000000..76bfb146ef
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-06-onPop-null.js
@@ -0,0 +1,19 @@
+// |jit-test| test-also=--wasm-compiler=optimizing; exitstatus: 3; skip-if: !wasmDebuggingEnabled()
+// Checking resumption values for 'null' at frame's onPop.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal('');
+var dbg = new Debugger();
+dbg.addDebuggee(g);
+sandbox.eval(`
+var wasm = wasmTextToBinary('(module (func (nop)) (export "test" 0))');
+var m = new WebAssembly.Instance(new WebAssembly.Module(wasm));`);
+dbg.onEnterFrame = function (frame) {
+ if (frame.type !== "wasmcall") return;
+ frame.onPop = function () {
+ return null;
+ };
+};
+g.eval("m.exports.test()");
+assertEq(false, true);
diff --git a/js/src/jit-test/tests/debug/wasm-06.js b/js/src/jit-test/tests/debug/wasm-06.js
new file mode 100644
index 0000000000..d8a8aa8b18
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-06.js
@@ -0,0 +1,332 @@
+// |jit-test| test-also=--wasm-compiler=optimizing; error: TestComplete; skip-if: !wasmDebuggingEnabled()
+// Tests that wasm module scripts raises onEnterFrame and onLeaveFrame events.
+
+load(libdir + "asserts.js");
+
+function runWasmWithDebugger(wast, lib, init, done) {
+ let g = newGlobal({newCompartment: true});
+ let dbg = new Debugger(g);
+
+ g.eval(`
+var wasm = wasmTextToBinary('${wast}');
+var lib = ${lib || 'undefined'};
+var m = new WebAssembly.Instance(new WebAssembly.Module(wasm), lib);`);
+
+ init(dbg, g);
+ let result = undefined, error = undefined;
+ try {
+ result = g.eval("m.exports.test()");
+ } catch (ex) {
+ error = ex;
+ }
+ done(dbg, result, error, g);
+}
+
+// Checking if onEnterFrame is fired for wasm frames and verifying the content
+// of the frame and environment properties.
+var onEnterFrameCalled, onLeaveFrameCalled, onExceptionUnwindCalled, testComplete;
+runWasmWithDebugger(
+ '(module (func (result i32) (i32.const 42)) (export "test" (func 0)))', undefined,
+ function (dbg) {
+ var wasmScript = dbg.findScripts().filter(s => s.format == 'wasm')[0];
+ assertEq(!!wasmScript, true);
+ onEnterFrameCalled = 0;
+ onLeaveFrameCalled = 0;
+ testComplete = false;
+ var evalFrame;
+ dbg.onEnterFrame = function (frame) {
+ if (frame.type !== 'wasmcall') {
+ if (frame.type === 'eval')
+ evalFrame = frame;
+ return;
+ }
+
+ onEnterFrameCalled++;
+
+ assertEq(frame.script, wasmScript);
+ assertEq(frame.older, evalFrame);
+ assertEq(frame.type, 'wasmcall');
+
+ let env = frame.environment;
+ assertEq(env instanceof Object, true);
+ assertEq(env.inspectable, true);
+ assertEq(env.parent !== null, true);
+ assertEq(env.type, 'declarative');
+ assertEq(env.calleeScript, null);
+ assertEq(Array.isArray(env.names()), true);
+ assertEq(env.names().length, 0);
+
+ frame.onPop = function() {
+ onLeaveFrameCalled++;
+ testComplete = true;
+ };
+ };
+ },
+ function (dbg, result, error) {
+ assertEq(testComplete, true);
+ assertEq(onEnterFrameCalled, 1);
+ assertEq(onLeaveFrameCalled, 1);
+ assertEq(result, 42);
+ assertEq(error, undefined);
+ }
+);
+
+// Checking the dbg.getNewestFrame() and frame.older.
+runWasmWithDebugger(
+ '(module (import "env" "ex" (func $fn1)) (func $fn2 (call $fn1)) (export "test" (func $fn2)))',
+ '{env: { ex: () => { }}}',
+ function (dbg) {
+ onEnterFrameCalled = 0;
+ onLeaveFrameCalled = 0;
+ testComplete = false;
+ var evalFrame, wasmFrame;
+ dbg.onEnterFrame = function (frame) {
+ onEnterFrameCalled++;
+
+ assertEq(dbg.getNewestFrame(), frame);
+
+ switch (frame.type) {
+ case 'eval':
+ evalFrame = frame;
+ break;
+ case 'wasmcall':
+ wasmFrame = frame;
+ break;
+ case 'call':
+ assertEq(frame.older, wasmFrame);
+ assertEq(frame.older.older, evalFrame);
+ assertEq(frame.older.older.older, null);
+ testComplete = true;
+ break;
+ }
+
+ frame.onPop = function() {
+ onLeaveFrameCalled++;
+ };
+ };
+ },
+ function (dbg, result, error) {
+ assertEq(testComplete, true);
+ assertEq(onEnterFrameCalled, 3);
+ assertEq(onLeaveFrameCalled, 3);
+ assertEq(error, undefined);
+ }
+);
+
+// Checking if we can enumerate frames and find 'wasmcall' one.
+runWasmWithDebugger(
+ '(module (import "env" "ex" (func $fn1)) (func $fn2 (call $fn1)) (export "test" (func $fn2)))',
+ '{env: { ex: () => { debugger; }}}',
+ function (dbg) {
+ testComplete = false;
+ dbg.onDebuggerStatement = function (frame) {
+ assertEq(frame.type, 'call');
+ assertEq(frame.older.type, 'wasmcall');
+ assertEq(frame.older.older.type, 'eval');
+ assertEq(frame.older.older.older, null);
+ testComplete = true;
+ }
+ },
+ function (dbg, result, error) {
+ assertEq(testComplete, true);
+ assertEq(error, undefined);
+ }
+);
+
+// Checking if onPop works without onEnterFrame handler.
+runWasmWithDebugger(
+ '(module (import "env" "ex" (func $fn1)) (func $fn2 (call $fn1)) (export "test" (func $fn2)))',
+ '{env: { ex: () => { debugger; }}}',
+ function (dbg) {
+ onLeaveFrameCalled = 0;
+ dbg.onDebuggerStatement = function (frame) {
+ if (!frame.older || frame.older.type != 'wasmcall')
+ return;
+ frame.older.onPop = function () {
+ onLeaveFrameCalled++;
+ };
+ }
+ },
+ function (dbg, result, error) {
+ assertEq(onLeaveFrameCalled, 1);
+ assertEq(error, undefined);
+ }
+);
+
+// Checking if function return values are not changed.
+runWasmWithDebugger(
+ '(module (func (result f64) (f64.const 0.42)) (export "test" (func 0)))', undefined,
+ function (dbg) {
+ dbg.onEnterFrame = function (frame) {
+ dbg.onPop = function () {};
+ };
+ },
+ function (dbg, result, error) {
+ assertEq(result, 0.42);
+ assertEq(error, undefined);
+ }
+);
+runWasmWithDebugger(
+ '(module (func (result f32) (f32.const 4.25)) (export "test" (func 0)))', undefined,
+ function (dbg) {
+ dbg.onEnterFrame = function (frame) {
+ dbg.onPop = function () {};
+ };
+ },
+ function (dbg, result, error) {
+ assertEq(result, 4.25);
+ assertEq(error, undefined);
+ }
+);
+
+// Checking if onEnterFrame/onExceptionUnwind work during exceptions --
+// `unreachable` causes wasm to throw WebAssembly.RuntimeError exception.
+runWasmWithDebugger(
+ '(module (func (unreachable)) (export "test" (func 0)))', undefined,
+ function (dbg) {
+ onEnterFrameCalled = 0;
+ onLeaveFrameCalled = 0;
+ onExceptionUnwindCalled = 0;
+ dbg.onEnterFrame = function (frame) {
+ if (frame.type !== "wasmcall") return;
+ onEnterFrameCalled++;
+ frame.onPop = function() {
+ onLeaveFrameCalled++;
+ };
+ };
+ dbg.onExceptionUnwind = function (frame) {
+ if (frame.type !== "wasmcall") return;
+ onExceptionUnwindCalled++;
+ };
+ },
+ function (dbg, result, error, g) {
+ assertEq(onEnterFrameCalled, 1);
+ assertEq(onLeaveFrameCalled, 1);
+ assertEq(onExceptionUnwindCalled, 1);
+ assertEq(error instanceof g.WebAssembly.RuntimeError, true);
+ }
+);
+
+// Checking if onEnterFrame/onExceptionUnwind work during exceptions
+// originated in the JavaScript import call.
+runWasmWithDebugger(
+ '(module (import "env" "ex" (func $fn1)) (func $fn2 (call $fn1)) (export "test" (func $fn2)))',
+ '{env: { ex: () => { throw new Error(); }}}',
+ function (dbg) {
+ onEnterFrameCalled = 0;
+ onLeaveFrameCalled = 0;
+ onExceptionUnwindCalled = 0;
+ dbg.onEnterFrame = function (frame) {
+ if (frame.type !== "wasmcall") return;
+ onEnterFrameCalled++;
+ frame.onPop = function() {
+ onLeaveFrameCalled++;
+ };
+ };
+ dbg.onExceptionUnwind = function (frame) {
+ if (frame.type !== "wasmcall") return;
+ onExceptionUnwindCalled++;
+ };
+ },
+ function (dbg, result, error, g) {
+ assertEq(onEnterFrameCalled, 1);
+ assertEq(onLeaveFrameCalled, 1);
+ assertEq(onExceptionUnwindCalled, 1);
+ assertEq(error instanceof g.Error, true);
+ }
+);
+
+// Checking throwing in the handler.
+runWasmWithDebugger(
+ '(module (func (unreachable)) (export "test" (func 0)))', undefined,
+ function (dbg) {
+ dbg.uncaughtExceptionHook = function (value) {
+ assertEq(value instanceof Error, true);
+ return {throw: 'test'};
+ };
+ dbg.onEnterFrame = function (frame) {
+ if (frame.type !== "wasmcall") return;
+ throw new Error();
+ };
+ },
+ function (dbg, result, error) {
+ assertEq(error, 'test');
+ }
+);
+runWasmWithDebugger(
+ '(module (func (unreachable)) (export "test" (func 0)))', undefined,
+ function (dbg) {
+ dbg.uncaughtExceptionHook = function (value) {
+ assertEq(value instanceof Error, true);
+ return {throw: 'test'};
+ };
+ dbg.onEnterFrame = function (frame) {
+ if (frame.type !== "wasmcall") return;
+ frame.onPop = function () {
+ throw new Error();
+ }
+ };
+ },
+ function (dbg, result, error) {
+ assertEq(error, 'test');
+ }
+);
+
+// Checking resumption values for JS_THROW.
+runWasmWithDebugger(
+ '(module (func (nop)) (export "test" (func 0)))', undefined,
+ function (dbg, g) {
+ dbg.onEnterFrame = function (frame) {
+ if (frame.type !== "wasmcall") return;
+ return {throw: 'test'};
+ };
+ },
+ function (dbg, result, error, g) {
+ assertEq(error, 'test');
+ }
+);
+runWasmWithDebugger(
+ '(module (func (nop)) (export "test" (func 0)))', undefined,
+ function (dbg, g) {
+ dbg.onEnterFrame = function (frame) {
+ if (frame.type !== "wasmcall") return;
+ frame.onPop = function () {
+ return {throw: 'test'};
+ }
+ };
+ },
+ function (dbg, result, error, g) {
+ assertEq(error, 'test');
+ }
+);
+
+// Checking resumption values for JS_RETURN (not implemented by wasm baseline).
+runWasmWithDebugger(
+ '(module (func (unreachable)) (export "test" (func 0)))', undefined,
+ function (dbg) {
+ dbg.onEnterFrame = function (frame) {
+ if (frame.type !== "wasmcall") return;
+ return {return: 2};
+ };
+ },
+ function (dbg, result, error) {
+ assertEq(result, undefined, 'NYI: result == 2, if JS_RETURN is implemented');
+ assertEq(error != undefined, true, 'NYI: error == undefined, if JS_RETURN is implemented');
+ }
+);
+runWasmWithDebugger(
+ '(module (func (unreachable)) (export "test" (func 0)))', undefined,
+ function (dbg) {
+ dbg.onEnterFrame = function (frame) {
+ if (frame.type !== "wasmcall") return;
+ frame.onPop = function () {
+ return {return: 2};
+ }
+ };
+ },
+ function (dbg, result, error) {
+ assertEq(result, undefined, 'NYI: result == 2, if JS_RETURN is implemented');
+ assertEq(error != undefined, true, 'NYI: error == undefined, if JS_RETURN is implemented');
+ }
+);
+throw "TestComplete";
diff --git a/js/src/jit-test/tests/debug/wasm-07.js b/js/src/jit-test/tests/debug/wasm-07.js
new file mode 100644
index 0000000000..be32a9977a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-07.js
@@ -0,0 +1,39 @@
+// |jit-test| test-also=--wasm-compiler=optimizing; skip-if: !wasmDebuggingEnabled()
+
+// Checking existence of all frame.offset references during onEnterFrame,
+// onLeaveFrame and onStep events in the source code, and that we can
+// potentially resolve offset back to the line/column.
+
+load(libdir + "wasm.js");
+
+var offsets;
+wasmRunWithDebugger(
+ '(module (func (nop) (nop)) (export "test" (func 0)))',
+ undefined,
+ function ({dbg}) {
+ offsets = [];
+ dbg.onEnterFrame = function (frame) {
+ if (frame.type != 'wasmcall') {
+ return;
+ }
+ offsets.push(frame.offset);
+ frame.onStep = function () {
+ offsets.push(frame.offset);
+ };
+ frame.onPop = function () {
+ offsets.push(frame.offset);
+ };
+ };
+ },
+ function ({wasmScript, error}) {
+ assertEq(error, undefined);
+ assertEq(offsets.length, 5);
+ offsets.forEach(offset => {
+ var loc = wasmScript.getOffsetLocation(offset);
+ assertEq(loc.isEntryPoint, true);
+ assertEq(loc.lineNumber > 0, true);
+ assertEq(loc.columnNumber > 0, true);
+ assertEq(wasmScript.getLineOffsets(loc.lineNumber).length, 1);
+ });
+ }
+);
diff --git a/js/src/jit-test/tests/debug/wasm-08.js b/js/src/jit-test/tests/debug/wasm-08.js
new file mode 100644
index 0000000000..b0b61d54fd
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-08.js
@@ -0,0 +1,121 @@
+// |jit-test| test-also=--wasm-compiler=optimizing; skip-if: !wasmDebuggingEnabled()
+// Checking if we destroying work registers by breakpoint/step handler.
+
+load(libdir + "wasm.js");
+
+// Running the following code compiled from C snippet:
+//
+// signed func0(signed n) {
+// double a = 1; float b = 0; signed c = 1; long long d = 1;
+// for (;n > 0; n--) {
+// a *= c; b += c; c++; d <<= 1;
+// }
+// return (signed)a + (signed)b + c + (signed)d;
+// }
+//
+var onStepCalled;
+wasmRunWithDebugger(
+ `(module
+ (func $func0 (param $var0 i32) (result i32)
+ (local $var1 i32) (local $var2 i64) (local $var3 f64) (local $var4 f32)
+ (local $var5 f64) (local $var6 i32) (local $var7 i32) (local $var8 i32)
+ i32.const 1
+ set_local $var1
+ i32.const 0
+ set_local $var7
+ i32.const 1
+ set_local $var6
+ i32.const 1
+ set_local $var8
+ block $label0
+ get_local $var0
+ i32.const 1
+ i32.lt_s
+ br_if $label0
+ get_local $var0
+ i32.const 1
+ i32.add
+ set_local $var1
+ f32.const 0
+ set_local $var4
+ i64.const 1
+ set_local $var2
+ f64.const 1
+ set_local $var3
+ i32.const 1
+ set_local $var0
+ f64.const 1
+ set_local $var5
+ block
+ loop $label1
+ get_local $var2
+ i64.const 1
+ i64.shl
+ set_local $var2
+ get_local $var5
+ get_local $var3
+ f64.mul
+ set_local $var5
+ get_local $var4
+ get_local $var0
+ f32.convert_s/i32
+ f32.add
+ set_local $var4
+ get_local $var3
+ f64.const 1
+ f64.add
+ set_local $var3
+ get_local $var0
+ i32.const 1
+ i32.add
+ tee_local $var6
+ set_local $var0
+ get_local $var1
+ i32.const -1
+ i32.add
+ tee_local $var1
+ i32.const 1
+ i32.gt_s
+ br_if $label1
+ end $label1
+ end
+ get_local $var2
+ i32.wrap/i64
+ set_local $var1
+ get_local $var4
+ i32.trunc_s/f32
+ set_local $var7
+ get_local $var5
+ i32.trunc_s/f64
+ set_local $var8
+ end $label0
+ get_local $var7
+ get_local $var8
+ i32.add
+ get_local $var6
+ i32.add
+ get_local $var1
+ i32.add
+ return
+ )
+ (func (export "test") (result i32) (call $func0 (i32.const 4)))
+)`.split('\n').join(' '),
+ undefined,
+ function ({dbg}) {
+ onStepCalled = [];
+ dbg.onEnterFrame = function (frame) {
+ if (frame.type != 'wasmcall') return;
+ frame.onStep = function () {
+ onStepCalled.push(frame.offset);
+ };
+ };
+ },
+ function ({result, error}) {
+ assertEq(result, /* func0(4) = */ 55);
+ assertEq(error, undefined);
+ // The number below reflects amount of wasm operators executed during
+ // the run of the test function, which runs $func0(4). It can change
+ // when the code above and/or meaning of wasm operators will change.
+ assertEq(onStepCalled.length, 166);
+ }
+);
diff --git a/js/src/jit-test/tests/debug/wasm-09.js b/js/src/jit-test/tests/debug/wasm-09.js
new file mode 100644
index 0000000000..6612a6434d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-09.js
@@ -0,0 +1,32 @@
+// |jit-test| test-also=--wasm-compiler=optimizing; skip-if: !wasmDebuggingEnabled()
+// Tests debugEnabled state of wasm when allowUnobservedAsmJS == true.
+
+load(libdir + "asserts.js");
+
+// Checking that there are no offsets are present in a wasm instance script for
+// which debug mode was not enabled.
+function getWasmScriptWithoutAllowUnobservedAsmJS(wast) {
+ var sandbox = newGlobal({newCompartment: true});
+ var dbg = new Debugger();
+ dbg.allowUnobservedAsmJS = true;
+ dbg.addDebuggee(sandbox);
+ sandbox.eval(`
+ var wasm = wasmTextToBinary('${wast}');
+ var m = new WebAssembly.Instance(new WebAssembly.Module(wasm));
+ `);
+ // Attaching after wasm instance is created.
+ var wasmScript = dbg.findScripts().filter(s => s.format == 'wasm')[0];
+ return wasmScript;
+}
+
+var wasmScript1 = getWasmScriptWithoutAllowUnobservedAsmJS('(module (func (nop)))');
+var wasmLines1 = wasmScript1.source.text.split('\n');
+assertEq(wasmScript1.startLine, 1);
+assertEq(wasmScript1.lineCount, 0);
+assertEq(wasmLines1.every((l, n) => wasmScript1.getLineOffsets(n + 1).length == 0), true);
+
+// Checking that we must not resolve any location for any offset in a wasm
+// instance which debug mode was not enabled.
+var wasmScript2 = getWasmScriptWithoutAllowUnobservedAsmJS('(module (func (nop)))');
+for (var i = wasmTextToBinary('(module (func (nop)))').length - 1; i >= 0; i--)
+ assertThrowsInstanceOf(() => wasmScript2.getOffsetLocation(i), Error);
diff --git a/js/src/jit-test/tests/debug/wasm-10.js b/js/src/jit-test/tests/debug/wasm-10.js
new file mode 100644
index 0000000000..6f43385bd3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-10.js
@@ -0,0 +1,77 @@
+// |jit-test| test-also=--wasm-compiler=optimizing; skip-if: !wasmDebuggingEnabled()
+// Tests that wasm module scripts has inspectable locals.
+
+load(libdir + "wasm.js");
+load(libdir + 'eqArrayHelper.js');
+
+function monitorLocalValues(wast, lib, expected) {
+ function setupFrame(frame) {
+ var locals = {};
+ framesLocals.push(locals);
+ frame.environment.names().forEach(n => {
+ locals[n] = [frame.environment.getVariable(n)];
+ });
+ frame.onStep = function () {
+ frame.environment.names().forEach(n => {
+ var prevValues = locals[n];
+ if (!prevValues)
+ locals[n] = prevValues = [void 0];
+ var value = frame.environment.getVariable(n);
+ if (prevValues[prevValues.length - 1] !== value)
+ prevValues.push(value);
+ });
+ }
+ }
+ var framesLocals = [];
+ wasmRunWithDebugger(wast, lib,
+ function ({dbg}) {
+ dbg.onEnterFrame = function(frame) {
+ if (frame.type == "wasmcall")
+ setupFrame(frame);
+ }
+ },
+ function ({error}) {
+ assertEq(error, undefined);
+ }
+ );
+ assertEq(framesLocals.length, expected.length);
+ for (var i = 0; i < framesLocals.length; i++) {
+ var frameLocals = framesLocals[i];
+ var expectedLocals = expected[i];
+ var localsNames = Object.keys(frameLocals);
+ assertEq(localsNames.length, Object.keys(expectedLocals).length);
+ localsNames.forEach(n => {
+ assertEqArray(frameLocals[n], expectedLocals[n]);
+ });
+ }
+}
+
+monitorLocalValues(
+ '(module (func (nop) (nop)) (export "test" (func 0)))',
+ undefined,
+ [{}]
+);
+monitorLocalValues(
+ '(module (func (export "test") (local i32) (i32.const 1) (set_local 0)))',
+ undefined,
+ [{var0: [0, 1]}]
+);
+monitorLocalValues(
+ '(module (func (export "test") (local f32) (f32.const 1.5) (set_local 0)))',
+ undefined,
+ [{var0: [0, 1.5]}]
+);
+monitorLocalValues(
+ '(module (func (export "test") (local f64) (f64.const 42.25) (set_local 0)))',
+ undefined,
+ [{var0: [0, 42.25]}]
+);
+monitorLocalValues(
+ `(module
+ (func (param i32) (result i32) (get_local 0) (i32.const 2) (i32.add))
+ (func (param i32) (local i32) (get_local 0) (call 0) (set_local 1))
+ (func (export "test") (i32.const 1) (call 1))
+)`.replace(/\n/g, " "),
+ undefined,
+ [{}, {var0: [1], var1: [0, 3]}, {var0: [1]}]
+);
diff --git a/js/src/jit-test/tests/debug/wasm-11.js b/js/src/jit-test/tests/debug/wasm-11.js
new file mode 100644
index 0000000000..1e776c2008
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-11.js
@@ -0,0 +1,33 @@
+// |jit-test| test-also=--wasm-compiler=optimizing; skip-if: !wasmDebuggingEnabled()
+
+// Test single-stepping where the TLS register can be evicted by a non-trivial
+// function body.
+
+var g = newGlobal({newCompartment: true});
+g.parent = this;
+g.eval(`
+ var dbg = new Debugger(parent);
+`);
+
+var i = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(`
+ (module
+ (func (export "f2")
+ i64.const 0
+ i64.const 0
+ i32.const 0
+ select
+ drop
+ )
+ )
+`)));
+
+g.eval(`
+ var calledOnStep = 0;
+ dbg.onEnterFrame = frame => {
+ if (frame.type === "wasmcall")
+ frame.onStep = () => { calledOnStep++ }
+ };
+`);
+
+i.exports.f2();
+assertEq(g.calledOnStep >= 5, true);
diff --git a/js/src/jit-test/tests/debug/wasm-12.js b/js/src/jit-test/tests/debug/wasm-12.js
new file mode 100644
index 0000000000..6c645f3f7f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-12.js
@@ -0,0 +1,25 @@
+// |jit-test| test-also=--wasm-compiler=optimizing; skip-if: !wasmDebuggingEnabled()
+
+// Tests that wasm module scripts have special URLs.
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+g.eval(`
+function initWasm(s) { return new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(s))); }
+o1 = initWasm('(module (func) (export "" (func 0)))');
+o2 = initWasm('(module (func) (func) (export "" (func 1)))');
+`);
+
+function isWasm(script) { return script.format === "wasm"; }
+
+function isValidWasmURL(url) {
+ // The URLs will have the following format:
+ // wasm: [<uri-encoded-filename-of-host> ":"] <64-bit-hash>
+ return /^wasm:(?:[^:]*:)*?[0-9a-f]{16}$/.test(url);
+}
+
+var foundScripts = dbg.findScripts().filter(isWasm);
+assertEq(foundScripts.length, 2);
+assertEq(isValidWasmURL(foundScripts[0].source.url), true);
+assertEq(isValidWasmURL(foundScripts[1].source.url), true);
+assertEq(foundScripts[0].source.url != foundScripts[1].source.url, true);
diff --git a/js/src/jit-test/tests/debug/wasm-13.js b/js/src/jit-test/tests/debug/wasm-13.js
new file mode 100644
index 0000000000..9c16fa8058
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-13.js
@@ -0,0 +1,106 @@
+// |jit-test| skip-if: !wasmDebuggingEnabled()
+// Tests that wasm module scripts has inspectable globals and memory.
+
+load(libdir + "wasm.js");
+load(libdir + 'eqArrayHelper.js');
+
+function monitorGlobalValues(wast, lib, expected) {
+ function setupFrame(frame) {
+ var globals = {};
+ framesGlobals.push(globals);
+ // Environment with globals follow function scope enviroment
+ var globalEnv = frame.environment.parent;
+ globalEnv.names().forEach(n => {
+ globals[n] = [globalEnv.getVariable(n)];
+ });
+ frame.onStep = function () {
+ var globalEnv = frame.environment.parent;
+ globalEnv.names().forEach(n => {
+ var prevValues = globals[n];
+ if (!prevValues)
+ globals[n] = prevValues = [void 0];
+ var value = globalEnv.getVariable(n);
+ if (prevValues[prevValues.length - 1] !== value)
+ prevValues.push(value);
+ });
+ }
+ }
+ var framesGlobals = [];
+ wasmRunWithDebugger(wast, lib,
+ function ({dbg}) {
+ dbg.onEnterFrame = function(frame) {
+ if (frame.type == "wasmcall")
+ setupFrame(frame);
+ }
+ },
+ function ({error}) {
+ assertEq(error, undefined);
+ }
+ );
+ assertEq(framesGlobals.length, expected.length);
+ for (var i = 0; i < framesGlobals.length; i++) {
+ var frameGlobals = framesGlobals[i];
+ var expectedGlobals = expected[i];
+ var globalsNames = Object.keys(frameGlobals);
+ assertEq(globalsNames.length, Object.keys(expectedGlobals).length);
+ globalsNames.forEach(n => {
+ if (typeof expectedGlobals[n][0] === "function") {
+ // expectedGlobals are assert functions
+ expectedGlobals[n].forEach((assertFn, i) => {
+ assertFn(frameGlobals[n][i]);
+ });
+ return;
+ }
+ assertEqArray(frameGlobals[n], expectedGlobals[n]);
+ });
+ }
+}
+
+monitorGlobalValues(
+ '(module (func (export "test") (nop)))',
+ undefined,
+ [{}]
+);
+monitorGlobalValues(
+ '(module (memory (export "memory") 1 1) (func (export "test") (nop) (nop)))',
+ undefined,
+ [{
+ memory0: [
+ function (actual) {
+ var bufferProp = actual.proto.getOwnPropertyDescriptor("buffer");
+ assertEq(!!bufferProp, true, "wasm memory buffer property");
+ var buffer = bufferProp.get.call(actual).return;
+ var bufferLengthProp = buffer.proto.getOwnPropertyDescriptor("byteLength");
+ var bufferLength = bufferLengthProp.get.call(buffer).return;
+ assertEq(bufferLength, 65536, "wasm memory size");
+ }
+ ]
+ }]
+);
+monitorGlobalValues(
+ `(module\
+ (global i32 (i32.const 1))(global i64 (i64.const 2))(global f32 (f32.const 3.5))(global f64 (f64.const 42.25))\
+ ${wasmReftypesEnabled() ? '(global externref (ref.null extern))' : ''}\
+ (func (export "test") (nop)))`,
+ undefined,
+ [(function () {
+ let x = {global0: [1], global1: [2], global2: [3.5], global3: [42.25]};
+ if (wasmReftypesEnabled()) x.global4 = [ function (x) { assertEq(x.optimizedOut, true); } ];
+ return x;
+ })()]
+);
+monitorGlobalValues(
+ `(module (global (mut i32) (i32.const 1))(global (mut i64) (i64.const 2))\
+ (global (mut f32) (f32.const 3.5))(global (mut f64) (f64.const 42.25))\
+ ${wasmReftypesEnabled() ? '(global (mut externref) (ref.null extern))' : ''}\
+ (func (export "test")\
+ (i32.const 2)(global.set 0)(i64.const 1)(global.set 1)\
+ (f32.const 42.25)(global.set 2)(f64.const 3.5)(global.set 3)\
+ ${wasmReftypesEnabled() ? '(ref.null extern)(global.set 4)' : ''}))`,
+ undefined,
+ [(function () {
+ let x = {global0: [1, 2], global1: [2, 1], global2: [3.5, 42.25], global3: [42.25, 3.5]};
+ if (wasmReftypesEnabled()) x.global4 = [ function (x) { assertEq(x.optimizedOut, true); } ];
+ return x;
+ })()]
+)
diff --git a/js/src/jit-test/tests/debug/wasm-binary-sources.js b/js/src/jit-test/tests/debug/wasm-binary-sources.js
new file mode 100644
index 0000000000..4ed5b9fb50
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-binary-sources.js
@@ -0,0 +1,32 @@
+// |jit-test| skip-if: !wasmDebuggingEnabled()
+
+// Tests that wasm module scripts have access to binary sources.
+
+load(libdir + "asserts.js");
+load(libdir + "array-compare.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+var s;
+dbg.onNewScript = (script) => {
+ s = script;
+}
+
+g.eval(`o = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (func) (export "" (func 0)))')));`);
+assertEq(s.format, "wasm");
+
+var source = s.source;
+
+// The text is never generated with the native Debugger API.
+assertEq(source.text.includes('module'), false);
+
+g.eval(`o = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (func) (export "" (func 0)))')));`);
+assertEq(s.format, "wasm");
+
+var source2 = s.source;
+
+// The text is predefined if wasm binary sources are enabled.
+assertEq(source2.text, '[debugger missing wasm binary-to-text conversion]');
+// The binary contains Uint8Array which is equal to wasm bytecode;
+arraysEqual(source2.binary, wasmTextToBinary('(module (func) (export "" (func 0)))'));
diff --git a/js/src/jit-test/tests/debug/wasm-breakpoint.js b/js/src/jit-test/tests/debug/wasm-breakpoint.js
new file mode 100644
index 0000000000..803ad4dde7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-breakpoint.js
@@ -0,0 +1,194 @@
+// |jit-test| test-also=--wasm-compiler=optimizing; skip-if: !wasmDebuggingEnabled()
+// Tests that wasm module scripts handles basic breakpoint operations.
+
+load(libdir + "wasm.js");
+
+function runTest(wast, initFunc, doneFunc) {
+ let g = newGlobal({newCompartment: true});
+ let dbg = new Debugger(g);
+
+ g.eval(`
+var b = wasmTextToBinary('${wast}');
+var m = new WebAssembly.Instance(new WebAssembly.Module(b));
+var breakpoints = wasmCodeOffsets(b);
+`);
+
+ var { breakpoints } = g;
+
+ var wasmScript = dbg.findScripts().filter(s => s.format == 'wasm')[0];
+
+ initFunc({
+ dbg,
+ wasmScript,
+ g,
+ breakpoints
+ });
+
+ let result, error;
+ try {
+ result = g.eval("m.exports.test()");
+ } catch (ex) {
+ error = ex;
+ }
+
+ doneFunc({
+ dbg,
+ result,
+ error,
+ wasmScript,
+ g
+ });
+}
+
+
+var onBreakpointCalled;
+
+// Checking if we can stop at specified breakpoint.
+runTest(
+ '(module (func (nop) (nop)) (export "test" (func 0)))',
+ function ({wasmScript, breakpoints}) {
+ print(`${JSON.stringify(breakpoints)}`);
+ assertEq(breakpoints.length, 3);
+ assertEq(breakpoints[0] > 0, true);
+ // Checking if breakpoints offsets are in ascending order.
+ assertEq(breakpoints[0] < breakpoints[1], true);
+ assertEq(breakpoints[1] < breakpoints[2], true);
+ onBreakpointCalled = 0;
+ breakpoints.forEach(function (offset) {
+ wasmScript.setBreakpoint(offset, {
+ hit: (frame) => {
+ assertEq(frame.offset, offset);
+ onBreakpointCalled++;
+ }
+ });
+ });
+ },
+ function ({dbg, error}) {
+ assertEq(error, undefined);
+ assertEq(onBreakpointCalled, 3);
+ }
+);
+
+// Checking if we can remove breakpoint one by one.
+runTest(
+ '(module (func (nop) (nop)) (export "test" (func 0)))',
+ function ({wasmScript, breakpoints}) {
+ onBreakpointCalled = 0;
+ var handlers = [];
+ breakpoints.forEach(function (offset, i) {
+ wasmScript.setBreakpoint(offset, handlers[i] = {
+ hit: (frame) => {
+ assertEq(frame.offset, breakpoints[0]);
+ onBreakpointCalled++;
+ // Removing all handlers.
+ handlers.forEach(h => wasmScript.clearBreakpoint(h));
+ }
+ });
+ });
+ },
+ function ({error}) {
+ assertEq(error, undefined);
+ assertEq(onBreakpointCalled, 1);
+ }
+);
+
+// Checking if we can remove breakpoint one by one from a breakpoint handler.
+runTest(
+ '(module (func (nop) (nop)) (export "test" (func 0)))',
+ function ({wasmScript, breakpoints}) {
+ onBreakpointCalled = 0;
+ var handlers = [];
+ breakpoints.forEach(function (offset, i) {
+ wasmScript.setBreakpoint(offset, handlers[i] = {
+ hit: (frame) => {
+ assertEq(frame.offset, breakpoints[0]);
+ onBreakpointCalled++;
+ // Removing all handlers.
+ handlers.forEach(h => wasmScript.clearBreakpoint(h));
+ }
+ });
+ });
+ },
+ function ({error}) {
+ assertEq(error, undefined);
+ assertEq(onBreakpointCalled, 1);
+ }
+);
+
+// Checking if we can remove breakpoint one by one from onEnterFrame,
+// but onStep will still work.
+var onStepCalled;
+runTest(
+ '(module (func (nop) (nop)) (export "test" (func 0)))',
+ function ({dbg, wasmScript, breakpoints}) {
+ onBreakpointCalled = 0;
+ onStepCalled = [];
+ var handlers = [];
+ breakpoints.forEach(function (offset, i) {
+ wasmScript.setBreakpoint(offset, handlers[i] = {
+ hit: (frame) => {
+ assertEq(false, true);
+ onBreakpointCalled++;
+ }
+ });
+ });
+ dbg.onEnterFrame = function (frame) {
+ if (frame.type != 'wasmcall') return;
+ frame.onStep = function () {
+ onStepCalled.push(frame.offset);
+ };
+
+ // Removing all handlers.
+ handlers.forEach(h => wasmScript.clearBreakpoint(h));
+ };
+ },
+ function ({error}) {
+ assertEq(error, undefined);
+ assertEq(onBreakpointCalled, 0);
+ assertEq(onStepCalled.length, 3);
+ }
+);
+
+// Checking if we can remove all breakpoints.
+runTest(
+ '(module (func (nop) (nop)) (export "test" (func 0)))',
+ function ({wasmScript, breakpoints}) {
+ onBreakpointCalled = 0;
+ breakpoints.forEach(function (offset, i) {
+ wasmScript.setBreakpoint(offset, {
+ hit: (frame) => {
+ assertEq(frame.offset, breakpoints[0]);
+ onBreakpointCalled++;
+ // Removing all handlers.
+ wasmScript.clearAllBreakpoints();
+ }
+ });
+ });
+ },
+ function ({error}) {
+ assertEq(error, undefined);
+ assertEq(onBreakpointCalled, 1);
+ }
+);
+
+// Checking if breakpoints are removed after debugger has been detached.
+runTest(
+ '(module (func (nop) (nop)) (export "test" (func 0)))',
+ function ({dbg, wasmScript, g, breakpoints}) {
+ onBreakpointCalled = 0;
+ breakpoints.forEach(function (offset, i) {
+ wasmScript.setBreakpoint(offset, {
+ hit: (frame) => {
+ onBreakpointCalled++;
+ }
+ });
+ });
+ dbg.onEnterFrame = function (frame) {
+ dbg.removeDebuggee(g);
+ };
+ },
+ function ({error}) {
+ assertEq(error, undefined);
+ assertEq(onBreakpointCalled, 0);
+ }
+);
diff --git a/js/src/jit-test/tests/debug/wasm-get-return.js b/js/src/jit-test/tests/debug/wasm-get-return.js
new file mode 100644
index 0000000000..233d8cf5da
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-get-return.js
@@ -0,0 +1,62 @@
+// |jit-test| test-also=--wasm-compiler=optimizing; skip-if: !wasmDebuggingEnabled()
+// Tests that wasm frame opPop event can access function resumption value.
+
+load(libdir + "wasm.js");
+load(libdir + 'eqArrayHelper.js');
+
+function monitorFrameOnPopReturns(wast, expected) {
+ var values = [];
+ wasmRunWithDebugger(
+ wast,
+ undefined,
+ function ({dbg}) {
+ dbg.onEnterFrame = function (frame) {
+ if (frame.type != 'wasmcall') return;
+ frame.onPop = function (value) {
+ values.push(value.return);
+ };
+ };
+ },
+ function ({error}) {
+ assertEq(error, undefined);
+ }
+ );
+ assertEqArray(values, expected);
+}
+
+monitorFrameOnPopReturns(
+ `(module (func (export "test")))`,
+ [undefined]);
+monitorFrameOnPopReturns(
+ `(module (func (export "test") (result i32) (i32.const 42)))`,
+ [42]);
+monitorFrameOnPopReturns(
+ `(module (func (export "test") (result f32) (f32.const 0.5)))`,
+ [0.5]);
+monitorFrameOnPopReturns(
+ `(module (func (export "test") (result f64) (f64.const -42.75)))`,
+ [-42.75]);
+monitorFrameOnPopReturns(
+ `(module (func (result i64) (i64.const 2)) (func (export "test") (call 0) (drop)))`,
+ [2, undefined]);
+
+// Checking if throwing frame has right resumption value.
+var throwCount = 0;
+wasmRunWithDebugger(
+ '(module (func (unreachable)) (func (export "test") (result i32) (call 0) (i32.const 1)))',
+ undefined,
+ function ({dbg, g}) {
+ dbg.onEnterFrame = function (frame) {
+ if (frame.type != 'wasmcall') return;
+ frame.onPop = function (value) {
+ if ('throw' in value)
+ throwCount++;
+ };
+ };
+ },
+ function ({error}) {
+ assertEq(error != undefined, true);
+ assertEq(throwCount, 2);
+ }
+);
+
diff --git a/js/src/jit-test/tests/debug/wasm-getAllColumnOffsets.js b/js/src/jit-test/tests/debug/wasm-getAllColumnOffsets.js
new file mode 100644
index 0000000000..58e85f1d84
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-getAllColumnOffsets.js
@@ -0,0 +1,41 @@
+// |jit-test| test-also=--wasm-compiler=optimizing; skip-if: !wasmDebuggingEnabled()
+
+// Tests that wasm module scripts have column and line to bytecode offset
+// information when source text is generated.
+
+load(libdir + "asserts.js");
+
+// Checking if experimental format generates internal source map to binary file
+// by querying debugger scripts getAllColumnOffsets.
+function getAllOffsets(wast) {
+ var sandbox = newGlobal({newCompartment: true});
+ var dbg = new Debugger();
+ dbg.addDebuggee(sandbox);
+ dbg.allowWasmBinarySource = true;
+ sandbox.eval(`
+ var wasm = wasmTextToBinary('${wast}');
+ var m = new WebAssembly.Instance(new WebAssembly.Module(wasm));
+ `);
+ var wasmScript = dbg.findScripts().filter(s => s.format == 'wasm')[0];
+ return wasmScript.getAllColumnOffsets();
+}
+
+var offsets1 = getAllOffsets('(module \
+ (func (nop)) \
+ (func (drop (f32.sqrt (f32.add (f32.const 1.0) (f32.const 2.0))))) \
+)');
+
+// There shall be total 8 lines with single and unique offset per line.
+var usedOffsets = Object.create(null),
+ usedLines = Object.create(null);
+assertEq(offsets1.length, 8);
+
+offsets1.forEach(({offset, lineNumber, columnNumber}) => {
+ assertEq(offset > 0, true);
+ assertEq(lineNumber > 0, true);
+ assertEq(columnNumber > 0, true);
+ usedOffsets[offset] = true;
+ usedLines[lineNumber] = true;
+});
+assertEq(Object.keys(usedOffsets).length, 8);
+assertEq(Object.keys(usedLines).length, 8);
diff --git a/js/src/jit-test/tests/debug/wasm-jseval.js b/js/src/jit-test/tests/debug/wasm-jseval.js
new file mode 100644
index 0000000000..ef35c3c0b7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-jseval.js
@@ -0,0 +1,45 @@
+// |jit-test| skip-if: !wasmDebuggingEnabled()
+// Tests that JS can be evaluated on wasm module scripts frames.
+
+load(libdir + "wasm.js");
+
+wasmRunWithDebugger(
+ '(module (memory 1 1)\
+ (global (mut f64) (f64.const 0.5))\
+ (global f32 (f32.const 3.5))\
+ (func (param i32) (local f64) (f64.const 1.0) (tee_local 1) (set_global 0) (nop))\
+ (export "test" (func 0))\
+ (data (i32.const 0) "Abc\\x2A"))',
+ undefined,
+ function ({dbg}) {
+ dbg.onEnterFrame = function (frame) {
+ if (frame.type != 'wasmcall') return;
+
+ var memoryContent = frame.eval('new DataView(memory0.buffer).getUint8(3)').return;
+ assertEq(memoryContent, 42, 'valid memory content is expected (0x2A)');
+
+ var global1AndParamSum = frame.eval('global1 + var0').return;
+ assertEq(global1AndParamSum, 3.5);
+
+ var stepNumber = 0;
+ frame.onStep = function () {
+ switch (stepNumber) {
+ case 1: // after i64.const 1.0
+ assertEq(frame.eval('global0').return, 0.5);
+ assertEq(frame.eval('var1').return, 0.0);
+ break;
+ case 2: // after tee_local $var1
+ assertEq(frame.eval('var1').return, 1.0);
+ break;
+ case 3: // after set_global $global0
+ assertEq(frame.eval('global0').return, 1.0);
+ break;
+ }
+ stepNumber++;
+ };
+ };
+ },
+ function ({error}) {
+ assertEq(error, undefined);
+ }
+);
diff --git a/js/src/jit-test/tests/debug/wasm-onExceptionUnwind-gc.js b/js/src/jit-test/tests/debug/wasm-onExceptionUnwind-gc.js
new file mode 100644
index 0000000000..5f4b4942af
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-onExceptionUnwind-gc.js
@@ -0,0 +1,48 @@
+// |jit-test| skip-if: !wasmDebuggingEnabled()
+
+var sandbox = newGlobal({newCompartment: true});
+var dbg = new Debugger(sandbox);
+var counter = 0;
+dbg.onExceptionUnwind = (frame, value) => {
+ if (frame.type !== "wasmcall")
+ return;
+ if (++counter != 2)
+ return;
+ gc();
+};
+
+sandbox.innerCode = wasmTextToBinary(`(module
+ (import "imports" "tbl" (table 1 anyfunc))
+ (import "imports" "setNull" (func $setNull))
+ (func $trap
+ call $setNull
+ unreachable
+ )
+ (elem (i32.const 0) $trap)
+)`);
+sandbox.outerCode = wasmTextToBinary(`(module
+ (import "imports" "tbl" (table 1 anyfunc))
+ (type $v2v (func))
+ (func (export "run")
+ i32.const 0
+ call_indirect (type $v2v)
+ )
+)`);
+
+sandbox.eval(`
+(function() {
+
+var tbl = new WebAssembly.Table({initial:1, element:"anyfunc"});
+function setNull() { tbl.set(0, null) }
+new WebAssembly.Instance(new WebAssembly.Module(innerCode), {imports:{tbl,setNull}});
+var outer = new WebAssembly.Instance(new WebAssembly.Module(outerCode), {imports:{tbl}});
+var caught;
+try {
+ outer.exports.run();
+} catch (e) {
+ caught = e;
+}
+assertEq(caught instanceof WebAssembly.RuntimeError, true);
+
+})();
+`);
diff --git a/js/src/jit-test/tests/debug/wasm-responseurls.js b/js/src/jit-test/tests/debug/wasm-responseurls.js
new file mode 100644
index 0000000000..bc00b8e605
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-responseurls.js
@@ -0,0 +1,40 @@
+// |jit-test| test-also=--wasm-compiler=optimizing; skip-if: !wasmDebuggingEnabled()
+// Tests that wasm module can accept URL and sourceMapURL from response
+// when instantiateStreaming is used.
+
+ignoreUnhandledRejections();
+
+try {
+ WebAssembly.compileStreaming();
+ // Avoid mixing the test's jobs with the debuggee's, so that
+ // automated checks can make sure AutoSaveJobQueue only
+ // suspends debuggee work.
+ drainJobQueue();
+} catch (err) {
+ assertEq(String(err).indexOf("not supported with --no-threads") !== -1, true);
+ quit();
+}
+
+load(libdir + "asserts.js");
+
+var g = newGlobal({newCompartment: true});
+
+var source = new g.Uint8Array(wasmTextToBinary('(module (func unreachable))'));
+source.url = "http://example.org/test.wasm";
+source.sourceMappingURL = "http://example.org/test.wasm.map";
+g.source = source;
+
+var gotUrl, gotSourceMapURL;
+var dbg = new Debugger(g);
+dbg.allowWasmBinarySource = true;
+dbg.onNewScript = function (s, g) {
+ gotUrl = s.source.url;
+ gotSourceMapURL = s.source.sourceMapURL;
+};
+
+g.eval('WebAssembly.instantiateStreaming(source);');
+
+drainJobQueue();
+
+assertEq(gotUrl, "http://example.org/test.wasm");
+assertEq(gotSourceMapURL, "http://example.org/test.wasm.map");
diff --git a/js/src/jit-test/tests/debug/wasm-sourceMappingURL.js b/js/src/jit-test/tests/debug/wasm-sourceMappingURL.js
new file mode 100644
index 0000000000..7f7f245faf
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-sourceMappingURL.js
@@ -0,0 +1,72 @@
+// |jit-test| test-also=--wasm-compiler=optimizing; skip-if: !wasmDebuggingEnabled()
+
+// Tests that wasm module sourceMappingURL section is parsed.
+
+load(libdir + "asserts.js");
+load(libdir + "wasm-binary.js");
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+
+var gotScript;
+dbg.allowWasmBinarySource = true;
+dbg.onNewScript = (script) => {
+ gotScript = script;
+}
+
+function toU8(array) {
+ for (let b of array)
+ assertEq(b < 256, true);
+ return Uint8Array.from(array);
+}
+
+function varU32(u32) {
+ assertEq(u32 >= 0, true);
+ assertEq(u32 < Math.pow(2,32), true);
+ var bytes = [];
+ do {
+ var byte = u32 & 0x7f;
+ u32 >>>= 7;
+ if (u32 != 0)
+ byte |= 0x80;
+ bytes.push(byte);
+ } while (u32 != 0);
+ return bytes;
+}
+
+function string(name) {
+ var nameBytes = name.split('').map(c => {
+ var code = c.charCodeAt(0);
+ assertEq(code < 128, true); // TODO
+ return code;
+ });
+ return varU32(nameBytes.length).concat(nameBytes);
+}
+
+function appendSourceMappingURL(wasmBytes, url) {
+ if (!url)
+ return wasmBytes;
+ var payload = [...string('sourceMappingURL'), ...string(url)];
+ return Uint8Array.from([...wasmBytes, userDefinedId, payload.length, ...payload]);
+}
+g.toWasm = (wast, url) => appendSourceMappingURL(wasmTextToBinary(wast), url);
+
+// The sourceMappingURL section is not present
+g.eval(`o = new WebAssembly.Instance(new WebAssembly.Module(toWasm('(module (func) (export "" (func 0)))')));`);
+assertEq(gotScript.format, "wasm");
+assertEq(gotScript.source.sourceMapURL, null);
+
+// The sourceMappingURL section is present
+g.eval(`o = new WebAssembly.Instance(new WebAssembly.Module(toWasm('(module (func) (export "a" (func 0)))', 'http://example.org/test')));`);
+assertEq(gotScript.format, "wasm");
+assertEq(gotScript.source.sourceMapURL, 'http://example.org/test');
+
+// The sourceMapURL is read-only for wasm
+assertThrowsInstanceOf(() => gotScript.source.sourceMapURL = 'foo', Error);
+
+// The sourceMappingURL section is present, and is still available when wasm
+// binary source is disabled.
+dbg.allowWasmBinarySource = false;
+g.eval(`o = new WebAssembly.Instance(new WebAssembly.Module(toWasm('(module (func) (export "a" (func 0)))', 'http://example.org/test2')));`);
+assertEq(gotScript.format, "wasm");
+assertEq(gotScript.source.sourceMapURL, 'http://example.org/test2');
diff --git a/js/src/jit-test/tests/debug/wasm-step.js b/js/src/jit-test/tests/debug/wasm-step.js
new file mode 100644
index 0000000000..a7509facc7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-step.js
@@ -0,0 +1,65 @@
+// |jit-test| test-also=--wasm-compiler=optimizing; skip-if: !wasmDebuggingEnabled()
+
+// Tests that wasm module scripts raises onEnterFrame and onLeaveFrame events.
+
+load(libdir + "wasm.js");
+
+// Checking if we stop at every wasm instruction during step.
+var onEnterFrameCalled, onLeaveFrameCalled, onStepCalled;
+wasmRunWithDebugger(
+ '(module (func (nop) (nop)) (export "test" (func 0)))',
+ undefined,
+ function ({dbg}) {
+ onEnterFrameCalled = 0;
+ onLeaveFrameCalled = 0;
+ onStepCalled = [];
+ dbg.onEnterFrame = function (frame) {
+ if (frame.type != 'wasmcall') return;
+ onEnterFrameCalled++;
+ frame.onStep = function () {
+ onStepCalled.push(frame.offset);
+ };
+ frame.onPop = function () {
+ onLeaveFrameCalled++;
+ };
+ };
+ },
+ function ({error}) {
+ assertEq(error, undefined);
+ assertEq(onEnterFrameCalled, 1);
+ assertEq(onLeaveFrameCalled, 1);
+ assertEq(onStepCalled.length, 3);
+ assertEq(onStepCalled[0] > 0, true);
+ // The onStepCalled offsets are in ascending order.
+ assertEq(onStepCalled[0] < onStepCalled[1], true);
+ assertEq(onStepCalled[1] < onStepCalled[2], true);
+ }
+);
+
+// Checking if step mode was disabled after debugger has been detached.
+wasmRunWithDebugger(
+ '(module (func (nop) (nop)) (export "test" (func 0)))',
+ undefined,
+ function ({dbg, g}) {
+ onEnterFrameCalled = 0;
+ onLeaveFrameCalled = 0;
+ onStepCalled = [];
+ dbg.onEnterFrame = function (frame) {
+ if (frame.type != 'wasmcall') return;
+ onEnterFrameCalled++;
+ frame.onStep = function () {
+ onStepCalled.push(frame.offset);
+ };
+ frame.onPop = function () {
+ onLeaveFrameCalled++;
+ };
+ dbg.removeDebuggee(g);
+ };
+ },
+ function ({error}) {
+ assertEq(error, undefined);
+ assertEq(onEnterFrameCalled, 1);
+ assertEq(onLeaveFrameCalled, 0);
+ assertEq(onStepCalled.length, 0);
+ }
+);