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-allowUnobservedAsmJS-01.js34
-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-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.js117
-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.js31
-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-enabled-01.js18
-rw-r--r--js/src/jit-test/tests/debug/Debugger-enabled-02.js17
-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.js27
-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.js128
-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-21.js21
-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.js19
-rw-r--r--js/src/jit-test/tests/debug/Debugger-findScripts-24.js35
-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-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-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.js24
-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.js20
-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.js29
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-13.js17
-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.js24
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewPromise-01.js19
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewPromise-02.js26
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewPromise-03.js43
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewPromise-04.js17
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewPromise-05.js27
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewPromise-06.js37
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onNewPromise-07.js15
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onPromiseSettled-01.js27
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onPromiseSettled-02.js26
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onPromiseSettled-03.js43
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onPromiseSettled-04.js17
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onPromiseSettled-05.js27
-rw-r--r--js/src/jit-test/tests/debug/Debugger-onPromiseSettled-06.js37
-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-callee-01.js48
-rw-r--r--js/src/jit-test/tests/debug/Environment-callee-02.js25
-rw-r--r--js/src/jit-test/tests/debug/Environment-callee-03.js31
-rw-r--r--js/src/jit-test/tests/debug/Environment-callee-04.js22
-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.js47
-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-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.js8
-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-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.js22
-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.js86
-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.js23
-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-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.js20
-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.js46
-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.js24
-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-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-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-live-01.js41
-rw-r--r--js/src/jit-test/tests/debug/Frame-live-02.js32
-rw-r--r--js/src/jit-test/tests/debug/Frame-live-03.js27
-rw-r--r--js/src/jit-test/tests/debug/Frame-live-04.js27
-rw-r--r--js/src/jit-test/tests/debug/Frame-live-05.js29
-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-older-01.js19
-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.js19
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-07.js30
-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.js32
-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-disabled.js44
-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-generators-01.js21
-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-multiple-01.js127
-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-03.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-star-generators-01.js20
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-star-generators-02.js21
-rw-r--r--js/src/jit-test/tests/debug/Frame-onPop-star-generators-03.js42
-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-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.js66
-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.js129
-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-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-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-environment-nondebuggee.js32
-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/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-12.js17
-rw-r--r--js/src/jit-test/tests/debug/Memory-drainAllocationsLog-13.js18
-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.js33
-rw-r--r--js/src/jit-test/tests/debug/Memory-drainAllocationsLog-16.js47
-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.js27
-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.js26
-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.js117
-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.js45
-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.js105
-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-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-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-displayName-01.js17
-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.js20
-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.js22
-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-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-global-01.js26
-rw-r--r--js/src/jit-test/tests/debug/Object-global-02.js25
-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.js22
-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.js13
-rw-r--r--js/src/jit-test/tests/debug/Object-name-02.js16
-rw-r--r--js/src/jit-test/tests/debug/Object-parameterNames.js33
-rw-r--r--js/src/jit-test/tests/debug/Object-preventExtensions-01.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.js61
-rw-r--r--js/src/jit-test/tests/debug/Object-script.js13
-rw-r--r--js/src/jit-test/tests/debug/Object-seal-01.js63
-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.js23
-rw-r--r--js/src/jit-test/tests/debug/Object-unwrap-03.js15
-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.js19
-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-01.js19
-rw-r--r--js/src/jit-test/tests/debug/Script-getAllColumnOffsets-02.js21
-rw-r--r--js/src/jit-test/tests/debug/Script-getAllColumnOffsets-03.js20
-rw-r--r--js/src/jit-test/tests/debug/Script-getAllColumnOffsets-04.js20
-rw-r--r--js/src/jit-test/tests/debug/Script-getAllColumnOffsets-05.js20
-rw-r--r--js/src/jit-test/tests/debug/Script-getAllColumnOffsets-06.js28
-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.js42
-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-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-getOffsetsCoverage-01.js475
-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-global-01.js20
-rw-r--r--js/src/jit-test/tests/debug/Script-global-02.js40
-rw-r--r--js/src/jit-test/tests/debug/Script-isInCatchScope.js68
-rw-r--r--js/src/jit-test/tests/debug/Script-lineCount.js23
-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-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.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.js27
-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-introductionScript-04.js8
-rw-r--r--js/src/jit-test/tests/debug/Source-introductionType-data1
-rw-r--r--js/src/jit-test/tests/debug/Source-introductionType.js120
-rw-r--r--js/src/jit-test/tests/debug/Source-invisible.js12
-rw-r--r--js/src/jit-test/tests/debug/Source-sourceMapURL-deprecated.js82
-rw-r--r--js/src/jit-test/tests/debug/Source-sourceMapURL.js82
-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.js19
-rw-r--r--js/src/jit-test/tests/debug/Source-text-lazy.js39
-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.js40
-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-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/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.js6
-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.js5
-rw-r--r--js/src/jit-test/tests/debug/bug-1238610.js27
-rw-r--r--js/src/jit-test/tests/debug/bug-1240090.js24
-rw-r--r--js/src/jit-test/tests/debug/bug-1248162.js14
-rw-r--r--js/src/jit-test/tests/debug/bug-1260725.js12
-rw-r--r--js/src/jit-test/tests/debug/bug-1260728.js12
-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.js11
-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.js15
-rw-r--r--js/src/jit-test/tests/debug/bug1108556.js10
-rw-r--r--js/src/jit-test/tests/debug/bug1109328.js7
-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.js16
-rw-r--r--js/src/jit-test/tests/debug/bug1130756.js28
-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.js11
-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.js15
-rw-r--r--js/src/jit-test/tests/debug/bug1219905.js14
-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.js12
-rw-r--r--js/src/jit-test/tests/debug/bug1240803.js24
-rw-r--r--js/src/jit-test/tests/debug/bug1242111.js11
-rw-r--r--js/src/jit-test/tests/debug/bug1242798.js14
-rw-r--r--js/src/jit-test/tests/debug/bug1245862.js25
-rw-r--r--js/src/jit-test/tests/debug/bug1246605.js13
-rw-r--r--js/src/jit-test/tests/debug/bug1251919.js13
-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.js17
-rw-r--r--js/src/jit-test/tests/debug/bug1254190.js15
-rw-r--r--js/src/jit-test/tests/debug/bug1254578.js23
-rw-r--r--js/src/jit-test/tests/debug/bug1257045.js11
-rw-r--r--js/src/jit-test/tests/debug/bug1263899.js29
-rw-r--r--js/src/jit-test/tests/debug/bug1264961.js28
-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.js34
-rw-r--r--js/src/jit-test/tests/debug/bug1302432.js10
-rw-r--r--js/src/jit-test/tests/debug/bug1308578.js10
-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/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/dispatch-02.js21
-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-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/inspect-wrapped-promise.js88
-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.js43
-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/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-04.js50
-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/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-11.js29
-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-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-async.js130
-rw-r--r--js/src/jit-test/tests/debug/onExceptionUnwind-resumption-generator.js117
-rw-r--r--js/src/jit-test/tests/debug/onNewScript-01.js45
-rw-r--r--js/src/jit-test/tests/debug/onNewScript-02.js65
-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.js18
-rw-r--r--js/src/jit-test/tests/debug/onNewScript-off-main-thread-02.js13
-rw-r--r--js/src/jit-test/tests/debug/optimized-out-01.js44
-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/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/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-04.js19
-rw-r--r--js/src/jit-test/tests/debug/resumption-05.js35
-rw-r--r--js/src/jit-test/tests/debug/resumption-06.js21
-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-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/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.js33
-rw-r--r--js/src/jit-test/tests/debug/wasm-02.js22
-rw-r--r--js/src/jit-test/tests/debug/wasm-03.js36
-rw-r--r--js/src/jit-test/tests/debug/wasm-04.js34
-rw-r--r--js/src/jit-test/tests/debug/wasm-05.js38
769 files changed, 20687 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 000000000..315e3dcf4
--- /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 000000000..290effcb9
--- /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 000000000..8978fe0c4
--- /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();
+var dbg = new Debugger(g);
+dbg.onNewGlobalObject = function () {
+ dbg.onNewGlobalObject = function () { throw "yadda"; };
+ newGlobal();
+}
+newGlobal();
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 000000000..6ed831b0d
--- /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();
+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 000000000..ba98347a7
--- /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); \ No newline at end of file
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 000000000..0acdb6167
--- /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();
+
+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-allowUnobservedAsmJS-01.js b/js/src/jit-test/tests/debug/Debugger-allowUnobservedAsmJS-01.js
new file mode 100644
index 000000000..836781a19
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-allowUnobservedAsmJS-01.js
@@ -0,0 +1,34 @@
+load(libdir + "asm.js");
+
+var g = newGlobal();
+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);
+
+// Disabling the debugger should uninhibit.
+g.dbg.enabled = false;
+assertEq(asmLink(asmCompile(asmFunStr))(), undefined);
+
+// Enabling it should inhibit again.
+g.dbg.enabled = true;
+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 000000000..f77f42978
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-allowUnobservedAsmJS-02.js
@@ -0,0 +1,25 @@
+// Debugger.allowUnobservedAsmJS with off-thread parsing.
+
+load(libdir + "asm.js");
+
+if (helperThreadCount() == 0)
+ quit();
+
+var g = newGlobal();
+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 000000000..d61c9d8a1
--- /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();
+
+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-ctor-01.js b/js/src/jit-test/tests/debug/Debugger-ctor-01.js
new file mode 100644
index 000000000..3136f01dd
--- /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();
+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 000000000..2095f9beb
--- /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();
+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 000000000..f2245e7c9
--- /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 000000000..6d3dbbb3c
--- /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();
+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 000000000..6c1f4750f
--- /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();
+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 000000000..b8a22c029
--- /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 000000000..e8b23b7e9
--- /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();
+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 000000000..315985668
--- /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();
+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 000000000..45f5b1f52
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-04.js
@@ -0,0 +1,26 @@
+// hasDebuggee tests.
+
+var g1 = newGlobal(), 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 000000000..569594de9
--- /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();
+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 000000000..39e00f484
--- /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();
+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 000000000..0aca89676
--- /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();
+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 000000000..95b4b77b3
--- /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();
+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 000000000..03af30c7d
--- /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();
+var d1 = newGlobal();
+d1.top = this;
+var d2 = newGlobal();
+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 000000000..04035c59b
--- /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();
+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();
+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 000000000..86870f713
--- /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();
+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 000000000..de9262cba
--- /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();
+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 000000000..a87c9bdba
--- /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();
+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 000000000..9270b282e
--- /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();
+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 000000000..4221cb827
--- /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();
+ 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 000000000..0e3408ead
--- /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(" + uneval(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();
+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();
+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 000000000..e119b6ba0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-18.js
@@ -0,0 +1,117 @@
+// 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() {
+ print("assertDebuggees([" + Array.prototype.slice.call(arguments).map((g) => g.toSource()) + "])");
+ var debuggees = dbg.getDebuggees();
+ assertEq(arguments.length, debuggees.length);
+ for each (g in arguments)
+ assertEq(debuggees.indexOf(g) != -1, true);
+}
+
+var g1 = newGlobal(); g1.toSource = function () { return "[global g1]"; };
+var g2 = newGlobal(); 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.global, dg1);
+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.global, dg2);
+assertEq(dg2.unwrap(), dg2);
+assertDebuggees(dg1, dg2);
+
+// "dwg1" means "Debugger.Object referring to CCW of g1".
+var dwg1 = dg2.makeDebuggeeValue(g1);
+assertEq(dwg1.global, dg2);
+assertEq(dwg1.unwrap(), dg1);
+dwg1.toSource = function() { return "[Debugger.Object for CCW of global 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(" + uneval(addAs) + ", " + uneval(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 000000000..3ef59cd1c
--- /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();
+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();
+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 000000000..121698ab4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-20.js
@@ -0,0 +1,31 @@
+// addAllGlobalsAsDebuggees adds all the globals as debuggees.
+
+var g1 = newGlobal(); // Created before the Debugger; debuggee.
+var g2 = newGlobal(); // Created before the Debugger; not debuggee.
+
+var dbg = new Debugger;
+
+var g3 = newGlobal(); // Created after the Debugger; debuggee.
+var g4 = newGlobal(); // 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 globals from their own compartments;
+// this is the sort that findAllGlobals and addDebuggee return.
+assertEq(g1w, g3w.makeDebuggeeValue(g1).unwrap());
+assertEq(g3w, 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 000000000..fb5f1b849
--- /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 000000000..bf1dd638e
--- /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();
+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 000000000..f8e85e45e
--- /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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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 000000000..cf554c16a
--- /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();
+var g2 = newGlobal();
+var g3 = newGlobal();
+
+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 000000000..4ded8e152
--- /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();
+var g2 = newGlobal();
+var g3 = newGlobal();
+
+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 000000000..20072fc4b
--- /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();
+ 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 000000000..05ff5e9e4
--- /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();
+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 000000000..f79ce2b9d
--- /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();
+ 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 000000000..30e97701a
--- /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({ invisibleToDebugger: true });
+
+assertThrowsInstanceOf(() => { new Debugger(g); }, TypeError);
diff --git a/js/src/jit-test/tests/debug/Debugger-enabled-01.js b/js/src/jit-test/tests/debug/Debugger-enabled-01.js
new file mode 100644
index 000000000..3c4ab227f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-enabled-01.js
@@ -0,0 +1,18 @@
+var desc = Object.getOwnPropertyDescriptor(Debugger.prototype, "enabled");
+assertEq(typeof desc.get, 'function');
+assertEq(typeof desc.set, 'function');
+
+var g = newGlobal();
+var hits;
+var dbg = new Debugger(g);
+assertEq(dbg.enabled, true);
+dbg.onDebuggerStatement = function () { hits++; };
+
+var vals = [true, false, null, undefined, NaN, "blah", {}];
+for (var i = 0; i < vals.length; i++) {
+ dbg.enabled = vals[i];
+ assertEq(dbg.enabled, !!vals[i]);
+ hits = 0;
+ g.eval("debugger;");
+ assertEq(hits, vals[i] ? 1 : 0);
+}
diff --git a/js/src/jit-test/tests/debug/Debugger-enabled-02.js b/js/src/jit-test/tests/debug/Debugger-enabled-02.js
new file mode 100644
index 000000000..97a5eb3ed
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-enabled-02.js
@@ -0,0 +1,17 @@
+// Tests that hooks work if set while the Debugger is disabled.
+
+var g = newGlobal();
+var dbg = new Debugger(g);
+var log = "";
+
+g.eval("" + function f() { return 42; });
+
+dbg.enabled = false;
+dbg.onEnterFrame = function (frame) {
+ log += "1";
+};
+dbg.enabled = true;
+
+g.f();
+
+assertEq(log, "1");
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 000000000..7400ef9ea
--- /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 000000000..d8766c160
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findAllGlobals-02.js
@@ -0,0 +1,27 @@
+// Debugger.prototype.findAllGlobals finds ALL the globals!
+
+var g1 = newGlobal(); // Created before the Debugger; debuggee.
+var g2 = newGlobal(); // Created before the Debugger; not debuggee.
+
+var dbg = new Debugger;
+
+var g3 = newGlobal(); // Created after the Debugger; debuggee.
+var g4 = newGlobal(); // Created after the Debugger; not debuggee.
+
+var g1w = dbg.addDebuggee(g1);
+var g3w = dbg.addDebuggee(g3);
+
+var a = dbg.findAllGlobals();
+
+// Get Debugger.Objects viewing the globals from their own compartments;
+// this is the sort that findAllGlobals and addDebuggee return.
+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(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 000000000..4ed43310a
--- /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 000000000..bfb33391f
--- /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();
+
+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 000000000..d391415cc
--- /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();
+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 000000000..a01378e35
--- /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();
+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 000000000..051209196
--- /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();
+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 000000000..8bdedab03
--- /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();
+var g2 = newGlobal();
+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 000000000..3e306a86c
--- /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();
+
+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 000000000..fd6ceb103
--- /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 000000000..a50f2be08
--- /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();
+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 000000000..9595f6963
--- /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();
+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 000000000..f0330447f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findObjects-11.js
@@ -0,0 +1,7 @@
+// This shouldn't segfault.
+
+var g = newGlobal();
+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 000000000..e39691605
--- /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 000000000..a9d85b04e
--- /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();
+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 000000000..1affdb254
--- /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();
+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 000000000..3c2afe1e3
--- /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();
+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 000000000..a013408a1
--- /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();
+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 000000000..0b49ce324
--- /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();
+var g2 = newGlobal();
+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 000000000..73513a545
--- /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();
+var g2 = newGlobal();
+var g3 = newGlobal();
+
+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 000000000..40b3aafff
--- /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 000000000..c43682fad
--- /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();
+var g2 = newGlobal();
+var g3 = newGlobal();
+
+// 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 000000000..468f33205
--- /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:Math.sqrt(4)}).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 000000000..6b8e372d2
--- /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();
+g1.eval('function f(){}');
+
+var g2 = newGlobal();
+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 000000000..7a170645e
--- /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 000000000..670b3d742
--- /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();
+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 000000000..f9f484e97
--- /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 000000000..3350c8ed2
--- /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 000000000..e3adee206
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-12.js
@@ -0,0 +1,128 @@
+// 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();
+g1.toSource = () => "[global g1]";
+g1.load(url1);
+g1.load(url2);
+var g2 = newGlobal();
+g2.toSource = () => "[global g2]";
+g2.load(url1);
+g2.load(url2);
+var g3 = newGlobal();
+
+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 ()
+ "[Debugger.Script for " + func.name + " in " + uneval(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(" + uneval(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 000000000..308344e79
--- /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();
+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 000000000..00da459fc
--- /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 000000000..c2308c613
--- /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();
+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 000000000..92b8176cc
--- /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();
+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 000000000..e837301ca
--- /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();
+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 000000000..b7b250f2b
--- /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();
+
+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 000000000..0983c0eca
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-19.js
@@ -0,0 +1,5 @@
+var g = newGlobal();
+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 000000000..c561bf910
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-20.js
@@ -0,0 +1,20 @@
+var g = newGlobal();
+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, true);
+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-21.js b/js/src/jit-test/tests/debug/Debugger-findScripts-21.js
new file mode 100644
index 000000000..15f7b16e6
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-21.js
@@ -0,0 +1,21 @@
+// Test that delazification works after compartment merging.
+
+if (helperThreadCount() === 0)
+ quit(0);
+
+var g = newGlobal();
+var dbg = new Debugger(g);
+
+var log;
+dbg.onNewScript = function (s) {
+ log += "s";
+ log += dbg.findScripts({ source: s.source }).length;
+}
+
+// Delazify everything just in case before we start the off-thread compile.
+dbg.findScripts();
+
+log = "";
+g.offThreadCompileScript("function inner() { function inner2() { print('inner2'); } print('inner'); }");
+g.runOffThreadScript();
+assertEq(log, "s3");
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 000000000..543d5656f
--- /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();
+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 000000000..bc956a95d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-23.js
@@ -0,0 +1,19 @@
+// If a script is (re)lazified, findScripts should delazify it.
+
+var dbg = new Debugger();
+
+var g = newGlobal();
+g.eval('function f(){}');
+assertEq(g.eval('isLazyFunction(f)'), true);
+
+dbg.addDebuggee(g);
+dbg.findScripts();
+assertEq(g.eval('isLazyFunction(f)'), false);
+
+dbg.removeAllDebuggees();
+relazifyFunctions();
+assertEq(g.eval('isLazyFunction(f)'), true);
+
+dbg.addDebuggee(g);
+var scripts = dbg.findScripts();
+assertEq(g.eval('isLazyFunction(f)'), false);
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 000000000..eec404286
--- /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();
+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-getNewestFrame-01.js b/js/src/jit-test/tests/debug/Debugger-getNewestFrame-01.js
new file mode 100644
index 000000000..54aa90f03
--- /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();
+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.live, 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 000000000..b91afe80c
--- /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();
+var dbg = Debugger(g);
+var hits = 0;
+var savedFrame, savedCallee;
+dbg.onDebuggerStatement = function (frame) {
+ assertEq(frame, savedFrame);
+ assertEq(frame.live, 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 000000000..89ec2ea52
--- /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();
+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-isCompilableUnit.js b/js/src/jit-test/tests/debug/Debugger-isCompilableUnit.js
new file mode 100644
index 000000000..dbfde0ef4
--- /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 000000000..cd29fc6de
--- /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();
+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 stops as soon as any hook 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, 'a');
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 000000000..f02f0e458
--- /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();
+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 000000000..f15868437
--- /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();
+ 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();
+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 000000000..fbe479ff3
--- /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();
+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.live, false);
+assertEq(g.set, false);
+
+// Call g.f as a constructor.
+savedFrame = undefined;
+var r = new g.f;
+assertEq(typeof r, "object");
+assertEq(savedFrame.live, 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.live, 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 000000000..2b8ea2ff3
--- /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();
+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.live, false);
+assertEq(g.set, false);
+
+savedFrame = undefined;
+assertThrowsValue(function () { new g.f; }, "pass");
+assertEq(savedFrame.live, 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 000000000..a42f2183a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-03.js
@@ -0,0 +1,26 @@
+// If debugger.onEnterFrame returns {return:val}, the frame returns immediately.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal();
+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.live, false);
+ savedFrame = frame;
+ return { return: "pass" };
+};
+
+savedFrame = undefined;
+assertEq(g.eval("debugger;"), "pass");
+assertEq(savedFrame.live, 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 000000000..3a30949de
--- /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();
+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.live, 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 000000000..4db69e47a
--- /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();
+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.live, 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.live, false);
+assertEq(debuggee.hits, 1);
+
+// Force an early return from the constructor.
+dbg.onEnterFrame = function (frame) {
+ hits++;
+ if (frame.constructing) {
+ savedFrame = frame;
+ assertEq(savedFrame.live, 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.live, false);
+assertEq(debuggee.hits, 0);
+
+// Force the constructor to throw an exception.
+dbg.onEnterFrame = function (frame) {
+ hits++;
+ if (frame.constructing) {
+ savedFrame = frame;
+ assertEq(savedFrame.live, 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.live, 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.live, 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.live, false);
+assertEq(debuggee.hits, 0);
+assertEq(debuggee.g_hits, 1);
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 000000000..736d29ad0
--- /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 000000000..c8c3d74ad
--- /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 000000000..76a3649a2
--- /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 000000000..096a2dead
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-04.js
@@ -0,0 +1,24 @@
+// 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');
+
+log = '';
+dbg.enabled = false;
+newGlobal();
+assertEq(log, '');
+
+log = '';
+dbg.enabled = true;
+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 000000000..4ec3edc19
--- /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 000000000..f58f4e7ee
--- /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 000000000..15c8568e0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-07.js
@@ -0,0 +1,20 @@
+// 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;
+ if (hit == 2)
+ dbg1.enabled = dbg2.enabled = dbg3.enabled = false;
+};
+
+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-08.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-08.js
new file mode 100644
index 000000000..f981e8ae0
--- /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 000000000..1c4f02972
--- /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 000000000..ce90ed541
--- /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 000000000..33345980d
--- /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 000000000..c40861811
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-12.js
@@ -0,0 +1,29 @@
+// Resumption values from uncaughtExceptionHook from onNewGlobalObject
+// handlers 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;
+var count;
+
+dbg1.onNewGlobalObject = dbg2.onNewGlobalObject = dbg3.onNewGlobalObject = function () {
+ log += 'n';
+ throw 'party';
+};
+
+dbg1.uncaughtExceptionHook = dbg2.uncaughtExceptionHook = dbg3.uncaughtExceptionHook =
+function (ex) {
+ log += 'u';
+ assertEq(ex, 'party');
+ if (++count == 2)
+ return { throw: 'fit' };
+ return undefined;
+};
+
+log = '';
+count = 0;
+assertEq(typeof newGlobal(), 'object');
+assertEq(log, 'nunu');
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 000000000..1ebd0b606
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-13.js
@@ -0,0 +1,17 @@
+// 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();
+assertEq(typeof gw, 'object');
+assertEq(dbg.addDebuggee(g), gw);
+
+// The Debugger.Objects passed to onNewGlobalObject are the global as
+// viewed from its own compartment.
+assertEq(gw.makeDebuggeeValue(g), 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 000000000..4415469ff
--- /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();
+
+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 000000000..16431a406
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-15.js
@@ -0,0 +1,24 @@
+// 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({invisibleToDebugger: true}), "object");
+assertEq(log, '');
+
+assertThrowsInstanceOf(() => dbg.addDebuggee(newGlobal({invisibleToDebugger: true})), Error);
+
+var glob = newGlobal({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 000000000..07081168c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewPromise-01.js
@@ -0,0 +1,19 @@
+// Test that the onNewPromise hook gets called when promises are allocated in
+// the scope of debuggee globals.
+if (!('Promise' in this))
+ quit(0);
+
+var g = newGlobal();
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+
+
+let promisesFound = [];
+dbg.onNewPromise = p => { promisesFound.push(p); };
+
+let p1 = new g.Promise(function (){});
+dbg.enabled = false;
+let p2 = new g.Promise(function (){});
+
+assertEq(promisesFound.indexOf(gw.makeDebuggeeValue(p1)) != -1, true);
+assertEq(promisesFound.indexOf(gw.makeDebuggeeValue(p2)) == -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 000000000..21db892d8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewPromise-02.js
@@ -0,0 +1,26 @@
+// onNewPromise handlers fire, until they are removed.
+if (!('Promise' in this))
+ quit(0);
+
+var g = newGlobal();
+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 000000000..377855a1f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewPromise-03.js
@@ -0,0 +1,43 @@
+// onNewPromise handlers on different Debugger instances are independent.
+if (!('Promise' in this))
+ quit(0);
+
+var g = newGlobal();
+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 000000000..410a0add9
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewPromise-04.js
@@ -0,0 +1,17 @@
+// An onNewPromise handler can disable itself.
+if (!('Promise' in this))
+ quit(0);
+
+var g = newGlobal();
+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 000000000..29edfeedd
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewPromise-05.js
@@ -0,0 +1,27 @@
+// Creating a promise within an onNewPromise handler causes a recursive handler
+// invocation.
+if (!('Promise' in this))
+ quit(0);
+
+var g = newGlobal();
+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 000000000..7e5b9927a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewPromise-06.js
@@ -0,0 +1,37 @@
+// Resumption values from onNewPromise handlers are disallowed.
+if (!('Promise' in this))
+ quit(0);
+
+load(libdir + 'asserts.js');
+
+var g = newGlobal();
+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 000000000..495f356c6
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewPromise-07.js
@@ -0,0 +1,15 @@
+// Errors in onNewPromise handlers are reported correctly, and don't mess up the
+// promise creation.
+if (!('Promise' in this))
+ quit(0);
+
+var g = newGlobal();
+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 000000000..7277ed8ae
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-01.js
@@ -0,0 +1,27 @@
+// Test that the onPromiseSettled hook gets called when a promise settles.
+if (!('Promise' in this))
+ quit(0);
+
+var g = newGlobal();
+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));
+
+log = "";
+dbg.enabled = false;
+p = new g.Promise(function (){});
+g.settlePromiseNow(p);
+
+assertEq(log, "");
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 000000000..febca8f78
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-02.js
@@ -0,0 +1,26 @@
+// onPromiseSettled handlers fire, until they are removed.
+if (!('Promise' in this))
+ quit(0);
+
+var g = newGlobal();
+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 000000000..34e9f2417
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-03.js
@@ -0,0 +1,43 @@
+// onPromiseSettled handlers on different Debugger instances are independent.
+if (!('Promise' in this))
+ quit(0);
+
+var g = newGlobal();
+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 000000000..86df066c8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-04.js
@@ -0,0 +1,17 @@
+// An onPromiseSettled handler can disable itself.
+if (!('Promise' in this))
+ quit(0);
+
+var g = newGlobal();
+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 000000000..da358efe8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-05.js
@@ -0,0 +1,27 @@
+// Settling a promise within an onPromiseSettled handler causes a recursive
+// handler invocation.
+if (!('Promise' in this))
+ quit(0);
+
+var g = newGlobal();
+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 000000000..97108bef4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-06.js
@@ -0,0 +1,37 @@
+// Resumption values from onPromiseSettled handlers are disallowed.
+if (!('Promise' in this))
+ quit(0);
+
+load(libdir + 'asserts.js');
+
+var g = newGlobal();
+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/Environment-01.js b/js/src/jit-test/tests/debug/Environment-01.js
new file mode 100644
index 000000000..7baee2aed
--- /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();
+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 000000000..e78edff0a
--- /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();
+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 000000000..857c20716
--- /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();
+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 000000000..9ba64a26c
--- /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();
+var dbg = new Debugger(g);
+dbg.onEnterFrame = function (frame) { frame.environment; };
+g.Function.prototype();
diff --git a/js/src/jit-test/tests/debug/Environment-callee-01.js b/js/src/jit-test/tests/debug/Environment-callee-01.js
new file mode 100644
index 000000000..424e9164a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-callee-01.js
@@ -0,0 +1,48 @@
+// Debugger.Environment.prototype.callee reveals the callee of environments
+// that have them.
+
+var g = newGlobal();
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+
+function check(code, expectedType, expectedCallee) {
+ print("check(" + uneval(code) + ")");
+ var hits;
+ dbg.onDebuggerStatement = function (frame) {
+ hits++;
+ var env = frame.environment;
+ assertEq(env.type, expectedType);
+ assertEq(env.callee, expectedCallee);
+ };
+ 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-callee-02.js b/js/src/jit-test/tests/debug/Environment-callee-02.js
new file mode 100644
index 000000000..da6ceddbf
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-callee-02.js
@@ -0,0 +1,25 @@
+// Debugger.Environment.prototype.callee gets the right closure.
+
+var g = newGlobal();
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+
+g.eval('function f(x) { return function (y) { 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.callee, gw.makeDebuggeeValue(fun));
+ };
+ 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-callee-03.js b/js/src/jit-test/tests/debug/Environment-callee-03.js
new file mode 100644
index 000000000..6369817a8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-callee-03.js
@@ -0,0 +1,31 @@
+// Environments of different instances of the same generator have the same
+// callee. I love this job.
+
+var g = newGlobal();
+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.callee, gw.makeDebuggeeValue(g.f));
+ };
+ 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');
+
+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-callee-04.js b/js/src/jit-test/tests/debug/Environment-callee-04.js
new file mode 100644
index 000000000..b2b9534d0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-callee-04.js
@@ -0,0 +1,22 @@
+// We shouldn't hand out environment callees when we can only provide the
+// internal function object, not the live function object. (We should never
+// create Debugger.Object instances referring to internal function objects.)
+
+var g = newGlobal();
+var dbg = new Debugger(g);
+
+dbg.onDebuggerStatement = function (frame) {
+ assertEq(frame.older.environment.parent.callee, null);
+}
+
+g.evaluate(`
+
+ function h() { debugger; }
+ (function () {
+ return function () {
+ h();
+ return 1;
+ }
+ })()();
+
+ `);
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 000000000..497fceb2a
--- /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();
+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 000000000..e6e0e80c9
--- /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();
+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 000000000..fd07f603f
--- /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();
+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 000000000..071420243
--- /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();
+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 000000000..e69de29bb
--- /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 000000000..b012dbe0a
--- /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();
+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 000000000..1737f29fd
--- /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();
+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 000000000..1fbd8b9ef
--- /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();
+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 000000000..b2d38ef84
--- /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();
+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 000000000..e3bbad052
--- /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();
+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 000000000..e3413e7f8
--- /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();
+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 000000000..c3aba311d
--- /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();
+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"), 4);
+ assertEq(lexicalEnv.getVariable("e"), 5);
+ hits++;
+};
+g.eval("function f(a, [b, c]) { var d = 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 000000000..b47fe65c1
--- /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();
+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 000000000..22875cb0b
--- /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();
+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 000000000..f4bd52144
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-getVariable-05.js
@@ -0,0 +1,10 @@
+// getVariable sees global variables.
+
+var g = newGlobal();
+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 000000000..096f77d8e
--- /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();
+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 000000000..964a6db58
--- /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();
+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 000000000..48ea9bf87
--- /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();
+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 000000000..d3155e957
--- /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();
+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 000000000..d79056eef
--- /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();
+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 000000000..ac144407e
--- /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();
+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 000000000..8005c3a73
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-getVariable-12.js
@@ -0,0 +1,61 @@
+var g = newGlobal();
+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 () { 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() { 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 000000000..aa8ea34b2
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-getVariable-13.js
@@ -0,0 +1,47 @@
+// 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");
+
+if (!jitTogglesMatch(Opts_Ion2NoOffthreadCompilation))
+ quit(0);
+
+withJitOptions(Opts_Ion2NoOffthreadCompilation, function () {
+ var g = newGlobal();
+ 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 < 200; 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 < 5; 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 000000000..237ea6e1a
--- /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();
+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 000000000..b6eee90e6
--- /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();
+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 000000000..495399985
--- /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();
+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 000000000..fe9f9c620
--- /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();
+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 000000000..ed576bf37
--- /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();
+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 000000000..fe5dcc7c0
--- /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();
+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 000000000..bf9b4086d
--- /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();
+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 000000000..c1d06b7e7
--- /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();
+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++) 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 000000000..ee5897833
--- /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();
+var g2 = newGlobal();
+g2.g1 = g1;
+g1.g2 = g2;
+
+g1.eval('function f(xf) { return function h(xh) { 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-names-01.js b/js/src/jit-test/tests/debug/Environment-names-01.js
new file mode 100644
index 000000000..b58cbb0d3
--- /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();
+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 000000000..982043f63
--- /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();
+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 000000000..a6275cc3b
--- /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();
+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 000000000..72d4ae990
--- /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();
+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.callee, 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 000000000..3ef1702a0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-object-01.js
@@ -0,0 +1,8 @@
+var g = newGlobal();
+var dbg = new Debugger(g);
+
+dbg.onDebuggerStatement = (frame) => {
+ assertEq(frame.environment.parent.type, "object");
+ assertEq(frame.environment.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 000000000..7701ebc07
--- /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();
+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.callee, 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.callee, f.older.callee);
+ 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.callee, 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 000000000..07e61faed
--- /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()
+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-setVariable-01.js b/js/src/jit-test/tests/debug/Environment-setVariable-01.js
new file mode 100644
index 000000000..610960241
--- /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();
+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 000000000..02494609e
--- /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();
+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 000000000..752f8122e
--- /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();
+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 000000000..9b3b32025
--- /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();
+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 000000000..0d5e6aec6
--- /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();
+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 000000000..17d9f8eb0
--- /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();
+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 000000000..5e3cdd177
--- /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();
+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 000000000..014483c4a
--- /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();
+ 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 000000000..6247d1fdf
--- /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();
+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 000000000..6491468e2
--- /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();
+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() { 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 000000000..2fd3b6fbc
--- /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();
+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 000000000..a9e51ca6b
--- /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();
+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 000000000..b19a58e81
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-setVariable-WouldRun.js
@@ -0,0 +1,22 @@
+// setVariable triggering a setter doesn't crash or explode.
+// It should throw WouldRunDebuggee, but that isn't implemented yet.
+
+function test(code) {
+ var g = newGlobal();
+ g.eval("function d() { debugger; }");
+ var dbg = Debugger(g);
+ var hits = 0;
+ dbg.onDebuggerStatement = function (frame) {
+ var env = frame.environment.find("x");
+ try {
+ env.setVariable("x", 0);
+ } catch (exc) {
+ }
+ hits++;
+ };
+ g.eval(code);
+}
+
+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 000000000..35489840c
--- /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();
+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 000000000..0075f5072
--- /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();
+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 000000000..f7fe35c01
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-variables.js
@@ -0,0 +1,86 @@
+// 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, catch, and comprehension bindings
+ "let x = VAL; @@",
+ "{ let x = VAL; @@ }",
+ "try { throw VAL; } catch (x) { @@ }",
+ "try { throw VAL; } catch (x) { @@ }",
+ "for (let x of [VAL]) { @@ }",
+ "for each (let x in [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();
+ g.eval("function debugMe() { var x = 'wrong-x'; 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, uneval(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, "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; 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 () { 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 000000000..cfcbe5a30
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-01.js
@@ -0,0 +1,34 @@
+// Test .type and .generator fields of topmost stack frame passed to onDebuggerStatement.
+
+var g = newGlobal();
+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.generator, 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 in 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 000000000..bb569aca4
--- /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();
+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 000000000..64463ab4e
--- /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();
+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 000000000..9cf6b7dfd
--- /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();
+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 000000000..697ccccbf
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-arguments-02.js
@@ -0,0 +1,19 @@
+// Object arguments.
+
+var g = newGlobal();
+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 000000000..579bc808c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-arguments-03.js
@@ -0,0 +1,34 @@
+// Destructuring arguments.
+
+var g = newGlobal();
+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 000000000..c8d13afee
--- /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();
+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 000000000..89dd11daa
--- /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();
+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 000000000..3ce6f4695
--- /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();
+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.live, 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.live, 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 000000000..29d6d975d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-arguments-07.js
@@ -0,0 +1,23 @@
+// When argument[x] is assigned, where x > callee.length, frame.arguments reflects the change.
+
+var g = newGlobal();
+g.eval("function f(a, b) {\n" +
+ " for (var i = 0; i < arguments.length; i++)\n" +
+ " arguments[i] = i;\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-environment-01.js b/js/src/jit-test/tests/debug/Frame-environment-01.js
new file mode 100644
index 000000000..755885791
--- /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()
+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 000000000..6d6101c59
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-environment-02.js
@@ -0,0 +1,12 @@
+// dbg.getNewestFrame().environment works.
+
+var g = newGlobal();
+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 000000000..3676389e8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-environment-03.js
@@ -0,0 +1,11 @@
+// If !frame.live, frame.environment throws.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal();
+var dbg = Debugger(g);
+var frame;
+g.h = function () { frame = dbg.getNewestFrame(); };
+g.eval("h();");
+assertEq(frame.live, 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 000000000..d74e9f2b9
--- /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();
+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 000000000..98f1c9a12
--- /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();
+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-eval-01.js b/js/src/jit-test/tests/debug/Frame-eval-01.js
new file mode 100644
index 000000000..49a98b60b
--- /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();
+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 000000000..541e19bdc
--- /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();
+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 000000000..69d6a3243
--- /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();
+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 000000000..93d2433e2
--- /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();
+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 000000000..306c5cc73
--- /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();
+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 000000000..a72876cdd
--- /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();
+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(), 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 000000000..5544a40e8
--- /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();
+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 000000000..bf3421d35
--- /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();
+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 000000000..9efb8b63d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-09.js
@@ -0,0 +1,20 @@
+// assigning to local variables in frame.eval code
+
+var g = newGlobal();
+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;
+ 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 000000000..ad4a3dac7
--- /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();
+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 000000000..ab60cf5d1
--- /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();
+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 000000000..d0f3ffc2f
--- /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();
+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 000000000..54a90885c
--- /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();
+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 000000000..0b341093f
--- /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();
+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 000000000..0f0843a5d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-15.js
@@ -0,0 +1,13 @@
+var g = newGlobal();
+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 000000000..d7082a12b
--- /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();
+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 000000000..eefe63587
--- /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();
+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 000000000..5dc645fc4
--- /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();
+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 000000000..3654fb9bb
--- /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();
+ 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 000000000..4622e5a8a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-20.js
@@ -0,0 +1,46 @@
+// 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();
+ 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 000000000..097eb5078
--- /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();
+ 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 000000000..f081ec495
--- /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();
+ 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 000000000..45070d82d
--- /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();
+ 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 000000000..b731771c6
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-24.js
@@ -0,0 +1,24 @@
+// Make sure the getVariable/setVariable/eval functions work correctly with
+// unaliased locals.
+var g = newGlobal();
+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();\
+};');
+
+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 000000000..cc91b2858
--- /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 000000000..ea11f0f4e
--- /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();
+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 000000000..4c7d5fb02
--- /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();
+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 000000000..e70281697
--- /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();
+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 000000000..73a3e735e
--- /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 000000000..f99912c45
--- /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 000000000..bef94a046
--- /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 000000000..9a5309d6a
--- /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-stack.js b/js/src/jit-test/tests/debug/Frame-eval-stack.js
new file mode 100644
index 000000000..1199dfd72
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-stack.js
@@ -0,0 +1,19 @@
+var g = newGlobal();
+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 000000000..e802d9d27
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-01.js
@@ -0,0 +1,35 @@
+// evalWithBindings basics
+
+var g = newGlobal();
+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 000000000..248aa8a20
--- /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();
+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 000000000..f3a3e4185
--- /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();
+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 000000000..d828a1183
--- /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();
+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 000000000..aa8dbd76d
--- /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();
+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 000000000..f1ca7c8f0
--- /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();
+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 000000000..9113198db
--- /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();
+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 000000000..48e51ba6a
--- /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();
+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 000000000..8b9419913
--- /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();
+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 000000000..61fe4eee6
--- /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();
+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 000000000..b1df83a19
--- /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();
+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 000000000..2a0bca435
--- /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();
+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 000000000..2c26b6ea4
--- /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();
+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 000000000..1c8fc93cb
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-14.js
@@ -0,0 +1,20 @@
+var g = newGlobal();
+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 000000000..319eaaeb3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-15.js
@@ -0,0 +1,15 @@
+var g = newGlobal();
+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 000000000..b06a33399
--- /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();
+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.live, 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 000000000..207e6879e
--- /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();
+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.live, 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 000000000..19c62def8
--- /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();
+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 000000000..7aea809b0
--- /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();
+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-implementation-01.js b/js/src/jit-test/tests/debug/Frame-implementation-01.js
new file mode 100644
index 000000000..e26c4ddd1
--- /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();
+ 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 000000000..41b4d6794
--- /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();
+ 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-live-01.js b/js/src/jit-test/tests/debug/Frame-live-01.js
new file mode 100644
index 000000000..192602e1b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-live-01.js
@@ -0,0 +1,41 @@
+// Debugger.Frame.prototype.live 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();
+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].live), 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-live-02.js b/js/src/jit-test/tests/debug/Frame-live-02.js
new file mode 100644
index 000000000..f0961ad00
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-live-02.js
@@ -0,0 +1,32 @@
+// Debugger.Frame.prototype.live is false for frames that have thrown or been thrown through
+
+load(libdir + "asserts.js");
+
+var g = newGlobal();
+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].live, true);
+ };
+ finalCheck = function (n) {
+ assertEq(a.length, n);
+ for (var i = 0; i < n; i++)
+ assertEq(a[i].live, 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-live-03.js b/js/src/jit-test/tests/debug/Frame-live-03.js
new file mode 100644
index 000000000..aa8283cbf
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-live-03.js
@@ -0,0 +1,27 @@
+// frame properties throw if !frame.live
+
+load(libdir + "asserts.js");
+
+var g = newGlobal();
+var f;
+Debugger(g).onDebuggerStatement = function (frame) {
+ assertEq(frame.live, 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.generator, false);
+ assertEq(frame.constructing, false);
+ assertEq(frame.arguments.length, 0);
+ f = frame;
+};
+
+g.eval("(function () { debugger; }).call({});");
+assertEq(f.live, false);
+assertThrowsInstanceOf(function () { f.type; }, Error);
+assertThrowsInstanceOf(function () { f.this; }, Error);
+assertThrowsInstanceOf(function () { f.older; }, Error);
+assertThrowsInstanceOf(function () { f.callee; }, Error);
+assertThrowsInstanceOf(function () { f.generator; }, Error);
+assertThrowsInstanceOf(function () { f.constructing; }, Error);
+assertThrowsInstanceOf(function () { f.arguments; }, Error);
diff --git a/js/src/jit-test/tests/debug/Frame-live-04.js b/js/src/jit-test/tests/debug/Frame-live-04.js
new file mode 100644
index 000000000..c4c9a4d06
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-live-04.js
@@ -0,0 +1,27 @@
+// frame.live is false for frames discarded during uncatchable error unwinding.
+
+var g = newGlobal();
+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].live, false);
diff --git a/js/src/jit-test/tests/debug/Frame-live-05.js b/js/src/jit-test/tests/debug/Frame-live-05.js
new file mode 100644
index 000000000..b8bcc6f9e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-live-05.js
@@ -0,0 +1,29 @@
+// frame.live is false for frames removed after their compartments stopped being debuggees.
+
+var g1 = newGlobal();
+var g2 = newGlobal();
+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].live, false);
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 000000000..4a40d622c
--- /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();
+ 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 000000000..3eb9114c5
--- /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();
+ 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 000000000..1dd0b1bd4
--- /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();
+ 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 000000000..e9bfbc810
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-offset-01.js
@@ -0,0 +1,11 @@
+// frame.offset throws if !frame.live.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal();
+var dbg = Debugger(g);
+var f;
+dbg.onDebuggerStatement = function (frame) { f = frame; };
+g.eval("debugger;");
+assertEq(f.live, 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 000000000..344a11bfc
--- /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();
+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-older-01.js b/js/src/jit-test/tests/debug/Frame-older-01.js
new file mode 100644
index 000000000..1a21d1b44
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-older-01.js
@@ -0,0 +1,19 @@
+// Basic call chain.
+
+var g = newGlobal();
+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-onPop-01.js b/js/src/jit-test/tests/debug/Frame-onPop-01.js
new file mode 100644
index 000000000..c459fd71c
--- /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();
+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 000000000..28d9cef8b
--- /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();
+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 000000000..dfd4dfcaf
--- /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();
+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 000000000..6b3adc1c7
--- /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();
+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 000000000..a4bc22f00
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-05.js
@@ -0,0 +1,25 @@
+var g = newGlobal();
+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 000000000..f627e248b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-06.js
@@ -0,0 +1,19 @@
+// dbg.getNewestFrame in an onPop handler returns the frame being popped.
+var g = newGlobal();
+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-07.js b/js/src/jit-test/tests/debug/Frame-onPop-07.js
new file mode 100644
index 000000000..5ef226b5a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-07.js
@@ -0,0 +1,30 @@
+// Trying to set an onPop handler on a dead frame throws an exception.
+var g = newGlobal();
+g.eval("function f() { }");
+g.eval("function g() { f(); }");
+g.eval("function h() { g(); }");
+g.eval("function i() { h(); }");
+var dbg = new Debugger(g);
+var log;
+
+var frames = [];
+dbg.onEnterFrame = function handleEnter(f) {
+ log += "(";
+ assertEq(f.live, true);
+ frames.push(f);
+};
+log = '';
+g.i();
+assertEq(log, "((((");
+assertEq(frames.length, 4);
+for (i = 0; i < frames.length; i++) {
+ assertEq(frames[i].live, false);
+ var set = false;
+ try {
+ frames[i].onPop = function unappreciated() { };
+ set = true; // don't assert in a 'try' block
+ } catch (x) {
+ assertEq(x instanceof Error, true);
+ }
+ assertEq(set, false);
+}
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 000000000..683948a6e
--- /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();
+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 000000000..abdd3c29d
--- /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();
+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 000000000..9184929a2
--- /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();
+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 000000000..83c60aec7
--- /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();
+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 000000000..044c23b6d
--- /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();
+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 000000000..45ebcc65e
--- /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();
+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 000000000..82b5c9ec8
--- /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();
+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 000000000..d76066ad7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-15.js
@@ -0,0 +1,32 @@
+// Each resumption of a generator gets a fresh frame, whose onPop handler
+// fires the next time the generator yields.
+// This is not the behavior the spec requests, but it's what we do for the
+// moment, and it's good to check that at least we don't crash.
+var g = newGlobal();
+var dbg = new Debugger(g);
+var log;
+
+var debuggerFrames = [];
+var poppedFrames = [];
+dbg.onDebuggerStatement = function handleDebugger(frame) {
+ log += 'd';
+ assertEq(frame.type, "call");
+
+ assertEq(debuggerFrames.indexOf(frame), -1);
+ assertEq(poppedFrames.indexOf(frame), -1);
+ debuggerFrames.push(frame);
+
+ if (frame.eval('i').return % 3 == 0) {
+ frame.onPop = function handlePop(c) {
+ log += ')' + c.return;
+ assertEq(debuggerFrames.indexOf(this) != -1, true);
+ assertEq(poppedFrames.indexOf(this), -1);
+ poppedFrames.push(this);
+ };
+ }
+};
+
+g.eval("function g() { for (var i = 0; i < 10; i++) { debugger; yield i; } }");
+log ='';
+assertEq(g.eval("var t = 0; for (j in g()) t += j; t;"), 45);
+assertEq(log, "d)0ddd)3ddd)6ddd)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 000000000..a1bdf71b6
--- /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();
+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 000000000..5cb382403
--- /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();
+var dbg = new Debugger(g);
+
+// Assigning a bogus value to Debugger.Frame.prototype.onPop raises a TypeError.
+function test(badValue) {
+ print("store " + uneval(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 000000000..0e04de6f4
--- /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();
+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 000000000..0df4154d6
--- /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();
+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 000000000..5da104e32
--- /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();
+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 000000000..2cff89740
--- /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();
+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 000000000..d49e1d456
--- /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();
+
+// 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 000000000..4e811a871
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-after-debugger-return.js
@@ -0,0 +1,11 @@
+// Bug 744730.
+
+var g = newGlobal();
+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-disabled.js b/js/src/jit-test/tests/debug/Frame-onPop-disabled.js
new file mode 100644
index 000000000..f02e41f23
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-disabled.js
@@ -0,0 +1,44 @@
+// An onPop handler in a disabled Debugger's frame shouldn't fire.
+
+var g = newGlobal();
+var dbg = new Debugger(g);
+g.eval('function f() { debugger; }');
+var log;
+dbg.onEnterFrame = function handleEnterFrame(f) {
+ log += '(';
+ assertEq(f.callee.name, 'f');
+ f.onPop = function handlePop(c) {
+ log += ')';
+ assertEq(dbg.enabled, true);
+ };
+};
+
+var enable;
+dbg.onDebuggerStatement = function handleDebugger(f) {
+ dbg.enabled = enable;
+}
+
+
+// This should fire the onEnterFrame and onPop handlers.
+log = 'a';
+enable = true;
+g.f();
+
+// This should fire the onEnterFrame handler, but not the onPop.
+log += 'b';
+enable = false;
+g.f();
+
+// This should fire neither.
+log += 'c';
+dbg.enabled = false;
+enable = false;
+g.f();
+
+// This should fire both again.
+log += 'd';
+dbg.enabled = true;
+enable = true;
+g.f();
+
+assertEq(log, 'a()b(cd()');
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 000000000..b17dac421
--- /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();
+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: " + uneval(type));
+ print("provocation: " + uneval(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 000000000..f0ad9baea
--- /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();
+var dbg = new Debugger(g);
+
+function test(type, provocation) {
+ var log;
+ var wasConstructing;
+
+ // Help people figure out which 'test' call failed.
+ print("type: " + uneval(type));
+ print("provocation: " + uneval(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 000000000..9c7b4f92e
--- /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();
+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 000000000..157dbf5bb
--- /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();
+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 000000000..1fabb6ff8
--- /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();
+var dbg = new Debugger(g);
+
+function test(type, provocation) {
+ var log;
+
+ // Help people figure out which 'test' call failed.
+ print("type: " + uneval(type));
+ print("provocation: " + uneval(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 000000000..0358ca701
--- /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();
+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: " + uneval(type));
+ print("provocation: " + uneval(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-generators-01.js b/js/src/jit-test/tests/debug/Frame-onPop-generators-01.js
new file mode 100644
index 000000000..ebb228c5b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-generators-01.js
@@ -0,0 +1,21 @@
+// |jit-test| error: StopIteration
+// Returning {throw:} from an onPop handler when yielding works and
+// does closes the generator-iterator.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal();
+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;
+g.it.next();
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 000000000..2962e6058
--- /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();
+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(), 1);
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 000000000..87e23825f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-multiple-01.js
@@ -0,0 +1,127 @@
+// Multiple debuggers all get their onPop handlers called, and see each others' effects.
+
+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(); // poor thing
+g.eval('function f() { debugger; return "1"; }');
+
+// We create a bunch of debuggers, but they all consult this global variable
+// for expectations and responses, so the order in which events get
+// reported to the debuggers doesn't matter.
+//
+// This list includes every pair of transitions, and is of minimal length.
+// As if opportunity cost were just some theoretical concern.
+var sequence = [{ expect: { return: '1' }, resume: { return: '2'} },
+ { expect: { return: '2' }, resume: { throw: '3'} },
+ { expect: { throw: '3' }, resume: { return: '4'} },
+ { expect: { return: '4' }, resume: null },
+ { expect: null, resume: { throw: '5'} },
+ { expect: { throw: '5' }, resume: { throw: '6'} },
+ { expect: { throw: '6' }, resume: null },
+ { expect: null, resume: null },
+ { expect: null, resume: { return: '7'} }];
+
+// 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.live, true);
+ frames.push(frame);
+
+ var dbgs = [];
+ var log;
+
+ // Create a separate debugger to carry out each item in sequence.
+ for (s in sequence) {
+ // 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.live, true);
+ frames.push(f);
+ };
+
+ // First expect the 'eval'...
+ dbg.onEnterFrame = function handleEnterEval(f) {
+ log += 'e';
+ assertEq(f.type, 'eval');
+ assertEq(f.live, true);
+ frames.push(f);
+
+ // Then expect the call.
+ dbg.onEnterFrame = function handleEnterCall(f) {
+ log += '(';
+ assertEq(f.type, 'call');
+ assertEq(f.live, true);
+ frames.push(f);
+
+ // Don't expect any further frames.
+ dbg.onEnterFrame = function handleExtraEnter(f) {
+ log += 'z';
+ };
+
+ f.onPop = function handlePop(c) {
+ log += ')' + completionString(c);
+ assertEq(this.live, 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);
+
+ // Check the frame's completion value against 'sequence'.
+ assertEq(completionsEqual(c, sequence[0].expect), true);
+
+ // Provide the next resumption value from 'sequence'.
+ return sequence.shift().resume;
+ };
+ };
+ };
+ }
+
+ log = '';
+ assertEq(completionsEqual(frame.eval('f()'), { return: '7' }), true);
+ assertEq(log, "eeeeeeeee(((((((((ddddddddd)r1)r2)t3)r4)x)t5)t6)x)x");
+
+ 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].live, 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 000000000..791b27752
--- /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();
+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-03.js b/js/src/jit-test/tests/debug/Frame-onPop-multiple-03.js
new file mode 100644
index 000000000..5e9d1411d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-multiple-03.js
@@ -0,0 +1,36 @@
+// One Debugger's onPop handler can disable another Debugger.
+var g = newGlobal();
+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.debugger = this;
+ 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].debugger.enabled = false;
+ else
+ frames[0].debugger.enabled = false;
+ } 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 000000000..953e231ad
--- /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();
+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.live, 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.live, 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 000000000..3aaf5bb0e
--- /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();
+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: " + uneval(type));
+ print("provocation: " + uneval(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 000000000..cdcccd194
--- /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();
+var dbg = new Debugger(g);
+
+function test(type, provocation) {
+ var log;
+ var wasConstructing;
+
+ // Help people figure out which 'test' call failed.
+ print("type: " + uneval(type));
+ print("provocation: " + uneval(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 000000000..f8a845fd7
--- /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();
+var dbg = new Debugger(g);
+
+function test(type, provocation) {
+ var log;
+
+ // Help people figure out which 'test' call failed.
+ print("type: " + uneval(type));
+ print("provocation: " + uneval(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 000000000..af45c616c
--- /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();
+var dbg = new Debugger(g);
+
+function test(type, provocation) {
+ var log;
+ var wasConstructing;
+
+ // Help people figure out which 'test' call failed.
+ print("type: " + uneval(type));
+ print("provocation: " + uneval(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-star-generators-01.js b/js/src/jit-test/tests/debug/Frame-onPop-star-generators-01.js
new file mode 100644
index 000000000..73cf3c8c7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-star-generators-01.js
@@ -0,0 +1,20 @@
+// Returning {throw:} from an onPop handler when yielding works and
+// closes the generator-iterator.
+
+load(libdir + "iteration.js");
+
+var g = newGlobal();
+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;
+assertIteratorDone(g.it);
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-star-generators-02.js b/js/src/jit-test/tests/debug/Frame-onPop-star-generators-02.js
new file mode 100644
index 000000000..de9ad5009
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-star-generators-02.js
@@ -0,0 +1,21 @@
+// |jit-test| error: fit
+
+// Throwing an exception from an onPop handler when yielding terminates the debuggee
+// but does not close the generator-iterator.
+
+load(libdir + 'iteration.js')
+
+var g = newGlobal();
+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;
+assertIteratorNext(g.it, 1);
diff --git a/js/src/jit-test/tests/debug/Frame-onPop-star-generators-03.js b/js/src/jit-test/tests/debug/Frame-onPop-star-generators-03.js
new file mode 100644
index 000000000..5e701a3ab
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-star-generators-03.js
@@ -0,0 +1,42 @@
+// Each resumption of an ES6 generator gets a fresh frame, whose onPop
+// handler fires the next time the generator yields. This is not the
+// behavior the spec requests, but it's what we do for the moment, and
+// it's good to check that at least we don't crash.
+
+load(libdir + 'iteration.js');
+
+var g = newGlobal();
+var dbg = new Debugger(g);
+var log;
+
+var debuggerFrames = [];
+var poppedFrames = [];
+dbg.onDebuggerStatement = function handleDebugger(frame) {
+ log += 'd';
+ assertEq(frame.type, "call");
+
+ assertEq(debuggerFrames.indexOf(frame), -1);
+ assertEq(poppedFrames.indexOf(frame), -1);
+ debuggerFrames.push(frame);
+
+ if (frame.eval('i').return % 3 == 0) {
+ frame.onPop = function handlePop(c) {
+ log += ')' + c.return.value;
+ assertEq(debuggerFrames.indexOf(this) != -1, true);
+ assertEq(poppedFrames.indexOf(this), -1);
+ poppedFrames.push(this);
+ };
+ }
+};
+
+g.eval("function* g() { for (var i = 0; i < 10; i++) { debugger; yield i; } }");
+log ='';
+g.eval("var t = 0, iter = g();");
+for (var j = 0; j < 10; j++)
+ g.eval("t += iter.next().value;");
+assertIteratorResult(g.eval("iter.next()"), undefined, true);
+assertEq(g.eval("t"), 45);
+
+// FIXME: Should equal this, but see bug 917809.
+// assertEq(log, "d)0ddd)3ddd)6ddd)9");
+assertEq(log, "d)undefinedddd)undefinedddd)undefinedddd)undefined");
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 000000000..e0c7b78df
--- /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();
+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: " + uneval(type));
+ print("provocation: " + uneval(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 000000000..bd35b6e16
--- /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();
+var dbg = new Debugger(g);
+
+function test(type, provocation) {
+ var log;
+ var wasConstructing;
+
+ // Help people figure out which 'test' call failed.
+ print("type: " + uneval(type));
+ print("provocation: " + uneval(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 000000000..1c27f0f34
--- /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();
+var dbg = new Debugger(g);
+
+function test(type, provocation) {
+ var log;
+
+ // Help people figure out which 'test' call failed.
+ print("type: " + uneval(type));
+ print("provocation: " + uneval(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 000000000..e50b43240
--- /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();
+var dbg = new Debugger(g);
+
+function test(type, provocation) {
+ var log;
+
+ // Help people figure out which 'test' call failed.
+ print("type: " + uneval(type));
+ print("provocation: " + uneval(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-onStep-01.js b/js/src/jit-test/tests/debug/Frame-onStep-01.js
new file mode 100644
index 000000000..28887f747
--- /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();
+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 000000000..6e02449bc
--- /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();
+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 000000000..14e535156
--- /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();
+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 000000000..d144a3988
--- /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();
+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 000000000..5fcdba945
--- /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();
+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 000000000..c447d83d7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-06.js
@@ -0,0 +1,66 @@
+// After returning from an implicit toString call, the calling frame's onStep
+// hook fires.
+
+var g = newGlobal();
+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("function lastStep() { throw StopIteration; }");
+g.eval("function emptyIterator() { debugger; log += 'x'; return { next: lastStep }; }");
+g.eval("var customEmptyIterator = { __iterator__: emptyIterator };");
+g.log = '';
+g.eval("for (i in customEmptyIterator);\n" +
+ "log += 'y';\n");
+assertEq(g.log, 'dxsy');
+
+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 000000000..6f6e1413f
--- /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();
+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 000000000..106f2bbf9
--- /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();
+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.
+assertEq(log.replace(/[^B]/g, ''), 'BBBB');
+
+// 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 000000000..58333719e
--- /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();
+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 000000000..dad19deff
--- /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();
+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 000000000..dae366165
--- /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();
+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 000000000..4db38b313
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-12.js
@@ -0,0 +1,129 @@
+// 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();
+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");
+
+// The same but without a finally clause. This relies on a
+// SpiderMonkey extension, because otherwise there's no way to see
+// extra instructions at the end of a catch.
+testOne("testCatch",
+ `try {
+ throw new TypeError();
+ } catch (e if e instanceof TypeError) {
+ ${bitOfCode}
+ } catch (e) { // +6
+ } // +7
+ nothing(); // +8
+ `, "189");
+
+// 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 000000000..cc2c18c6d
--- /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();
+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 000000000..2e77d5647
--- /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();
+
+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 000000000..539ff5f05
--- /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();
+
+// 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 000000000..b011da428
--- /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();
+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-iterators.js b/js/src/jit-test/tests/debug/Frame-onStep-iterators.js
new file mode 100644
index 000000000..1b9fa6585
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-iterators.js
@@ -0,0 +1,20 @@
+var g = newGlobal();
+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 000000000..0c486971c
--- /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();
+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: " + uneval(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: " + uneval(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: " + uneval(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 000000000..45a474724
--- /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();
+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 000000000..98b3a4011
--- /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();
+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 000000000..d2ee163ee
--- /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();
+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 000000000..07f2d5c0e
--- /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();
+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 000000000..aae12d224
--- /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();
+ 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-script-01.js b/js/src/jit-test/tests/debug/Frame-script-01.js
new file mode 100644
index 000000000..fbf1d0bd8
--- /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();
+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 000000000..1ca139bb7
--- /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();
+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 000000000..37d410c11
--- /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();
+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-environment-nondebuggee.js b/js/src/jit-test/tests/debug/Frame-script-environment-nondebuggee.js
new file mode 100644
index 000000000..7965ea555
--- /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();
+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 show 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-this-01.js b/js/src/jit-test/tests/debug/Frame-this-01.js
new file mode 100644
index 000000000..3a610a7c5
--- /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();
+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 000000000..8f60a5f46
--- /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();
+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 000000000..fbd55c6e0
--- /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();
+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 000000000..cfa3f5df9
--- /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();
+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 000000000..bd404d240
--- /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();
+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 000000000..04dc3e5d1
--- /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();
+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 000000000..c7bc41272
--- /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();
+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 000000000..493214e3b
--- /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();
+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 000000000..7ad90fe8c
--- /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();
+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 000000000..90a833c98
--- /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();
+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 000000000..6ab8ccba3
--- /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();
+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 000000000..625fa9df5
--- /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();
+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/Memory-01.js b/js/src/jit-test/tests/debug/Memory-01.js
new file mode 100644
index 000000000..7dc00b9ee
--- /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 000000000..6575cfe74
--- /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();
+
+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 000000000..cd8c7df77
--- /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();
+
+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 000000000..920fcff94
--- /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();
+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 000000000..91aa07864
--- /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();
+const dbg = new Debugger();
+const wrappedRoot = 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(),
+ ];
+} + "());");
+
+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 000000000..e9d56bf10
--- /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 000000000..064aed5cb
--- /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();
+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 000000000..169db55d0
--- /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();
+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 000000000..0e8e035da
--- /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();
+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 000000000..3eb6a97bb
--- /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();
+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 000000000..fed52aac6
--- /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();
+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 000000000..e9f2fb41c
--- /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();
+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 000000000..d69249109
--- /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();
+const root2 = newGlobal();
+const root3 = newGlobal();
+
+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 000000000..42e133128
--- /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();
+dbg.addDebuggee(root1);
+root1.eval("this.alloc = {}");
+
+const root2 = newGlobal();
+dbg.addDebuggee(root2);
+root2.eval("this.alloc = {}");
+
+const root3 = newGlobal();
+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 000000000..ff993b45f
--- /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();
+dbg.addDebuggee(root1);
+const root2 = newGlobal();
+dbg.addDebuggee(root2);
+const root3 = newGlobal();
+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-12.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-12.js
new file mode 100644
index 000000000..ed1c4e5f6
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-12.js
@@ -0,0 +1,17 @@
+// Test that disabling the debugger disables allocation tracking.
+
+load(libdir + "asserts.js");
+
+const dbg = new Debugger();
+const root = newGlobal();
+dbg.addDebuggee(root);
+
+dbg.memory.trackingAllocationSites = true;
+dbg.enabled = false;
+
+root.eval("this.alloc = {}");
+
+// We shouldn't accumulate allocations in our log while the debugger is
+// disabled.
+let 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 000000000..4ac6b26a5
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-13.js
@@ -0,0 +1,18 @@
+// 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.
+
+if (helperThreadCount() === 0) {
+ quit(0);
+}
+
+const root = newGlobal();
+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 000000000..dff8c8c0a
--- /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(dateNow());
+
+const root = newGlobal();
+const dbg = new Debugger(root);
+
+dbg.memory.trackingAllocationSites = true;
+root.eval("this.alloc1 = {}");
+allocTimes.push(dateNow());
+root.eval("this.alloc2 = {}");
+allocTimes.push(dateNow());
+root.eval("this.alloc3 = {}");
+allocTimes.push(dateNow());
+root.eval("this.alloc4 = {}");
+allocTimes.push(dateNow());
+
+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 000000000..471e8ce39
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-15.js
@@ -0,0 +1,33 @@
+// Test drainAllocationsLog() and [[Class]] names.
+if (!('Promise' in this))
+ quit(0);
+
+const root = newGlobal();
+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-16.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-16.js
new file mode 100644
index 000000000..b147d6ded
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-16.js
@@ -0,0 +1,47 @@
+// Test drainAllocationsLog() and constructor names.
+
+const root = newGlobal();
+const dbg = new Debugger();
+const wrappedRoot = dbg.addDebuggee(root);
+
+root.eval(
+ `
+ function Ctor() {}
+
+ var nested = {};
+ nested.Ctor = function () {};
+
+ function makeInstance() {
+ let LexicalCtor = function () {};
+ return new LexicalCtor;
+ }
+
+ function makeObject() {
+ let object = {};
+ return object;
+ }
+
+ this.tests = [
+ { name: "Ctor", fn: () => new Ctor },
+ { name: "nested.Ctor", fn: () => new nested.Ctor },
+ { name: "makeInstance/LexicalCtor", fn: () => makeInstance() },
+ { name: null, fn: () => ({}) },
+ { name: null, fn: () => (nested.object = {}) },
+ { name: null, fn: () => makeObject() },
+ ];
+ `
+);
+
+for (let { name, fn } of root.tests) {
+ print(name);
+
+ dbg.memory.trackingAllocationSites = true;
+
+ fn();
+
+ let entries = dbg.memory.drainAllocationsLog();
+ let ctors = entries.map(e => e.constructor);
+ assertEq(ctors.some(ctor => ctor === name), true);
+
+ dbg.memory.trackingAllocationSites = false;
+}
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 000000000..c48afc0f5
--- /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();
+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 000000000..614c4e559
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-18.js
@@ -0,0 +1,27 @@
+// Test drainAllocationsLog() entries' inNursery flag.
+
+const root = newGlobal();
+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 000000000..1ca3649aa
--- /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();
+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 000000000..14cf9e4e7
--- /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();
+ 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 000000000..dda6e6b2f
--- /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();
+dbg.addDebuggee(g1);
+var census1 = dbg.memory.takeCensus();
+Census.walkCensus(census1, "census1", Census.assertAllNotLessThan(census0));
+
+var g2 = newGlobal();
+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 000000000..daef676dd
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-takeCensus-04.js
@@ -0,0 +1,26 @@
+// Test that Debugger.Memory.prototype.takeCensus finds GC roots that are on the
+// stack.
+
+var g = newGlobal();
+var dbg = new Debugger(g);
+
+g.eval(`
+ function withAllocationMarkerOnStack(f) {
+ (function () {
+ var onStack = allocationMarker();
+ f();
+ }())
+ }
+`);
+
+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 000000000..e83425ad0
--- /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();
+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 000000000..542d38174
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-takeCensus-06.js
@@ -0,0 +1,117 @@
+// Check Debugger.Memory.prototype.takeCensus handling of 'breakdown' argument.
+
+load(libdir + 'match.js');
+var Pattern = Match.Pattern;
+
+var g = newGlobal();
+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 },
+ JSScript: { 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,
+ JSScript: 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 000000000..6dae65eac
--- /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();
+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 000000000..637f0d059
--- /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();
+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 000000000..09b28da34
--- /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();
+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 000000000..e7b82379c
--- /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();
+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 000000000..c9c65b003
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-takeCensus-11.js
@@ -0,0 +1,45 @@
+// Check byte counts produced by takeCensus.
+
+const g = newGlobal();
+g.eval("setLazyParsingDisabled(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 000000000..49e57849b
--- /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();
+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 000000000..4175dfeda
--- /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();
+
+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 000000000..fff4666cc
--- /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();
+
+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 000000000..4770537cc
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-trackingAllocationSites-03.js
@@ -0,0 +1,105 @@
+// 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();
+ root2 = newGlobal();
+ 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);
+ });
+
+test("A Debugger isn't tracking allocation sites when disabled.",
+ () => {
+ dbg1.memory.trackingAllocationSites = true;
+ let d1r1 = dbg1.addDebuggee(root1);
+
+ assertEq(isTrackingAllocations(root1, d1r1), true);
+ dbg1.enabled = false;
+ assertEq(isTrackingAllocations(root1, d1r1), false);
+ });
+
+test("Re-enabling throws an error if we can't reinstall allocations tracking " +
+ "for all debuggees.",
+ () => {
+ dbg1.enabled = false
+ dbg1.memory.trackingAllocationSites = true;
+ let d1r1 = dbg1.addDebuggee(root1);
+ let d1r2 = dbg1.addDebuggee(root2);
+
+ // Can't install allocation hooks for root2 with this set.
+ root2.enableShellAllocationMetadataBuilder();
+
+ assertThrowsInstanceOf(() => dbg1.enabled = true,
+ Error);
+
+ assertEq(dbg1.enabled, 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 000000000..c897ee713
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-01.js
@@ -0,0 +1,17 @@
+// Debugger.Object basics
+
+var g = newGlobal();
+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 000000000..57e862084
--- /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();
+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 000000000..954e80b66
--- /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();
+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:} 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");
+ 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 000000000..938c75a52
--- /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();
+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 000000000..3f29809d6
--- /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();
+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 000000000..dd327d87e
--- /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();
+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 000000000..1f5e3be42
--- /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();
+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 000000000..033d6b07e
--- /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();
+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 000000000..d5dadf9b8
--- /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();
+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 000000000..705302dad
--- /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();
+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 000000000..50d55b428
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-callable.js
@@ -0,0 +1,18 @@
+// Test Debugger.Object.prototype.callable.
+
+var g = newGlobal();
+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 000000000..5e90c6562
--- /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();
+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-defineProperties-01.js b/js/src/jit-test/tests/debug/Object-defineProperties-01.js
new file mode 100644
index 000000000..ee1bce436
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-defineProperties-01.js
@@ -0,0 +1,46 @@
+// Debug.Object.prototype.defineProperties.
+
+var g = newGlobal();
+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 000000000..2d930449c
--- /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();
+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 000000000..7e7773281
--- /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();
+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 000000000..21812af08
--- /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();
+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 000000000..0b1c43bd1
--- /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();
+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 000000000..82add1791
--- /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();
+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 000000000..e109e748e
--- /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();
+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 000000000..c7a3dde5e
--- /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();
+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 000000000..92aed6f1b
--- /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();
+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 000000000..f75c6edf2
--- /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();
+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 000000000..b3226294e
--- /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();
+var g2 = newGlobal();
+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 000000000..c8493c6a1
--- /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();
+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 000000000..62a7d63be
--- /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();
+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 000000000..8b3205405
--- /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();
+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 000000000..84ec3fbb5
--- /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();
+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 000000000..7da13e75c
--- /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();
+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 000000000..193824b08
--- /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();
+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-surfaces-01.js b/js/src/jit-test/tests/debug/Object-defineProperty-surfaces-01.js
new file mode 100644
index 000000000..5bbea9122
--- /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();
+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 000000000..70ed69ce2
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-deleteProperty-01.js
@@ -0,0 +1,17 @@
+// Basic deleteProperty tests.
+
+var g = newGlobal();
+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 000000000..9115f551c
--- /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();
+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 000000000..0048c2f57
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-deleteProperty-error-02.js
@@ -0,0 +1,19 @@
+var g = newGlobal();
+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-displayName-01.js b/js/src/jit-test/tests/debug/Object-displayName-01.js
new file mode 100644
index 000000000..8e046d59c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-displayName-01.js
@@ -0,0 +1,17 @@
+// Debugger.Object.prototype.displayName
+
+var g = newGlobal();
+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");
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 000000000..d6883ad8c
--- /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()
+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 000000000..088bc09a8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-environment-02.js
@@ -0,0 +1,20 @@
+// The .environment of a function Debugger.Object is an Environment object.
+
+var g = newGlobal()
+var dbg = Debugger(g);
+var hits = 0;
+g.h = function () {
+ var frame = dbg.getNewestFrame();
+ var fn = frame.eval("j").return;
+ assertEq(fn.environment instanceof Debugger.Environment, true);
+ var closure = frame.eval("f").return;
+ assertEq(closure.environment instanceof Debugger.Environment, true);
+ hits++;
+};
+g.eval("function j(a) {\n" +
+ " var f = function () { return a; };\n" +
+ " h();\n" +
+ " return f;\n" +
+ "}\n" +
+ "j(0);\n");
+assertEq(hits, 1);
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 000000000..eeda4f2a5
--- /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();
+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();
+ 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 000000000..a7f8efb41
--- /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();
+var h = newGlobal();
+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 000000000..6623466fc
--- /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();
+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 000000000..a19005fb0
--- /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();
+var h = newGlobal();
+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 000000000..fb8394586
--- /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();
+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 000000000..f89302010
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-executeInGlobal-05.js
@@ -0,0 +1,22 @@
+// 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();
+var dg1 = dbg.addDebuggee(g1);
+
+var g2 = newGlobal();
+var dg2 = dbg.addDebuggee(g2);
+
+// Generate a Debugger.Object viewing g2 from g1's compartment.
+var dg1wg2 = dg1.makeDebuggeeValue(g2);
+assertEq(dg1wg2.global, dg1);
+assertEq(dg1wg2.unwrap(), dg2);
+assertThrowsInstanceOf(function () { dg1wg2.executeInGlobal('1'); }, TypeError);
+assertThrowsInstanceOf(function () { dg1wg2.executeInGlobalWithBindings('x', { x: 1 }); }, TypeError);
+
+// These, however, should not throw.
+assertEq(dg1wg2.unwrap().executeInGlobal('1729').return, 1729);
+assertEq(dg1wg2.unwrap().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 000000000..a7390aeb2
--- /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();
+var g1w = dbg.addDebuggee(g1);
+
+assertEq(g1w.executeInGlobal('this').return, g1w);
+assertEq(g1w.executeInGlobalWithBindings('this', { x:42 }).return, g1w);
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 000000000..5522e3b45
--- /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();
+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 000000000..61dc3a14b
--- /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();
+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 000000000..f6b13c6a9
--- /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();
+ 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 000000000..904b62480
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-executeInGlobal-10.js
@@ -0,0 +1,13 @@
+var g = newGlobal();
+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 000000000..4546a23d6
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-forceLexicalInitializationByName.js
@@ -0,0 +1,61 @@
+load(libdir + "asserts.js");
+
+var g = newGlobal();
+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 000000000..c2c6c51fc
--- /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();
+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 000000000..bea9aad91
--- /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();
+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();
+ 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 000000000..5a67074dd
--- /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();
+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 000000000..9523cd5ea
--- /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();
+var dbg = Debugger();
+var gobj = dbg.addDebuggee(g);
+g.self = g;
+var desc = gobj.getOwnPropertyDescriptor("self");
+assertEq(desc.value, gobj);
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 000000000..78f5d9efa
--- /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();
+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 000000000..641e5b837
--- /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();
+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 000000000..c8510c9d8
--- /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();
+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 000000000..98200c880
--- /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();
+var g2 = newGlobal();
+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-surfaces-01.js b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-surfaces-01.js
new file mode 100644
index 000000000..a3224739b
--- /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();
+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 000000000..7da47e954
--- /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();
+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 000000000..9637e1ee0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-getOwnPropertyNames-01.js
@@ -0,0 +1,33 @@
+// Basic getOwnPropertyNames tests.
+
+var g = newGlobal();
+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 000000000..8ed94e67a
--- /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();
+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 000000000..029c765b7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-getOwnPropertySymbols-01.js
@@ -0,0 +1,33 @@
+// Basic getOwnPropertSymbols tests.
+
+var g = newGlobal();
+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 000000000..5f7175676
--- /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();
+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-global-01.js b/js/src/jit-test/tests/debug/Object-global-01.js
new file mode 100644
index 000000000..24a5a5f32
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-global-01.js
@@ -0,0 +1,26 @@
+// Debugger.Object.prototype.global accessor surfaces.
+
+load(libdir + 'asserts.js');
+
+var dbg = new Debugger;
+var g = newGlobal();
+var gw = dbg.addDebuggee(g);
+
+assertEq(Object.getOwnPropertyDescriptor(gw, 'global'), undefined);
+var d = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(gw), 'global');
+assertEq(d.enumerable, false);
+assertEq(d.configurable, true);
+assertEq(typeof d.get, "function");
+assertEq(d.get.length, 0);
+assertEq(d.set, undefined);
+
+// This should not throw.
+gw.global = '';
+
+// This should throw.
+assertThrowsInstanceOf(function () { "use strict"; gw.global = {}; }, TypeError);
+assertEq(gw.global, gw);
+
+// You shouldn't be able to apply the accessor to the prototype.
+assertThrowsInstanceOf(function () { return Debugger.Object.prototype.global; },
+ TypeError);
diff --git a/js/src/jit-test/tests/debug/Object-global-02.js b/js/src/jit-test/tests/debug/Object-global-02.js
new file mode 100644
index 000000000..68fc1b166
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-global-02.js
@@ -0,0 +1,25 @@
+// Debugger.Object.prototype.global retrieves the correct global.
+
+var dbg = new Debugger;
+var g1 = newGlobal();
+var g1w = dbg.addDebuggee(g1);
+var g2 = newGlobal();
+var g2w = dbg.addDebuggee(g2);
+
+assertEq(g1w === g2w, false);
+assertEq(g1w.global, g1w);
+assertEq(g2w.global, g2w);
+
+var g1ow = g1w.makeDebuggeeValue(g1.Object());
+var g2ow = g2w.makeDebuggeeValue(g2.Object());
+assertEq(g1ow.global, g1w);
+assertEq(g2ow.global, g2w);
+
+// mild paranoia
+assertEq(g1ow.global === g1ow, false);
+assertEq(g2ow.global === g2ow, false);
+
+// The .global accessor doesn't unwrap.
+assertEq(g1w.makeDebuggeeValue(g2.Object()).global, g1w);
+assertEq(g2w.makeDebuggeeValue(g1.Object()).global, g2w);
+
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 000000000..a4c48e420
--- /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();
+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 000000000..ec7f07a4b
--- /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();
+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 000000000..d3e8fc08b
--- /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();
+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 000000000..34c4a1de5
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-isArrowFunction.js
@@ -0,0 +1,22 @@
+// Debugger.Object.prototype.isArrowFunction recognizes arrow functions.
+
+var g = newGlobal();
+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("")');
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 000000000..5537150e3
--- /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();
+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 000000000..ef3e099b8
--- /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();
+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 000000000..eb68b019b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-name-01.js
@@ -0,0 +1,13 @@
+// Debugger.Object.prototype.name
+
+var g = newGlobal();
+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");
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 000000000..2406c3d55
--- /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();
+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 000000000..24c0702d1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-parameterNames.js
@@ -0,0 +1,33 @@
+load(libdir + 'array-compare.js');
+
+var g = newGlobal();
+var dbg = new Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ var arr = frame.arguments;
+ assertEq(arraysEqual(arr[0].parameterNames, []), true);
+ assertEq(arraysEqual(arr[1].parameterNames, ["x"]), true);
+ assertEq(arraysEqual(arr[2].parameterNames,
+ ["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"]),
+ true);
+ assertEq(arraysEqual(arr[3].parameterNames, ["a", (void 0), (void 0)]), true);
+ assertEq(arr[4].parameterNames, (void 0));
+ assertEq(arraysEqual(arr[5].parameterNames, [(void 0), (void 0)]), true);
+ assertEq(arr.length, 6);
+ hits++;
+};
+
+g.eval("("
+ + function () {
+ (function () { debugger; }
+ (function () {},
+ function (x) {},
+ 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) {},
+ function (a, [b, c], {d, e:f}) { },
+ {a:1},
+ Math.atan2
+ ));
+ }
+ +")()");
+assertEq(hits, 1);
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 000000000..729a72262
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-preventExtensions-01.js
@@ -0,0 +1,17 @@
+// Basic preventExtensions test.
+
+var g = newGlobal();
+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-proto.js b/js/src/jit-test/tests/debug/Object-proto.js
new file mode 100644
index 000000000..8d1791445
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-proto.js
@@ -0,0 +1,23 @@
+// Debugger.Object.prototype.proto
+var g = newGlobal();
+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 000000000..b8296fe6d
--- /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();
+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 000000000..650b71374
--- /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();
+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 000000000..5e4643e38
--- /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();
+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 000000000..b58dc38ad
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-script-lazy.js
@@ -0,0 +1,61 @@
+// 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();
+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();
+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.global, g2w);
+assertEq(fDO.unwrap().global === g2w, false);
+assertEq(fDO.unwrap().script, null);
+
+// Similarly for g1.g, and asking for its parameter names.
+var gDO = g2w.getOwnPropertyDescriptor('g').value;
+assertEq(gDO.global, g2w);
+assertEq(gDO.unwrap().global === g2w, false);
+assertEq(gDO.unwrap().parameterNames, undefined);
+
+// Similarly for g1.h, and asking for its bound function properties.
+var hDO = g2w.getOwnPropertyDescriptor('h').value;
+assertEq(hDO.global, g2w);
+assertEq(hDO.unwrap().global === g2w, false);
+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 000000000..a73032f93
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-script.js
@@ -0,0 +1,13 @@
+var g = newGlobal();
+var dbg = new Debugger(g);
+var hits = 0;
+dbg.onDebuggerStatement = function (frame) {
+ var arr = frame.arguments;
+ assertEq(arr[0].script instanceof Debugger.Script, true);
+ assertEq(arr[1].script, undefined);
+ assertEq(arr[2].script, undefined);
+ hits++;
+};
+
+g.eval("(function () { debugger; })(function g(){}, {}, Math.atan2);");
+assertEq(hits, 1);
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 000000000..760060208
--- /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();
+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-unsafeDereference-01.js b/js/src/jit-test/tests/debug/Object-unsafeDereference-01.js
new file mode 100644
index 000000000..7e560b665
--- /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();
+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 000000000..f9668c5c2
--- /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();
+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 000000000..1f1c0e586
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-unwrap-02.js
@@ -0,0 +1,23 @@
+// Debugger.Object.prototype.unwrap unwraps Debugger.Objects referring to
+// cross-compartment wrappers.
+
+var dbg = new Debugger();
+
+var g1 = newGlobal();
+var dg1 = dbg.addDebuggee(g1);
+assertEq(dg1.unwrap(), dg1);
+
+var g2 = newGlobal();
+var dg2 = dbg.addDebuggee(g2);
+
+var dg1g2 = dg1.makeDebuggeeValue(g2);
+assertEq(dg1g2.global, dg1);
+assertEq(dg1g2.unwrap(), dg2);
+
+// Try an ordinary object, not a global.
+var g2o = g2.Object();
+var dg2o = dg2.makeDebuggeeValue(g2o);
+var dg1g2o = dg1.makeDebuggeeValue(g2o);
+assertEq(dg1g2o.global, dg1);
+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 000000000..cdb6d84ef
--- /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({ 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/RematerializedFrame-retval.js b/js/src/jit-test/tests/debug/RematerializedFrame-retval.js
new file mode 100644
index 000000000..ce16404d1
--- /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();
+ 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 000000000..330ff8a54
--- /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();
+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 000000000..071dc465a
--- /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 000000000..4d0b2bb83
--- /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();
+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 000000000..3fefb98c0
--- /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();
+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 000000000..34fdc91ba
--- /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();
+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 000000000..6ddb95f03
--- /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();
+
+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 000000000..1f1000d73
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-displayName-01.js
@@ -0,0 +1,17 @@
+// Debugger.Script.prototype.displayName
+
+var g = newGlobal();
+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 000000000..008de4e62
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-format-01.js
@@ -0,0 +1,19 @@
+// Tests that JavaScript scripts have a "js" format and wasm scripts have a
+// "wasm" format.
+
+var g = newGlobal();
+var dbg = new Debugger(g);
+
+var gotScript;
+dbg.onNewScript = (script) => {
+ gotScript = script;
+};
+
+g.eval(`(() => {})()`);
+assertEq(gotScript.format, "js");
+
+if (!wasmIsSupported())
+ quit();
+
+g.eval(`o = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (func) (export "" 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 000000000..5d71b1e39
--- /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();
+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 000000000..33d33dfc1
--- /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();
+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, 1);
+
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 000000000..b2cb70232
--- /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();
+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, 1);
+
+gc();
diff --git a/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-01.js b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-01.js
new file mode 100644
index 000000000..620d56244
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-01.js
@@ -0,0 +1,19 @@
+// getColumnOffsets correctly places the various parts of a ForStatement.
+
+var global = newGlobal();
+Debugger(global).onDebuggerStatement = function (frame) {
+ var script = frame.eval("f").return.script;
+ script.getAllColumnOffsets().forEach(function (offset) {
+ script.setBreakpoint(offset.offset, {
+ hit: function (frame) {
+ assertEq(offset.lineNumber, 1);
+ global.log += offset.columnNumber + " ";
+ }
+ });
+ });
+};
+
+global.log = '';
+global.eval("function f(n) { for (var i = 0; i < n; ++i) log += '. '; log += '! '; } debugger;");
+global.f(3);
+assertEq(global.log, "25 32 44 . 39 32 44 . 39 32 44 . 39 32 57 ! 70 ");
diff --git a/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-02.js b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-02.js
new file mode 100644
index 000000000..405b13d62
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-02.js
@@ -0,0 +1,21 @@
+// getColumnOffsets correctly places multiple variable declarations.
+
+var global = newGlobal();
+Debugger(global).onDebuggerStatement = function (frame) {
+ var script = frame.eval("f").return.script;
+ script.getAllColumnOffsets().forEach(function (offset) {
+ script.setBreakpoint(offset.offset, {
+ hit: function (frame) {
+ assertEq(offset.lineNumber, 1);
+ global.log += offset.columnNumber + " ";
+ }
+ });
+ });
+};
+
+global.log = '';
+global.eval("function f(n){var w0,x1=3,y2=4,z3=9} debugger;");
+global.f(3);
+
+// Should have hit each variable declared.
+assertEq(global.log, "21 26 31 35 ");
diff --git a/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-03.js b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-03.js
new file mode 100644
index 000000000..1019f0cdf
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-03.js
@@ -0,0 +1,20 @@
+// getColumnOffsets correctly places comma separated expressions.
+
+var global = newGlobal();
+Debugger(global).onDebuggerStatement = function (frame) {
+ var script = frame.eval("f").return.script;
+ script.getAllColumnOffsets().forEach(function (offset) {
+ script.setBreakpoint(offset.offset, {
+ hit: function (frame) {
+ assertEq(offset.lineNumber, 1);
+ global.log += offset.columnNumber + " ";
+ }
+ });
+ });
+};
+
+global.log = '';
+global.eval("function f(n){print(n),print(n),print(n)} debugger;");
+global.f(3);
+// Should hit each call that was separated by commas.
+assertEq(global.log, "14 23 32 40 ");
diff --git a/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-04.js b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-04.js
new file mode 100644
index 000000000..23a537f53
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-04.js
@@ -0,0 +1,20 @@
+// getColumnOffsets correctly places object properties.
+
+var global = newGlobal();
+Debugger(global).onDebuggerStatement = function (frame) {
+ var script = frame.eval("f").return.script;
+ script.getAllColumnOffsets().forEach(function (offset) {
+ script.setBreakpoint(offset.offset, {
+ hit: function (frame) {
+ assertEq(offset.lineNumber, 1);
+ global.log += offset.columnNumber + " ";
+ }
+ });
+ });
+};
+
+global.log = '';
+global.eval("function f(n){var o={a:1,b:2,c:3}} debugger;");
+global.f(3);
+// Should hit each property in the object.
+assertEq(global.log, "18 21 25 29 33 ");
diff --git a/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-05.js b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-05.js
new file mode 100644
index 000000000..0e1704ee2
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-05.js
@@ -0,0 +1,20 @@
+// getColumnOffsets correctly places array properties.
+
+var global = newGlobal();
+Debugger(global).onDebuggerStatement = function (frame) {
+ var script = frame.eval("f").return.script;
+ script.getAllColumnOffsets().forEach(function (offset) {
+ script.setBreakpoint(offset.offset, {
+ hit: function (frame) {
+ assertEq(offset.lineNumber, 1);
+ global.log += offset.columnNumber + " ";
+ }
+ });
+ });
+};
+
+global.log = '';
+global.eval("function f(n){var a=[1,2,n]} debugger;");
+global.f(3);
+// Should hit each item in the array.
+assertEq(global.log, "18 21 23 25 27 ");
diff --git a/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-06.js b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-06.js
new file mode 100644
index 000000000..20b49add8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-06.js
@@ -0,0 +1,28 @@
+// getColumnOffsets correctly places function calls.
+
+var global = newGlobal();
+Debugger(global).onDebuggerStatement = function (frame) {
+ var script = frame.eval("f").return.script;
+ script.getAllColumnOffsets().forEach(function (offset) {
+ script.setBreakpoint(offset.offset, {
+ hit: function (frame) {
+ assertEq(offset.lineNumber, 1);
+ global.log += offset.columnNumber + " ";
+ }
+ });
+ });
+};
+
+global.log = "";
+global.eval("function ppppp() { return 1; }");
+// 1 2 3 4
+// 01234567890123456789012345678901234567890123456789
+global.eval("function f(){ 1 && ppppp(ppppp()) && new Error() } debugger;");
+global.f();
+
+// 14 - Enter the function body
+// 25 - Inner print()
+// 19 - Outer print()
+// 37 - new Error()
+// 49 - Exit the function body
+assertEq(global.log, "14 25 19 37 49 ");
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 000000000..1d31d9972
--- /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();
+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 000000000..183fe1180
--- /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();
+
+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 000000000..e0b245a85
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getChildScripts-01.js
@@ -0,0 +1,42 @@
+// Basic getChildScripts tests.
+
+var g = newGlobal();
+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 r(n) { for (var i = 0; i < n; i++) yield i; } debugger;",
+ "S[S]");
+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 000000000..2a6c1b9ca
--- /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();
+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 000000000..2e29a7c86
--- /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();
+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 000000000..ab2a979d5
--- /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();
+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 000000000..ffdfcd0e2
--- /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();
+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-getLineOffsets-01.js b/js/src/jit-test/tests/debug/Script-getLineOffsets-01.js
new file mode 100644
index 000000000..ae1d8c35b
--- /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();
+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 000000000..c77691af2
--- /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();
+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 000000000..d6e3f37cd
--- /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();
+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 000000000..8fee27913
--- /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();
+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 000000000..f2f65a232
--- /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();
+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 000000000..da312ccf9
--- /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();
+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 000000000..726a38e5d
--- /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();
+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 000000000..8a7fb69e1
--- /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();
+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 000000000..58ee76c81
--- /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();
+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 000000000..84215fd3b
--- /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();
+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 000000000..7cfc2e790
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getOffsetLocation.js
@@ -0,0 +1,37 @@
+// getOffsetLocation agrees with getAllColumnOffsets
+
+var global = newGlobal();
+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-getOffsetsCoverage-01.js b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-01.js
new file mode 100644
index 000000000..130d28439
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-01.js
@@ -0,0 +1,475 @@
+// 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.toSource();
+ 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();
+ 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 () { //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,- //BRDA:$,0,1,1 //BRDA:$,0,2,- //BRDA:$,0,3,-
+ 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,- //BRDA:$,0,1,- //BRDA:$,0,2,1 //BRDA:$,0,3,-
+ 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,- //BRDA:$,0,1,- //BRDA:$,0,2,1 //BRDA:$,0,3,-
+ 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,- //BRDA:$,0,1,- //BRDA:$,0,2,1 //BRDA:$,0,3,-
+ 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,- //BRDA:$,0,1,- //BRDA:$,0,2,1 //BRDA:$,0,3,- //BRDA:$,0,4,-
+ 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,- //BRDA:$,0,1,- //BRDA:$,0,2,- //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
+});
+
+// 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 000000000..20c8f6b8d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-02.js
@@ -0,0 +1,41 @@
+// |jit-test| --ion-pgo=off;
+
+// This script check that when we enable / disable the code coverage collection,
+// then we have different results for the getOffsetsCoverage methods.
+
+var g = newGlobal();
+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.toSource());
+
+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 000000000..094f60447
--- /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();
+var dbg = Debugger(g);
+function loop(i) {
+ var n = 0;
+ for (n = 0; n < i; n++)
+ debugger;
+}
+g.eval(loop.toSource());
+
+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 000000000..e9888ac16
--- /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();
+var dbg = Debugger(g);
+
+function loop(i) {
+ var n = 0;
+ for (n = 0; n < i; n++)
+ debugger;
+}
+g.eval(loop.toSource());
+
+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 000000000..de2c74351
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-05.js
@@ -0,0 +1,24 @@
+var g = newGlobal();
+var dbg = Debugger(g);
+function f(x) {
+ while (x) {
+ interruptIf(true);
+ x -= 1;
+ }
+}
+g.eval(f.toSource());
+
+// 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 000000000..633c8176b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-bug1233178.js
@@ -0,0 +1,13 @@
+gczeal(2);
+g = newGlobal();
+dbg = Debugger(g);
+function loop() {
+ for (var i = 0; i < 10; i++)
+ debugger;
+}
+g.eval(loop.toSource());
+dbg.onDebuggerStatement = function(f) {
+ f.script.getOffsetsCoverage();
+}
+dbg.collectCoverageInfo = true;
+g.eval("loop")();
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 000000000..bc088a33a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-global-01.js
@@ -0,0 +1,20 @@
+// Debugger.Script.prototype.script returns the global the script runs in.
+
+var g = newGlobal();
+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');
+
+assertEq(gw.getOwnPropertyDescriptor('f').value.global, gw);
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 000000000..fb4f30205
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-global-02.js
@@ -0,0 +1,40 @@
+// Debugger.Script.prototype.script returns the global the script runs in.
+// Multi-global version.
+
+var dbg = new Debugger;
+
+var g1 = newGlobal();
+var g1w = dbg.addDebuggee(g1);
+
+var g2 = newGlobal();
+var g2w = dbg.addDebuggee(g2);
+
+var g3 = newGlobal();
+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');
+
+assertEq(g1w.getOwnPropertyDescriptor('f').value.global, g1w);
+assertEq(g2w.getOwnPropertyDescriptor('g').value.global, g2w);
+assertEq(g3w.getOwnPropertyDescriptor('h').value.global, g3w);
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 000000000..0aabc5da4
--- /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();
+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-lineCount.js b/js/src/jit-test/tests/debug/Script-lineCount.js
new file mode 100644
index 000000000..8eff08112
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-lineCount.js
@@ -0,0 +1,23 @@
+// Test Script.lineCount.
+
+var g = newGlobal();
+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
+ '})();\n'; // 4
+test(src, 5);
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 000000000..2698bd3f6
--- /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();
+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 000000000..77eaaae6d
--- /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();
+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 000000000..5e535eb97
--- /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();
+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 000000000..dd1cf2374
--- /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();
+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 000000000..c947a6b0b
--- /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();
+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 000000000..1910dde5a
--- /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();
+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 000000000..c12e669bf
--- /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();
+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, 5]);
+test("new Function('x', 'x * x')", [0, 5]);
+assertEq(count, 6);
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 000000000..f5b2d1085
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-startLine.js
@@ -0,0 +1,63 @@
+var g = newGlobal();
+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;\n");
+});
+
+test(function () {
+ g.evaluate("first = Error().lineNumber;\n" +
+ "debugger;\n" +
+ Array(17000).join("\n") +
+ "last = Error().lineNumber;\n");
+});
+
+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 000000000..71f9dc629
--- /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();
+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 000000000..f8017d7f7
--- /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('new-compartment');
+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.js b/js/src/jit-test/tests/debug/Source-displayURL.js
new file mode 100644
index 000000000..a16a76186
--- /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('new-compartment');
+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 000000000..70c76bef0
--- /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();
+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 000000000..38c92280b
--- /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 000000000..7b192fe1b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-element-03.js
@@ -0,0 +1,27 @@
+// Owning elements and attribute names are attached to scripts compiled
+// off-thread.
+
+if (helperThreadCount() === 0)
+ quit(0);
+
+var g = newGlobal();
+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-elementAttributeName.js b/js/src/jit-test/tests/debug/Source-elementAttributeName.js
new file mode 100644
index 000000000..685d2f5eb
--- /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('new-compartment');
+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 000000000..e61936cc4
--- /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();
+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 000000000..0bc8324f0
--- /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();
+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 000000000..96a07b565
--- /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();
+g1.g1 = g1;
+var g1DO = dbg.addDebuggee(g1);
+
+var g2 = newGlobal();
+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-introductionScript-04.js b/js/src/jit-test/tests/debug/Source-introductionScript-04.js
new file mode 100644
index 000000000..6f0721b63
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-introductionScript-04.js
@@ -0,0 +1,8 @@
+// Function.prototype's script source should be fully initialized.
+var g = newGlobal();
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+
+var DOfp = gw.getOwnPropertyDescriptor('Function').value.proto;
+// This should not crash.
+print(DOfp.script.source.introductionScript);
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 000000000..eab746921
--- /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 000000000..448b784c2
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-introductionType.js
@@ -0,0 +1,120 @@
+// Check that scripts' introduction types are properly marked.
+
+if (helperThreadCount() === 0)
+ quit(0);
+
+var g = newGlobal();
+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 000000000..277c32459
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-invisible.js
@@ -0,0 +1,12 @@
+// Looking at ScriptSourceObjects in invisible-to-debugger compartments is okay.
+
+var gi = newGlobal({ invisibleToDebugger: true });
+gi.eval('function f() {}');
+
+var gv = newGlobal();
+gv.f = gi.f;
+gv.eval('f = clone(f);');
+
+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-sourceMapURL-deprecated.js b/js/src/jit-test/tests/debug/Source-sourceMapURL-deprecated.js
new file mode 100644
index 000000000..f3445e101
--- /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();
+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.js b/js/src/jit-test/tests/debug/Source-sourceMapURL.js
new file mode 100644
index 000000000..3bfe0aa97
--- /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();
+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-surfaces.js b/js/src/jit-test/tests/debug/Source-surfaces.js
new file mode 100644
index 000000000..f2b3e81d9
--- /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 000000000..b147d7103
--- /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();
+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 000000000..64cfce92a
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-text-02.js
@@ -0,0 +1,19 @@
+// Nested compilation units (say, an eval with in an eval) should have the
+// correct sources attributed to them.
+let g = newGlobal();
+let dbg = new Debugger(g);
+
+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 000000000..21896b212
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-text-lazy.js
@@ -0,0 +1,39 @@
+/*
+ * Debugger.Source.prototype.text should correctly retrieve the source for
+ * code compiled with CompileOptions::LAZY_SOURCE.
+ */
+
+// withSourceHook isn't defined if you pass the shell the --fuzzing-safe
+// option. Skip this test silently, to avoid spurious failures.
+if (typeof withSourceHook != 'function')
+ quit(0);
+
+let g = newGlobal();
+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.js b/js/src/jit-test/tests/debug/Source-url.js
new file mode 100644
index 000000000..02f822b3a
--- /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();
+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 000000000..6da5a3ff4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-01.js
@@ -0,0 +1,22 @@
+// Basic breakpoint test.
+
+var g = newGlobal();
+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 000000000..3f02a6c38
--- /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();
+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 000000000..c5fd986bc
--- /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();
+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 000000000..67d5e7e40
--- /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();
+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 000000000..217bb823a
--- /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();
+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 000000000..9a7f78670
--- /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();
+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 000000000..d7a168ba8
--- /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();
+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 000000000..b1011cb49
--- /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();
+
+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 000000000..cf3b81f4f
--- /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();
+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 000000000..cf42e9063
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-10.js
@@ -0,0 +1,19 @@
+var g = newGlobal();
+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 000000000..f29544095
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-11.js
@@ -0,0 +1,40 @@
+// 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();
+var dbg = Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+ function hit(frame) {
+ assertEq(frame.generator, true);
+ assertEq(frame.older.generator, true);
+ 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 000000000..54f48b7c6
--- /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();
+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();
+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 000000000..f054fb535
--- /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();
+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 000000000..98196ee9b
--- /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();
+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 000000000..db454ced1
--- /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();
+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 000000000..09b1f846c
--- /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();
+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 000000000..121bc656a
--- /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();
+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 000000000..0639f0269
--- /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();
+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 000000000..d0afdd5c7
--- /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();
+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 000000000..d75afce47
--- /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();
+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 000000000..3a87be938
--- /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();
+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 000000000..2dba62615
--- /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();
+ 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 000000000..7a45fa2af
--- /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();
+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-resume-01.js b/js/src/jit-test/tests/debug/breakpoint-resume-01.js
new file mode 100644
index 000000000..932f4d5f9
--- /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();
+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 000000000..397e2ca3a
--- /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();
+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 000000000..e57ecc6fe
--- /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();
+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/bug-1102549.js b/js/src/jit-test/tests/debug/bug-1102549.js
new file mode 100644
index 000000000..d7bb893f2
--- /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();
+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 000000000..4c1777a35
--- /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()).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 000000000..07d7fe1f5
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-1103813.js
@@ -0,0 +1,6 @@
+// Random chosen test: js/src/jit-test/tests/debug/Source-invisible.js
+newGlobal({
+ 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 000000000..dc79ff20c
--- /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());
+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 000000000..0e2a66fc0
--- /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()
+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 000000000..104e79b1e
--- /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 000000000..82bc233d7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-1192401.js
@@ -0,0 +1,5 @@
+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 000000000..369d7550d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-1238610.js
@@ -0,0 +1,27 @@
+// |jit-test| allow-oom
+
+if (!('oomAfterAllocations' in this))
+ quit();
+
+if (helperThreadCount() === 0)
+ quit(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 000000000..8749a03f3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-1240090.js
@@ -0,0 +1,24 @@
+gczeal(2);
+g = newGlobal();
+dbg = Debugger(g);
+dbg.onNewScript = function() function() this;
+schedulegc(10);
+g.eval("setLazyParsingDisabled(true)");
+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 000000000..1beee180d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-1248162.js
@@ -0,0 +1,14 @@
+// |jit-test| allow-oom
+
+if (typeof oomTest !== "function")
+ quit();
+
+// 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();
+})
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 000000000..7719c70b5
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-1260725.js
@@ -0,0 +1,12 @@
+// |jit-test| error: out of memory
+
+if (!('oomTest' in this))
+ throw new Error("out of memory");
+
+var dbg = new Debugger;
+dbg.onNewGlobalObject = function(global) {
+ dbg.memory.takeCensus({});
+};
+oomTest(function() {
+ newGlobal({})
+});
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 000000000..fbccb448e
--- /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-725733.js b/js/src/jit-test/tests/debug/bug-725733.js
new file mode 100644
index 000000000..bdd2f21e3
--- /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();
+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 000000000..a8a983c59
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-800586.js
@@ -0,0 +1,7 @@
+var g = newGlobal();
+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 000000000..f26ccba08
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-826669.js
@@ -0,0 +1,7 @@
+gczeal(9, 2)
+var g1 = newGlobal();
+var g2 = newGlobal();
+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 000000000..a45002a59
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug-858170.js
@@ -0,0 +1,7 @@
+var g = newGlobal();
+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 000000000..e66939f81
--- /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
+// JSCompartment::findOutgoingEdges()
+
+let g = newGlobal();
+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 000000000..1f0bc78fa
--- /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();\
+ 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 000000000..f7cbcfccf
--- /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 000000000..e9a289fa0
--- /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();
+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 000000000..bf4a1de3e
--- /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();\
+g.debuggeeGlobal = this;\
+g.eval(\"(\" + function () {\
+ dbg = new Debugger(debuggeeGlobal);\
+ } + \")();\");\
+");
+lfcode.push("gc();");
+lfcode.push("\
+var g = newGlobal();\
+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 000000000..48fd9129b
--- /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();\
+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 000000000..40759439e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1106164.js
@@ -0,0 +1,17 @@
+// |jit-test| error: ReferenceError
+var g = newGlobal();
+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 000000000..87eb3da94
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1106719.js
@@ -0,0 +1,11 @@
+// |jit-test| allow-oom; allow-overrecursed
+
+g = newGlobal()
+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 000000000..e80ba0f64
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1107525.js
@@ -0,0 +1,9 @@
+// |jit-test| error: InternalError
+enableSPSProfiling();
+var g = newGlobal();
+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 000000000..b1ba03ba2
--- /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 000000000..d1a84c7be
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1108159.js
@@ -0,0 +1,15 @@
+// |jit-test| slow
+
+if (helperThreadCount() == 0)
+ quit(0);
+
+var s = '';
+for (var i = 0; i < 70000; i++)
+ {
+ s += 'function x' + i + '() { x' + i + '(); }\n';
+}
+evaluate(s);
+var g = newGlobal();
+(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 000000000..2e399bc6d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1108556.js
@@ -0,0 +1,10 @@
+// |jit-test| error: ReferenceError
+
+var g = newGlobal();
+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 000000000..f369bde66
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1109328.js
@@ -0,0 +1,7 @@
+try {
+ gcslice(0)(""());
+} catch (e) {}
+g = newGlobal()
+g.parent = this
+g.eval("Debugger(parent).onExceptionUnwind=(function(){})");
+gcparam("maxBytes", gcparam("gcBytes"));
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 000000000..7a11215fa
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1109915.js
@@ -0,0 +1,17 @@
+var evalInFrame = (function (global) {
+ var dbgGlobal = newGlobal();
+ 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 000000000..42e708991
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1109964.js
@@ -0,0 +1,10 @@
+var dbgGlobal = newGlobal();
+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 000000000..30bce2e56
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1111199.js
@@ -0,0 +1,17 @@
+g = newGlobal()
+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 000000000..538419b03
--- /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 000000000..2c3fd0698
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1116103.js
@@ -0,0 +1,11 @@
+// |jit-test| error: ReferenceError
+
+evaluate(`
+ var g = newGlobal();
+ 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 000000000..91e6e8cb9
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1118878.js
@@ -0,0 +1,11 @@
+
+var evalInFrame = (function (global) {
+ var dbgGlobal = newGlobal();
+ 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 000000000..ef4ff69c8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1121083.js
@@ -0,0 +1,16 @@
+// |jit-test| error:terminated
+options('werror');
+
+g = newGlobal();
+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/bug1130756.js b/js/src/jit-test/tests/debug/bug1130756.js
new file mode 100644
index 000000000..24664f7fc
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1130756.js
@@ -0,0 +1,28 @@
+// |jit-test| error: timeout
+
+options('werror');
+
+var g = newGlobal();
+g.parent = this;
+g.eval("(" + function() {
+var dbg = Debugger(parent);
+var handler = {hit: function() {}};
+
+dbg.onEnterFrame = function(frame) {
+ frame.onStep = function() {}
+}
+} + ")()");
+
+g = newGlobal();
+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 000000000..4c16a3132
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1130768.js
@@ -0,0 +1,12 @@
+// |jit-test| error:foo
+var g = newGlobal();
+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 000000000..60d963a8b
--- /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 000000000..a2bd2cf79
--- /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 000000000..2645f913f
--- /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 000000000..a1b732894
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1160182.js
@@ -0,0 +1,11 @@
+var g = newGlobal();
+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 000000000..2b8f37149
--- /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 000000000..a5c0a77c4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1188334.js
@@ -0,0 +1,18 @@
+var evalInFrame = (function (global) {
+ var dbgGlobal = newGlobal();
+ 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 000000000..07086c4cb
--- /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();
+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 000000000..71f11fe80
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1216261.js
@@ -0,0 +1,15 @@
+// |jit-test| exitstatus: 3
+
+if (!('oomAfterAllocations' in this))
+ quit(3);
+
+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 000000000..d7bc83527
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1219905.js
@@ -0,0 +1,14 @@
+// |jit-test| allow-oom
+
+// 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.
+
+if (!('oomTest' in this))
+ quit();
+
+var g = newGlobal();
+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 000000000..d8e6781df
--- /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 000000000..b55acdc85
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1232655.js
@@ -0,0 +1,5 @@
+g = newGlobal();
+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 000000000..58efe086e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1240546.js
@@ -0,0 +1,12 @@
+// |jit-test| allow-oom
+
+if (!('oomAfterAllocations' in this))
+ quit();
+
+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 000000000..84f6d17e5
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1240803.js
@@ -0,0 +1,24 @@
+// |jit-test| allow-oom
+
+
+if (!('oomAfterAllocations' in this))
+ quit();
+
+(function() {
+ g = newGlobal()
+ dbg = new Debugger
+ g.toggle = function(d) {
+ if (d) {
+ dbg.addDebuggee(g);
+ dbg.getNewestFrame();
+ oomAfterAllocations(2);
+ setBreakpoint;
+ }
+ }
+ g.eval("" + function f(d) 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 000000000..4b365ad5f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1242111.js
@@ -0,0 +1,11 @@
+// |jit-test| allow-oom
+
+if (!('oomAfterAllocations' in this))
+ quit();
+
+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 000000000..8e4b74b22
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1242798.js
@@ -0,0 +1,14 @@
+
+var g = newGlobal();
+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 000000000..8cfeafbbe
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1245862.js
@@ -0,0 +1,25 @@
+// |jit-test| allow-oom
+
+if (!('oomAfterAllocations' in this))
+ quit();
+
+var g = newGlobal();
+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 000000000..cac1fa186
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1246605.js
@@ -0,0 +1,13 @@
+g = newGlobal();
+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 000000000..188bfa393
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1251919.js
@@ -0,0 +1,13 @@
+// |jit-test| error: out of memory
+
+if (!('oomTest' in this))
+ throw new Error("out of memory");
+
+// 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();
+})
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 000000000..79f56528f
--- /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) 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 000000000..48c6f8ec0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1252464.js
@@ -0,0 +1,15 @@
+// |jit-test| error: ReferenceError
+
+g = newGlobal();
+dbg = Debugger(g);
+hits = 0;
+dbg.onNewScript = function () 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 000000000..440959a8c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1253246.js
@@ -0,0 +1,5 @@
+var g = newGlobal();
+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 000000000..51b2c8940
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1254123.js
@@ -0,0 +1,17 @@
+// |jit-test| error: boom
+
+if (!('oomTest' in this))
+ throw new Error("boom");
+
+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();
+})
+`);
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 000000000..0d95ce76f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1254190.js
@@ -0,0 +1,15 @@
+// |jit-test| error: out of memory; slow;
+
+if (!('oomTest' in this))
+ throw new Error("out of memory");
+
+var g = newGlobal();
+var dbg = new Debugger(g);
+dbg.onNewScript = function (s) {
+ log += dbg.findScripts({ source: s.source }).length;
+}
+log = "";
+oomTest(() => {
+ var static = newGlobal();
+ 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 000000000..828faf186
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1254578.js
@@ -0,0 +1,23 @@
+// |jit-test| error:ReferenceError; slow
+
+if (!('oomTest' in this))
+ throw (new ReferenceError);
+
+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;
+ };
+} + ")();");
+var dbg = new Debugger;
+dbg.onNewGlobalObject = function(global) {
+ get.seen = true;
+};
+oomTest(function() {
+ newGlobal({
+ })
+});
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 000000000..a6437b308
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1257045.js
@@ -0,0 +1,11 @@
+if (!wasmIsSupported())
+ quit();
+
+fullcompartmentchecks(true);
+var g = newGlobal();
+var dbg = new Debugger(g);
+dbg.onNewScript = (function(script) {
+ s = script;
+})
+g.eval(`new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (func) (export "" 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 000000000..89f121e83
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1263899.js
@@ -0,0 +1,29 @@
+try {
+ evaluate(`
+ function runTestCase() $ERROR()
+ function $ERROR() {
+ throw Error
+ }
+ Object.defineProperty(this, "x", { value: 0 });
+ setJitCompilerOption("ion.warmup.trigger", 0)
+ `)
+ evaluate(`function f() {} f(x)`)
+ runTestCase()
+} catch (exc) {}
+evaluate(`
+ g = newGlobal()
+ 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 000000000..c0ae7aaa4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1264961.js
@@ -0,0 +1,28 @@
+// |jit-test| slow;
+
+if (!('oomTest' in this))
+ quit();
+
+loadFile(`
+ var o = {}
+ var global = this;
+ var p = new Proxy(o, {
+ "deleteProperty": function (await , key) {
+ var g = newGlobal();
+ 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)
+ quit();
+ eval(lfVarx);
+ })
+}
+
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 000000000..7e4a7a08d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1266434.js
@@ -0,0 +1,8 @@
+var g = newGlobal();
+var dbg = new Debugger(g);
+var g = newGlobal();
+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 000000000..f4c573684
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1272908.js
@@ -0,0 +1,19 @@
+// |jit-test| error: out of memory; slow;
+
+// Adapted from randomly chosen test: js/src/jit-test/tests/modules/bug-1233915.js
+g = newGlobal();
+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();
+})
+
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 000000000..b8bdc5556
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1275001.js
@@ -0,0 +1,30 @@
+
+g = newGlobal();
+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.toString()
+ assertEq(s.slice(11, -err.length), expected)
+ }
+}
+ieval = eval
+function check(expr, expected = expr) {
+ var end, err
+ for ([end, err] of[[".random_prop", " is undefined" ]])
+ statement = "o = {};" + expr + end;
+ cases = [
+ function() 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 000000000..9f342f9cf
--- /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();
+ 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 000000000..4139c9772
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1299121.js
@@ -0,0 +1,10 @@
+var g = newGlobal();
+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 000000000..4170d5feb
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1300517.js
@@ -0,0 +1,12 @@
+// |jit-test| error: ReferenceError
+g = newGlobal();
+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 000000000..9078fdd74
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1300528.js
@@ -0,0 +1,34 @@
+load(libdir + "asserts.js");
+
+if (helperThreadCount() === 0)
+ quit(0);
+
+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();
+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 000000000..6d99326eb
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1302432.js
@@ -0,0 +1,10 @@
+setJitCompilerOption('ion.warmup.trigger', 0);
+gczeal(7, 1);
+var dbgGlobal = newGlobal();
+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/bug1308578.js b/js/src/jit-test/tests/debug/bug1308578.js
new file mode 100644
index 000000000..c44b546a9
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1308578.js
@@ -0,0 +1,10 @@
+// |jit-test| error: ReferenceError
+
+g = newGlobal();
+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/bug911065.js b/js/src/jit-test/tests/debug/bug911065.js
new file mode 100644
index 000000000..ad539ae3d
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug911065.js
@@ -0,0 +1,34 @@
+var g = newGlobal();
+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 000000000..93350368e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug967039.js
@@ -0,0 +1,6 @@
+var g1 = newGlobal();
+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 000000000..5f332f164
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug973566.js
@@ -0,0 +1,7 @@
+Object.prototype[1] = 'peek';
+var g = newGlobal();
+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 000000000..48988733c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug980585.js
@@ -0,0 +1,10 @@
+var g = newGlobal();
+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 000000000..0b9ce4d07
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug999655.js
@@ -0,0 +1,11 @@
+
+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.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 000000000..66f390bd4
--- /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 000000000..3821eea26
--- /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 000000000..da1a4f434
--- /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 000000000..5f772e4d3
--- /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 000000000..4af7c23f8
--- /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 000000000..fc6ebdbaa
--- /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 000000000..8613e737b
--- /dev/null
+++ b/js/src/jit-test/tests/debug/class-07.js
@@ -0,0 +1,21 @@
+// |jit-test| error: ReferenceError
+
+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 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 000000000..89c7ccc22
--- /dev/null
+++ b/js/src/jit-test/tests/debug/class-08.js
@@ -0,0 +1,13 @@
+let g = newGlobal();
+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/clear-old-analyses-01.js b/js/src/jit-test/tests/debug/clear-old-analyses-01.js
new file mode 100644
index 000000000..b566df17d
--- /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();
+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 000000000..0f8da178c
--- /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();
+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 000000000..223ab8c88
--- /dev/null
+++ b/js/src/jit-test/tests/debug/dispatch-01.js
@@ -0,0 +1,22 @@
+// Test removing hooks during dispatch.
+
+var g = newGlobal();
+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/dispatch-02.js b/js/src/jit-test/tests/debug/dispatch-02.js
new file mode 100644
index 000000000..549f68342
--- /dev/null
+++ b/js/src/jit-test/tests/debug/dispatch-02.js
@@ -0,0 +1,21 @@
+// Disabling a Debugger object causes events to stop being delivered to it
+// immediately, even if we're in the middle of dispatching.
+
+var g = newGlobal();
+var log;
+
+var arr = [];
+for (var i = 0; i < 4; i++) {
+ arr[i] = new Debugger(g);
+ arr[i].num = i;
+ arr[i].onDebuggerStatement = function () {
+ log += this.num;
+ // Disable them all.
+ for (var j = 0; j < arr.length; j++)
+ arr[j].enabled = false;
+ };
+}
+
+log = '';
+g.eval("debugger; debugger;");
+assertEq(log, '0');
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 000000000..0a05d19c7
--- /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();
+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 000000000..2f6d65d06
--- /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();
+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 000000000..bc46d46d4
--- /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();
+ 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 000000000..472820b3f
--- /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();
+ 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 000000000..ba0a3efa6
--- /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();
+ 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 000000000..87919ade2
--- /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();
+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 000000000..a77aa30ba
--- /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();
+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 000000000..4055650c7
--- /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();
+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 000000000..773c4ad8a
--- /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();
+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 000000000..8cc6dfe3f
--- /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();
+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 000000000..e31253d75
--- /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();
+ 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 000000000..69da384e4
--- /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());
+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 000000000..d2de5eab4
--- /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();
+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 000000000..9999ba6bc
--- /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();
+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 000000000..f124d4287
--- /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();
+ 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 000000000..742d2d95a
--- /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();
+ 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-compartment-01.js b/js/src/jit-test/tests/debug/gc-compartment-01.js
new file mode 100644
index 000000000..a22a73618
--- /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();
+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 000000000..7eeb14e78
--- /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();
+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/inspect-wrapped-promise.js b/js/src/jit-test/tests/debug/inspect-wrapped-promise.js
new file mode 100644
index 000000000..f27ab4172
--- /dev/null
+++ b/js/src/jit-test/tests/debug/inspect-wrapped-promise.js
@@ -0,0 +1,88 @@
+if (typeof Promise === "undefined")
+ quit(0);
+
+load(libdir + "asserts.js");
+
+let g = newGlobal();
+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/makeGlobalObjectReference-01.js b/js/src/jit-test/tests/debug/makeGlobalObjectReference-01.js
new file mode 100644
index 000000000..85f958777
--- /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();
+let dbg = new Debugger;
+assertEq(dbg.hasDebuggee(g1), false);
+
+let g1w = dbg.makeGlobalObjectReference(g1);
+assertEq(dbg.hasDebuggee(g1), false);
+assertEq(g1w.unsafeDereference(), g1);
+assertEq(g1w, g1w.makeDebuggeeValue(g1));
+
+assertEq(dbg.addDebuggee(g1w), g1w);
+assertEq(dbg.hasDebuggee(g1), true);
+assertEq(dbg.hasDebuggee(g1w), true);
+assertEq(g1w.unsafeDereference(), g1);
+assertEq(g1w, g1w.makeDebuggeeValue(g1));
+
+// makeGlobalObjectReference dereferences CCWs.
+let g2 = newGlobal();
+g2.g1 = g1;
+let g2w = dbg.addDebuggee(g2);
+let g2g1w = g2w.getOwnPropertyDescriptor('g1').value;
+assertEq(g2g1w !== g1w, true);
+assertEq(g2g1w.unwrap(), g1w);
+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 000000000..a3db8e45e
--- /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 000000000..4c5685d30
--- /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({ 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 000000000..993342141
--- /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();
+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 000000000..2a8dfef97
--- /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();
+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 000000000..5ee15866f
--- /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();
+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 000000000..f769f04d8
--- /dev/null
+++ b/js/src/jit-test/tests/debug/noExecute-04.js
@@ -0,0 +1,43 @@
+// Tests that NX works through the enabled toggle and adding/removing the
+// global.
+
+load(libdir + "asserts.js");
+load(libdir + "debuggerNXHelper.js");
+
+var g = newGlobal();
+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);
+ dbg.enabled = false;
+ h();
+ dbg.enabled = true;
+ 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 000000000..93877c958
--- /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();
+var g2 = newGlobal();
+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 000000000..50b0be2c6
--- /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();
+var g2 = newGlobal();
+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 000000000..828c26fd0
--- /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();
+var g2 = newGlobal();
+var g3 = newGlobal();
+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 000000000..524072072
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onDebuggerStatement-01.js
@@ -0,0 +1,7 @@
+var g = newGlobal();
+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 000000000..3b3598947
--- /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();
+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();
+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 000000000..0a5525efe
--- /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();
+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 000000000..1bdaa65de
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onDebuggerStatement-04.js
@@ -0,0 +1,10 @@
+var g = newGlobal();
+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 000000000..0a05d19c7
--- /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();
+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/onEnterFrame-01.js b/js/src/jit-test/tests/debug/onEnterFrame-01.js
new file mode 100644
index 000000000..dbcce8935
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-01.js
@@ -0,0 +1,29 @@
+// Basic enterFrame hook tests.
+
+var g = newGlobal();
+var dbg = Debugger(g);
+var type;
+dbg.onEnterFrame = function (frame) {
+ try {
+ assertEq(frame instanceof Debugger.Frame, true);
+ assertEq(frame.live, 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 000000000..99b2841ca
--- /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();
+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 000000000..5b6bbb21e
--- /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();
+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-04.js b/js/src/jit-test/tests/debug/onEnterFrame-04.js
new file mode 100644
index 000000000..21f1a6cb3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-04.js
@@ -0,0 +1,50 @@
+// We detect and stop the runaway recursion caused by making onEnterFrame a
+// wrapper of a debuggee function.
+
+// This is all a bit silly. In any reasonable design, both debugger re-entry
+// (the second onEnterFrame invocation) and debuggee re-entry (the call to g.f
+// from within the debugger, not via a Debugger invocation function) would raise
+// errors immediately. We have plans to do so, but in the mean time, we settle
+// for at least detecting the recursion.
+
+load(libdir + 'asserts.js');
+
+var g = newGlobal();
+g.eval("function f(frame) { n++; return 42; }");
+g.n = 0;
+
+var dbg = Debugger();
+var gw = dbg.addDebuggee(g);
+
+// Register the debuggee function as the onEnterFrame handler. When we first
+// call or eval in the debuggee:
+//
+// - The onEnterFrame call reporting that frame's creation is itself an event
+// that must be reported, so we call onEnterFrame again.
+//
+// - SpiderMonkey detects the out-of-control recursion, and generates a "too
+// much recursion" InternalError in the youngest onEnterFrame call.
+//
+// - We don't catch it, so the onEnterFrame handler call itself throws.
+//
+// - Since the Debugger doesn't have an uncaughtExceptionHook (it can't; such a
+// hook would itself raise a "too much recursion" exception), Spidermonkey
+// reports the exception immediately and terminates the debuggee --- which is
+// the next-older onEnterFrame call.
+//
+// - This termination propagates all the way out to the initial attempt to
+// create a frame in the debuggee.
+dbg.onEnterFrame = g.f;
+
+// Get a Debugger.Object instance referring to f.
+var debuggeeF = gw.makeDebuggeeValue(g.f);
+
+// Using f.call allows us to catch the termination.
+assertEq(debuggeeF.call(), null);
+
+// We should never actually begin execution of the function.
+assertEq(g.n, 0);
+
+// When an error is reported, the shell usually exits with a nonzero exit code.
+// If we get here, the test passed, so override that behavior.
+quit(0);
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 000000000..6fadf3194
--- /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();
+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 000000000..9d097691c
--- /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();
+var g2 = newGlobal();
+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 000000000..ce4316bab
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-07.js
@@ -0,0 +1,15 @@
+var g = newGlobal();
+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/onExceptionUnwind-01.js b/js/src/jit-test/tests/debug/onExceptionUnwind-01.js
new file mode 100644
index 000000000..7695d0ada
--- /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();
+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 000000000..3907d5fc5
--- /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();
+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 000000000..913b1d2fe
--- /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();
+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 000000000..443499a35
--- /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();
+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 000000000..d2032dd45
--- /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();
+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 000000000..bab342dd1
--- /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();
+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 000000000..53d41c298
--- /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();
+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 000000000..0c2752ee2
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-08.js
@@ -0,0 +1,18 @@
+// Ensure that ScriptDebugEpilogue gets called when onExceptionUnwind
+// throws an uncaught exception.
+var g = newGlobal();
+var dbg = Debugger(g);
+var frame;
+dbg.onExceptionUnwind = function (f, x) {
+ frame = f;
+ assertEq(frame.live, true);
+ throw 'unhandled';
+};
+dbg.onDebuggerStatement = function(f) {
+ assertEq(f.eval('throw 42'), null);
+ assertEq(frame.live, 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 000000000..eb4755697
--- /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();
+var dbg = Debugger(g);
+var frame;
+dbg.onExceptionUnwind = function (f, x) {
+ frame = f;
+ assertEq(frame.live, true);
+ return null;
+};
+dbg.onDebuggerStatement = function(f) {
+ assertEq(f.eval('throw 42'), null);
+ assertEq(frame.live, 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 000000000..b5dd59df9
--- /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();
+var dbg = Debugger(g);
+var frame;
+dbg.onExceptionUnwind = function (f, x) {
+ frame = f;
+ assertEq(frame.type, 'eval');
+ assertEq(frame.live, true);
+ terminate();
+};
+dbg.onDebuggerStatement = function(f) {
+ assertEq(f.eval('throw 42'), null);
+ assertEq(frame.live, false);
+};
+g.eval('debugger');
diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-11.js b/js/src/jit-test/tests/debug/onExceptionUnwind-11.js
new file mode 100644
index 000000000..f5eb15df7
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-11.js
@@ -0,0 +1,29 @@
+// Closing legacy generators should not invoke the onExceptionUnwind hook.
+
+var g = newGlobal();
+var dbg = Debugger(g);
+dbg.onExceptionUnwind = function (frame, exc) {
+ log += "ERROR";
+ assertEq(0, 1);
+};
+g.eval(`
+var log = "";
+function f() {
+ function gen() {
+ try {
+ log += "yield";
+ yield 3;
+ yield 4;
+ } catch(e) {
+ log += "catch";
+ } finally {
+ log += "finally";
+ }
+ };
+ var it = gen();
+ assertEq(it.next(), 3);
+ it.close();
+};
+f();
+`);
+assertEq(g.log, "yieldfinally");
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 000000000..78874ddea
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-12.js
@@ -0,0 +1,14 @@
+var g = newGlobal();
+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 000000000..611bf4c9d
--- /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();
+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 000000000..121f5728e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-14.js
@@ -0,0 +1,23 @@
+var g = newGlobal();
+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 000000000..0c6f46f23
--- /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();
+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 new throwInNext);
+}
+
+var log = "";
+try {
+ f();
+} catch (e) {
+ log += e;
+}
+
+assertEq(log, "42");
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 000000000..946490edf
--- /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();
+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 000000000..0e2750b0c
--- /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();
+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 000000000..b507e0f48
--- /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();
+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 000000000..8c195aadc
--- /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();
+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-async.js b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-async.js
new file mode 100644
index 000000000..d4e7e8576
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-async.js
@@ -0,0 +1,130 @@
+load(libdir + "asserts.js");
+
+var g = newGlobal();
+var dbg = Debugger(g);
+
+g.eval(`
+async function f() {
+ return e;
+}
+`);
+
+// To continue testing after uncaught exception, remember the exception and
+// return normal completeion.
+var currentFrame;
+var uncaughtException;
+dbg.uncaughtExceptionHook = function(e) {
+ uncaughtException = e;
+ return {
+ return: currentFrame.eval("({ done: true, value: 'uncaught' })").return
+ };
+};
+function testUncaughtException() {
+ uncaughtException = undefined;
+ var val = g.eval(`
+var val;
+f().then(v => { val = v });
+drainJobQueue();
+val;
+`);
+ assertEq(val, "uncaught");
+ assertEq(uncaughtException instanceof TypeError, true);
+}
+
+// Just continue
+dbg.onExceptionUnwind = function(frame) {
+ return undefined;
+};
+g.eval(`
+var E;
+f().catch(e => { exc = e });
+drainJobQueue();
+assertEq(exc instanceof ReferenceError, true);
+`);
+
+// Should return object.
+dbg.onExceptionUnwind = function(frame) {
+ currentFrame = frame;
+ return {
+ return: "foo"
+ };
+};
+testUncaughtException();
+
+// The object should have `done` property and `value` property.
+dbg.onExceptionUnwind = function(frame) {
+ currentFrame = frame;
+ return {
+ return: frame.eval("({})").return
+ };
+};
+testUncaughtException();
+
+// The object should have `done` property.
+dbg.onExceptionUnwind = function(frame) {
+ currentFrame = frame;
+ return {
+ return: frame.eval("({ value: 10 })").return
+ };
+};
+testUncaughtException();
+
+// The object should have `value` property.
+dbg.onExceptionUnwind = function(frame) {
+ currentFrame = frame;
+ return {
+ return: frame.eval("({ done: true })").return
+ };
+};
+testUncaughtException();
+
+// `done` property should be a boolean value.
+dbg.onExceptionUnwind = function(frame) {
+ currentFrame = frame;
+ return {
+ return: frame.eval("({ done: 10, value: 10 })").return
+ };
+};
+testUncaughtException();
+
+// `done` property shouldn't be an accessor.
+dbg.onExceptionUnwind = function(frame) {
+ currentFrame = frame;
+ return {
+ return: frame.eval("({ get done() { return true; }, value: 10 })").return
+ };
+};
+testUncaughtException();
+
+// `value` property shouldn't be an accessor.
+dbg.onExceptionUnwind = function(frame) {
+ currentFrame = frame;
+ return {
+ return: frame.eval("({ done: true, get value() { return 10; } })").return
+ };
+};
+testUncaughtException();
+
+// The object shouldn't be a Proxy.
+dbg.onExceptionUnwind = function(frame) {
+ currentFrame = frame;
+ return {
+ return: frame.eval("new Proxy({ done: true, value: 10 }, {})").return
+ };
+};
+testUncaughtException();
+
+// Correct resumption value.
+dbg.onExceptionUnwind = function(frame) {
+ currentFrame = frame;
+ return {
+ return: frame.eval("({ done: true, value: 10 })").return
+ };
+};
+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 000000000..6239d9e7e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-generator.js
@@ -0,0 +1,117 @@
+load(libdir + "asserts.js");
+
+var g = newGlobal();
+var dbg = Debugger(g);
+
+g.eval(`
+function* f() {
+ e;
+}
+`);
+
+// To continue testing after uncaught exception, remember the exception and
+// return normal completeion.
+var currentFrame;
+var uncaughtException;
+dbg.uncaughtExceptionHook = function(e) {
+ uncaughtException = e;
+ return {
+ return: currentFrame.eval("({ done: true, value: 'uncaught' })").return
+ };
+};
+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);
+
+// Should return object.
+dbg.onExceptionUnwind = function(frame) {
+ currentFrame = frame;
+ return {
+ return: "foo"
+ };
+};
+testUncaughtException();
+
+// The object should have `done` property and `value` property.
+dbg.onExceptionUnwind = function(frame) {
+ currentFrame = frame;
+ return {
+ return: frame.eval("({})").return
+ };
+};
+testUncaughtException();
+
+// The object should have `done` property.
+dbg.onExceptionUnwind = function(frame) {
+ currentFrame = frame;
+ return {
+ return: frame.eval("({ value: 10 })").return
+ };
+};
+testUncaughtException();
+
+// The object should have `value` property.
+dbg.onExceptionUnwind = function(frame) {
+ currentFrame = frame;
+ return {
+ return: frame.eval("({ done: true })").return
+ };
+};
+testUncaughtException();
+
+// `done` property should be a boolean value.
+dbg.onExceptionUnwind = function(frame) {
+ currentFrame = frame;
+ return {
+ return: frame.eval("({ done: 10, value: 10 })").return
+ };
+};
+testUncaughtException();
+
+// `done` property shouldn't be an accessor.
+dbg.onExceptionUnwind = function(frame) {
+ currentFrame = frame;
+ return {
+ return: frame.eval("({ get done() { return true; }, value: 10 })").return
+ };
+};
+testUncaughtException();
+
+// `value` property shouldn't be an accessor.
+dbg.onExceptionUnwind = function(frame) {
+ currentFrame = frame;
+ return {
+ return: frame.eval("({ done: true, get value() { return 10; } })").return
+ };
+};
+testUncaughtException();
+
+// The object shouldn't be a Proxy.
+dbg.onExceptionUnwind = function(frame) {
+ currentFrame = frame;
+ return {
+ return: frame.eval("new Proxy({ done: true, value: 10 }, {})").return
+ };
+};
+testUncaughtException();
+
+// Correct resumption value.
+dbg.onExceptionUnwind = function(frame) {
+ currentFrame = frame;
+ return {
+ return: frame.eval("({ done: true, value: 10 })").return
+ };
+};
+var obj = g.eval(`f().next()`);
+assertEq(obj.done, true);
+assertEq(obj.value, 10);
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 000000000..230584859
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onNewScript-01.js
@@ -0,0 +1,45 @@
+// Basic newScript hook tests.
+
+var g = newGlobal();
+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
+fn = g.evaluate("(function(a) { return 5 + a; })");
+var g2 = newGlobal();
+dbg.addDebuggee(g2, dbg);
+hits = 0;
+g2.clone(fn);
+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 000000000..b9f248bd9
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onNewScript-02.js
@@ -0,0 +1,65 @@
+// Creating a new script with any number of subscripts triggers the newScript hook exactly once.
+
+var g = newGlobal();
+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 () { g.clone(evaluate("(function(x) { return x + 1; })")); });
+
+// eval declaring a generator
+test(function () { g.eval("function r(n) { for (var i=0;i<n;i++) yield i; }"); });
+
+// 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 000000000..1471c3721
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onNewScript-03.js
@@ -0,0 +1,7 @@
+var g = newGlobal();
+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 000000000..9e56ce920
--- /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();
+var g2 = newGlobal();
+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 000000000..7fb6ebf3a
--- /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();
+var g2 = newGlobal();
+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 000000000..090c8d6c3
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onNewScript-off-main-thread-01.js
@@ -0,0 +1,18 @@
+// We still get onNewScript notifications for code compiled off the main thread.
+
+if (helperThreadCount() === 0)
+ quit(0);
+
+var g = newGlobal();
+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 000000000..f4dd96e3f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onNewScript-off-main-thread-02.js
@@ -0,0 +1,13 @@
+if (helperThreadCount() === 0)
+ quit(0);
+
+var global = newGlobal();
+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/optimized-out-01.js b/js/src/jit-test/tests/debug/optimized-out-01.js
new file mode 100644
index 000000000..da5d83efa
--- /dev/null
+++ b/js/src/jit-test/tests/debug/optimized-out-01.js
@@ -0,0 +1,44 @@
+// Tests that we can reflect optimized out values.
+//
+// Unfortunately these tests are brittle. They depend on opaque JIT heuristics
+// kicking in.
+
+load(libdir + "jitopts.js");
+
+if (!jitTogglesMatch(Opts_Ion2NoOffthreadCompilation))
+ quit(0);
+
+withJitOptions(Opts_Ion2NoOffthreadCompilation, function () {
+ var g = newGlobal();
+ 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 < 200; i++);
+ toggle(d);
+ });
+
+ g.eval("(" + function test() {
+ for (i = 0; i < 5; 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 000000000..cd4b89864
--- /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();
+var dbg = new Debugger(g);
+
+g.eval(`
+function outer(unaliasedArg) {
+ var unaliasedVar = unaliasedArg + 42;
+ var aliasedVar = unaliasedArg;
+
+ inner();
+ return;
+
+ 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 000000000..e45285dfa
--- /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();
+ 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 () {
+ function a() {}
+ for (var i = 0; i < 1; i++) f();
+ })();
+
+ // Test optimized out binding in block scope.
+ (function () {
+ {
+ function a() {}
+ for (var i = 0; i < 1; i++) f();
+ }
+ })();
+});
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 000000000..1702eeb92
--- /dev/null
+++ b/js/src/jit-test/tests/debug/prologueFailure-01.js
@@ -0,0 +1,32 @@
+g = newGlobal();
+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 000000000..d8c6e38ca
--- /dev/null
+++ b/js/src/jit-test/tests/debug/prologueFailure-02.js
@@ -0,0 +1,49 @@
+g = newGlobal();
+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 000000000..c3f8aa018
--- /dev/null
+++ b/js/src/jit-test/tests/debug/prologueFailure-03.js
@@ -0,0 +1,26 @@
+g = newGlobal();
+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/resumption-01.js b/js/src/jit-test/tests/debug/resumption-01.js
new file mode 100644
index 000000000..a2e04edb7
--- /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();
+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 000000000..8a8481dbb
--- /dev/null
+++ b/js/src/jit-test/tests/debug/resumption-02.js
@@ -0,0 +1,9 @@
+// Simple {return:} resumption.
+
+var g = newGlobal();
+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 000000000..cca96a080
--- /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();
+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-04.js b/js/src/jit-test/tests/debug/resumption-04.js
new file mode 100644
index 000000000..1309871ea
--- /dev/null
+++ b/js/src/jit-test/tests/debug/resumption-04.js
@@ -0,0 +1,19 @@
+// |jit-test| error: already executing generator
+// Forced return from a generator frame.
+
+var g = newGlobal();
+g.debuggeeGlobal = this;
+g.eval("var dbg = new Debugger(debuggeeGlobal);" +
+ "dbg.onDebuggerStatement = function () { return {return: '!'}; };");
+
+function gen() {
+ yield '1';
+ debugger; // Force return here. The value is ignored.
+ yield '2';
+}
+
+var iter = gen();
+assertEq(iter.next(), "1");
+assertEq(iter.next(), "!");
+iter.next();
+assertEq(0, 1);
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 000000000..3c790ad32
--- /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();
+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-06.js b/js/src/jit-test/tests/debug/resumption-06.js
new file mode 100644
index 000000000..61e63f8ac
--- /dev/null
+++ b/js/src/jit-test/tests/debug/resumption-06.js
@@ -0,0 +1,21 @@
+// |jit-test| error: already executing generator
+// Forced return from a star generator frame.
+
+load(libdir + 'asserts.js')
+load(libdir + 'iteration.js')
+
+var g = newGlobal();
+g.debuggeeGlobal = this;
+g.eval("var dbg = new Debugger(debuggeeGlobal);" +
+ "dbg.onDebuggerStatement = function (frame) { return { return: frame.eval(\"({ done: true, value: '!' })\").return }; };");
+
+function* gen() {
+ yield '1';
+ debugger; // Force return here. The value is ignored.
+ yield '2';
+}
+var iter = gen();
+assertIteratorNext(iter, '1');
+assertIteratorDone(iter, '!');
+iter.next();
+assertEq(0, 1);
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 000000000..419befdb2
--- /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();
+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 000000000..06032ab22
--- /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();
+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-error-01.js b/js/src/jit-test/tests/debug/resumption-error-01.js
new file mode 100644
index 000000000..76e2717ce
--- /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();
+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 000000000..be5bf54b9
--- /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();
+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/surfaces-01.js b/js/src/jit-test/tests/debug/surfaces-01.js
new file mode 100644
index 000000000..0907916a4
--- /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 000000000..f656fdd3a
--- /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();
+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 000000000..bbc11e122
--- /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();
+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 000000000..1c696293f
--- /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();
+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 000000000..43472fc7f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/testEarlyReturnOnCall.js
@@ -0,0 +1,24 @@
+var g = newGlobal();
+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 000000000..5be42dd7c
--- /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();
+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 000000000..e660c9a18
--- /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();
+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 000000000..d9dc50676
--- /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();
+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 000000000..b31c0972a
--- /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();
+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 000000000..a7ffeaf8f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/uncaughtExceptionHook-resumption-02.js
@@ -0,0 +1,25 @@
+// uncaughtExceptionHook resumption value other than undefined causes further
+// hooks to be skipped.
+
+var g = newGlobal();
+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, "012");
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 000000000..85240f5d9
--- /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();
+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 000000000..1b2908d0f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-01.js
@@ -0,0 +1,33 @@
+// Tests that wasm module scripts are available via findScripts.
+
+if (!wasmIsSupported())
+ quit();
+
+var g = newGlobal();
+g.eval(`o = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (func) (export "" 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" 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 000000000..e6b591b58
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-02.js
@@ -0,0 +1,22 @@
+// Tests that wasm module scripts are available via onNewScript.
+
+if (!wasmIsSupported())
+ quit();
+
+var g = newGlobal();
+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 "" 0))')));`);
+assertEq(gotScript.format, "wasm");
+
+var gotScript2 = gotScript;
+g.eval(`o = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (func) (export "a" 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 000000000..03e96bbf1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-03.js
@@ -0,0 +1,36 @@
+// Tests that wasm module scripts have synthesized sources.
+
+load(libdir + "asserts.js");
+
+if (!wasmIsSupported())
+ quit();
+
+var g = newGlobal();
+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 "" 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 000000000..4fb9cd7d6
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-04.js
@@ -0,0 +1,34 @@
+// Tests that wasm module scripts throw for everything except text.
+
+load(libdir + "asserts.js");
+
+if (!wasmIsSupported())
+ quit();
+
+var g = newGlobal();
+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 "" 0))')));`);
+assertEq(s.format, "wasm");
+
+assertThrowsInstanceOf(() => s.displayName, Error);
+assertThrowsInstanceOf(() => s.url, Error);
+assertThrowsInstanceOf(() => s.startLine, Error);
+assertThrowsInstanceOf(() => s.lineCount, Error);
+assertThrowsInstanceOf(() => s.sourceStart, Error);
+assertThrowsInstanceOf(() => s.sourceLength, Error);
+assertThrowsInstanceOf(() => s.global, Error);
+assertThrowsInstanceOf(() => s.getChildScripts(), Error);
+assertThrowsInstanceOf(() => s.getAllOffsets(), Error);
+assertThrowsInstanceOf(() => s.getAllColumnOffsets(), Error);
+assertThrowsInstanceOf(() => s.setBreakpoint(0, { hit: () => {} }), Error);
+assertThrowsInstanceOf(() => s.getBreakpoint(0), Error);
+assertThrowsInstanceOf(() => s.clearBreakpoint({}), Error);
+assertThrowsInstanceOf(() => s.clearAllBreakpoints(), Error);
+assertThrowsInstanceOf(() => s.isInCatchScope(0), Error);
+assertThrowsInstanceOf(() => s.getOffsetsCoverage(), Error);
diff --git a/js/src/jit-test/tests/debug/wasm-05.js b/js/src/jit-test/tests/debug/wasm-05.js
new file mode 100644
index 000000000..e0262abe4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-05.js
@@ -0,0 +1,38 @@
+// Tests that wasm module scripts have text line to bytecode offset information
+// when source text is generated.
+
+load(libdir + "asserts.js");
+
+// Disabled in aurora (see also bug 1326452).
+quit();
+
+// Checking if experimental format generates internal source map to binary file
+// by querying debugger scripts getLineOffsets.
+// (Notice that the source map will not be produced by wasmBinaryToText)
+function getAllOffsets(wast) {
+ var sandbox = newGlobal('');
+ var dbg = new Debugger();
+ dbg.addDebuggee(sandbox);
+ 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];
+ var lines = wasmScript.source.text.split('\n');
+ return lines.map((l, n) => { return { str: l, offsets: wasmScript.getLineOffsets(n + 1) }; });
+}
+
+var result1 = getAllOffsets('(module \
+ (func (nop)) \
+ (func (drop (f32.sqrt (f32.add (f32.const 1.0) (f32.const 2.0))))) \
+)');
+
+var nopLine = result1.filter(i => i.str.indexOf('nop') >= 0);
+assertEq(nopLine.length, 1);
+// The nopLine shall have single offset.
+assertEq(nopLine[0].offsets.length, 1);
+assertEq(nopLine[0].offsets[0] > 0, true);
+
+var singleOffsetLines = result1.filter(i => i.offsets.length === 1);
+// There shall be total 6 lines with single offset.
+assertEq(singleOffsetLines.length === 6, true);