mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
New rule: a Debug object cannot be attached to a compartment that is not in debug mode. Includes a jsapi-test to check that we do not crash if you turn debug mode off while a Debug object is already attached. (This changeset moves all the Debug object tests under jit-tests because the jit-test runner lets tests ask for debug mode.)
--HG-- rename : js/src/tests/js1_8_5/extensions/debug-object-01.js => js/src/jit-test/tests/debug/debug-object-01.js rename : js/src/tests/js1_8_5/extensions/debug-object-02.js => js/src/jit-test/tests/debug/debug-object-02.js rename : js/src/tests/js1_8_5/extensions/debug-object-03.js => js/src/jit-test/tests/debug/debug-object-03.js rename : js/src/tests/js1_8_5/extensions/debug-object-04.js => js/src/jit-test/tests/debug/debug-object-04.js rename : js/src/tests/js1_8_5/extensions/debug-object-05.js => js/src/jit-test/tests/debug/debug-object-05.js rename : js/src/tests/js1_8_5/extensions/debug-object-06.js => js/src/jit-test/tests/debug/debug-object-06.js rename : js/src/tests/js1_8_5/extensions/debug-object-07.js => js/src/jit-test/tests/debug/debug-object-07.js rename : js/src/tests/js1_8_5/extensions/debug-object-08.js => js/src/jit-test/tests/debug/debug-object-08.js rename : js/src/tests/js1_8_5/extensions/debug-object-09.js => js/src/jit-test/tests/debug/debug-object-09.js rename : js/src/tests/js1_8_5/extensions/debug-object-10.js => js/src/jit-test/tests/debug/debug-object-10.js rename : js/src/tests/js1_8_5/extensions/debug-object-11.js => js/src/jit-test/tests/debug/debug-object-11.js rename : js/src/tests/js1_8_5/extensions/debug-object-12.js => js/src/jit-test/tests/debug/debug-object-12.js rename : js/src/tests/js1_8_5/extensions/debug-object-13.js => js/src/jit-test/tests/debug/debug-object-13.js rename : js/src/tests/js1_8_5/extensions/debug-object-14.js => js/src/jit-test/tests/debug/debug-object-14.js rename : js/src/tests/js1_8_5/extensions/debug-object-15.js => js/src/jit-test/tests/debug/debug-object-15.js rename : js/src/tests/js1_8_5/extensions/debug-object-16.js => js/src/jit-test/tests/debug/debug-object-16.js rename : js/src/tests/js1_8_5/extensions/debug-object-17.js => js/src/jit-test/tests/debug/debug-object-17.js rename : js/src/tests/js1_8_5/extensions/debug-object-18.js => js/src/jit-test/tests/debug/debug-object-18.js rename : js/src/tests/js1_8_5/extensions/debug-object-19.js => js/src/jit-test/tests/debug/debug-object-19.js rename : js/src/tests/js1_8_5/extensions/debug-object-20.js => js/src/jit-test/tests/debug/debug-object-20.js rename : js/src/tests/js1_8_5/extensions/debug-object-21.js => js/src/jit-test/tests/debug/debug-object-21.js
This commit is contained in:
parent
a39d2d024a
commit
4fcb55d0aa
@ -52,7 +52,7 @@ The general format in EBNF is:
|
|||||||
cookie ::= "|jit-test|"
|
cookie ::= "|jit-test|"
|
||||||
item ::= flag | attribute
|
item ::= flag | attribute
|
||||||
|
|
||||||
flag ::= "slow" | "allow-oom"
|
flag ::= "slow" | "allow-oom" | "valgrind" | "mjitalways" | "debug"
|
||||||
|
|
||||||
attribute ::= name ":" value
|
attribute ::= name ":" value
|
||||||
name ::= "TMFLAGS" | "error"
|
name ::= "TMFLAGS" | "error"
|
||||||
@ -66,6 +66,8 @@ The meaning of the items:
|
|||||||
slow Test runs slowly. Do not run if the --no-slow option is given.
|
slow Test runs slowly. Do not run if the --no-slow option is given.
|
||||||
allow-oom If the test runs out of memory, it counts as passing.
|
allow-oom If the test runs out of memory, it counts as passing.
|
||||||
valgrind Run test under valgrind.
|
valgrind Run test under valgrind.
|
||||||
|
mjitalways Run js with -a, whether --jitflags says to or not
|
||||||
|
debug Run js with -d, whether --jitflags says to or not
|
||||||
|
|
||||||
error The test should be considered to pass iff it throws the
|
error The test should be considered to pass iff it throws the
|
||||||
given JS exception.
|
given JS exception.
|
||||||
|
36
js/src/jit-test/lib/asserts.js
Normal file
36
js/src/jit-test/lib/asserts.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
|
||||||
|
if (typeof assertThrowsInstanceOf === 'undefined') {
|
||||||
|
var assertThrowsInstanceOf = function assertThrowsInstanceOf(f, ctor, msg) {
|
||||||
|
var fullmsg;
|
||||||
|
try {
|
||||||
|
f();
|
||||||
|
} catch (exc) {
|
||||||
|
if (exc instanceof ctor)
|
||||||
|
return;
|
||||||
|
fullmsg = "Assertion failed: expected exception " + ctor.name + ", got " + exc;
|
||||||
|
}
|
||||||
|
if (fullmsg === undefined)
|
||||||
|
fullmsg = "Assertion failed: expected exception " + ctor.name + ", no exception thrown";
|
||||||
|
if (msg !== undefined)
|
||||||
|
fullmsg += " - " + msg;
|
||||||
|
throw new Error(fullmsg);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof assertThrowsValue === 'undefined') {
|
||||||
|
var assertThrowsValue = function assertThrowsValue(f, val, msg) {
|
||||||
|
var fullmsg;
|
||||||
|
try {
|
||||||
|
f();
|
||||||
|
} catch (exc) {
|
||||||
|
if ((exc === val) === (val === val) && (val !== 0 || 1 / exc === 1 / val))
|
||||||
|
return;
|
||||||
|
fullmsg = "Assertion failed: expected exception " + val + ", got " + exc;
|
||||||
|
}
|
||||||
|
if (fullmsg === undefined)
|
||||||
|
fullmsg = "Assertion failed: expected exception " + val + ", no exception thrown";
|
||||||
|
if (msg !== undefined)
|
||||||
|
fullmsg += " - " + msg;
|
||||||
|
throw new Error(fullmsg);
|
||||||
|
};
|
||||||
|
}
|
17
js/src/jit-test/tests/debug/debug-object-01.js
Normal file
17
js/src/jit-test/tests/debug/debug-object-01.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// |jit-test| debug
|
||||||
|
|
||||||
|
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, "Debug", 1);
|
||||||
|
|
||||||
|
assertEq(Debug.prototype.constructor, Debug);
|
||||||
|
assertEq(Object.prototype.toString.call(Debug.prototype), "[object Debug]");
|
||||||
|
assertEq(Object.getPrototypeOf(Debug.prototype), Object.prototype);
|
36
js/src/jit-test/tests/debug/debug-object-02.js
Normal file
36
js/src/jit-test/tests/debug/debug-object-02.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// |jit-test| debug
|
||||||
|
|
||||||
|
load(libdir + 'asserts.js');
|
||||||
|
|
||||||
|
// Debug rejects arguments that aren't cross-compartment wrappers.
|
||||||
|
assertThrowsInstanceOf(function () { Debug(); }, TypeError);
|
||||||
|
assertThrowsInstanceOf(function () { Debug(null); }, TypeError);
|
||||||
|
assertThrowsInstanceOf(function () { Debug(true); }, TypeError);
|
||||||
|
assertThrowsInstanceOf(function () { Debug(42); }, TypeError);
|
||||||
|
assertThrowsInstanceOf(function () { Debug("bad"); }, TypeError);
|
||||||
|
assertThrowsInstanceOf(function () { Debug(function () {}); }, TypeError);
|
||||||
|
assertThrowsInstanceOf(function () { Debug(this); }, TypeError);
|
||||||
|
assertThrowsInstanceOf(function () { new Debug(); }, TypeError);
|
||||||
|
assertThrowsInstanceOf(function () { new Debug(null); }, TypeError);
|
||||||
|
assertThrowsInstanceOf(function () { new Debug(true); }, TypeError);
|
||||||
|
assertThrowsInstanceOf(function () { new Debug(42); }, TypeError);
|
||||||
|
assertThrowsInstanceOf(function () { new Debug("bad"); }, TypeError);
|
||||||
|
assertThrowsInstanceOf(function () { new Debug(function () {}); }, TypeError);
|
||||||
|
assertThrowsInstanceOf(function () { new Debug(this); }, TypeError);
|
||||||
|
|
||||||
|
// Very basic tests of Debug creation.
|
||||||
|
var g = newGlobal('new-compartment');
|
||||||
|
var dbg = new Debug(g);
|
||||||
|
assertEq(dbg instanceof Debug, true);
|
||||||
|
assertEq(Object.getPrototypeOf(dbg), Debug.prototype);
|
||||||
|
|
||||||
|
// The reverse.
|
||||||
|
var g2 = newGlobal('new-compartment');
|
||||||
|
g2.debuggeeGlobal = this;
|
||||||
|
g2.eval("var dbg = new Debug(debuggeeGlobal);");
|
||||||
|
assertEq(g2.eval("dbg instanceof Debug"), true);
|
||||||
|
|
||||||
|
// The Debug constructor from this compartment will not accept as its argument
|
||||||
|
// an Object from this compartment. Shenanigans won't fool the membrane.
|
||||||
|
g2.outer = this;
|
||||||
|
assertThrowsInstanceOf(function () { g2.eval("outer.Debug(outer.Object())"); }, TypeError);
|
@ -1,6 +1,4 @@
|
|||||||
// Any copyright is dedicated to the Public Domain.
|
// |jit-test| debug
|
||||||
// http://creativecommons.org/licenses/publicdomain/
|
|
||||||
|
|
||||||
var g = newGlobal('new-compartment');
|
var g = newGlobal('new-compartment');
|
||||||
g.log = '';
|
g.log = '';
|
||||||
|
|
||||||
@ -14,5 +12,3 @@ dbg.hooks = hooks;
|
|||||||
assertEq(dbg.hooks, hooks);
|
assertEq(dbg.hooks, hooks);
|
||||||
assertEq(g.eval("log += '1'; debugger; log += '2'; 3;"), 3);
|
assertEq(g.eval("log += '1'; debugger; log += '2'; 3;"), 3);
|
||||||
assertEq(g.log, '1!2');
|
assertEq(g.log, '1!2');
|
||||||
|
|
||||||
reportCompare(0, 0, 'ok');
|
|
@ -1,6 +1,4 @@
|
|||||||
// Any copyright is dedicated to the Public Domain.
|
// |jit-test| debug
|
||||||
// http://creativecommons.org/licenses/publicdomain/
|
|
||||||
|
|
||||||
// Activity in the debugger compartment should not trigger debug hooks.
|
// Activity in the debugger compartment should not trigger debug hooks.
|
||||||
|
|
||||||
var g = newGlobal('new-compartment');
|
var g = newGlobal('new-compartment');
|
||||||
@ -27,5 +25,3 @@ assertEq(hit, false, "debugger statement in debugger compartment eval code shoul
|
|||||||
var g2 = newGlobal('new-compartment');
|
var g2 = newGlobal('new-compartment');
|
||||||
g2.eval("debugger;");
|
g2.eval("debugger;");
|
||||||
assertEq(hit, false, "debugger statement in unrelated non-debuggee compartment should not hit");
|
assertEq(hit, false, "debugger statement in unrelated non-debuggee compartment should not hit");
|
||||||
|
|
||||||
reportCompare(0, 0, 'ok');
|
|
@ -1,6 +1,4 @@
|
|||||||
// Any copyright is dedicated to the Public Domain.
|
// |jit-test| debug
|
||||||
// http://creativecommons.org/licenses/publicdomain/
|
|
||||||
|
|
||||||
// A debugger statement in a debuggerHandler should not reenter.
|
// A debugger statement in a debuggerHandler should not reenter.
|
||||||
|
|
||||||
var g = newGlobal('new-compartment');
|
var g = newGlobal('new-compartment');
|
||||||
@ -16,5 +14,3 @@ dbg.hooks = {
|
|||||||
|
|
||||||
assertEq(g.eval("debugger; 7;"), 7);
|
assertEq(g.eval("debugger; 7;"), 7);
|
||||||
assertEq(calls, 1);
|
assertEq(calls, 1);
|
||||||
|
|
||||||
reportCompare(0, 0, 'ok');
|
|
18
js/src/jit-test/tests/debug/debug-object-06.js
Normal file
18
js/src/jit-test/tests/debug/debug-object-06.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// |jit-test| debug
|
||||||
|
// Simple {throw:} resumption.
|
||||||
|
|
||||||
|
load(libdir + "asserts.js");
|
||||||
|
|
||||||
|
var g = newGlobal('new-compartment');
|
||||||
|
|
||||||
|
var dbg = Debug(g);
|
||||||
|
dbg.hooks = {
|
||||||
|
debuggerHandler: function (stack) {
|
||||||
|
return {throw: "oops"};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assertThrowsValue(function () { g.eval("debugger;"); }, "oops");
|
||||||
|
|
||||||
|
g.eval("function f() { debugger; }");
|
||||||
|
assertThrowsValue(function () { g.f(); }, "oops");
|
@ -1,6 +1,4 @@
|
|||||||
// Any copyright is dedicated to the Public Domain.
|
// |jit-test| debug
|
||||||
// http://creativecommons.org/licenses/publicdomain/
|
|
||||||
|
|
||||||
// Simple {return:} resumption.
|
// Simple {return:} resumption.
|
||||||
|
|
||||||
var g = newGlobal('new-compartment');
|
var g = newGlobal('new-compartment');
|
||||||
@ -15,5 +13,3 @@ dbg.hooks = {
|
|||||||
assertEq(g.eval("debugger; false;"), 1234);
|
assertEq(g.eval("debugger; false;"), 1234);
|
||||||
g.eval("function f() { debugger; return 'bad'; }");
|
g.eval("function f() { debugger; return 'bad'; }");
|
||||||
assertEq(g.f(), 1234);
|
assertEq(g.f(), 1234);
|
||||||
|
|
||||||
reportCompare(0, 0, 'ok');
|
|
@ -1,6 +1,4 @@
|
|||||||
// Any copyright is dedicated to the Public Domain.
|
// |jit-test| debug
|
||||||
// http://creativecommons.org/licenses/publicdomain/
|
|
||||||
|
|
||||||
// When there are multiple debuggers, their hooks are called in order.
|
// When there are multiple debuggers, their hooks are called in order.
|
||||||
|
|
||||||
var g = newGlobal('new-compartment');
|
var g = newGlobal('new-compartment');
|
||||||
@ -38,5 +36,3 @@ arr[0].hooks = {
|
|||||||
log = '';
|
log = '';
|
||||||
assertEq(g.eval("debugger; 0;"), 1);
|
assertEq(g.eval("debugger; 0;"), 1);
|
||||||
assertEq(log, 'a');
|
assertEq(log, 'a');
|
||||||
|
|
||||||
reportCompare(0, 0, 'ok');
|
|
30
js/src/jit-test/tests/debug/debug-object-09.js
Normal file
30
js/src/jit-test/tests/debug/debug-object-09.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// |jit-test| debug
|
||||||
|
// Debug.prototype.hooks
|
||||||
|
|
||||||
|
load(libdir + 'asserts.js');
|
||||||
|
|
||||||
|
var g = newGlobal('new-compartment');
|
||||||
|
var dbg = new Debug(g);
|
||||||
|
gc(); // don't assert marking dbg.hooks
|
||||||
|
var h = dbg.hooks;
|
||||||
|
assertEq(typeof h, 'object');
|
||||||
|
assertEq(Object.getOwnPropertyNames(h).length, 0);
|
||||||
|
assertEq(Object.getPrototypeOf(h), Object.prototype);
|
||||||
|
|
||||||
|
assertThrowsInstanceOf(function () { dbg.hooks = null; }, TypeError);
|
||||||
|
assertThrowsInstanceOf(function () { dbg.hooks = "bad"; }, TypeError);
|
||||||
|
|
||||||
|
assertEq(Object.getOwnPropertyNames(dbg).length, 0);
|
||||||
|
var desc = Object.getOwnPropertyDescriptor(Debug.prototype, "hooks");
|
||||||
|
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(Debug.prototype); }, TypeError);
|
||||||
|
assertEq(desc.get.call(dbg), h);
|
||||||
|
|
||||||
|
assertThrowsInstanceOf(function () { desc.set(); }, TypeError);
|
||||||
|
assertThrowsInstanceOf(function () { desc.set.call(dbg); }, TypeError);
|
||||||
|
assertThrowsInstanceOf(function () { desc.set.call({}, {}); }, TypeError);
|
||||||
|
assertThrowsInstanceOf(function () { desc.set.call(Debug.prototype, {}); }, TypeError);
|
@ -1,6 +1,4 @@
|
|||||||
// Any copyright is dedicated to the Public Domain.
|
// |jit-test| debug
|
||||||
// http://creativecommons.org/licenses/publicdomain/
|
|
||||||
|
|
||||||
// If a hook is deleted after setHooks or overwritten with a primitive, it
|
// If a hook is deleted after setHooks or overwritten with a primitive, it
|
||||||
// simply isn't called.
|
// simply isn't called.
|
||||||
|
|
||||||
@ -21,5 +19,3 @@ assertEq(hit, false);
|
|||||||
dbg.hooks.debuggerHandler = function (stack) { hit = true; };
|
dbg.hooks.debuggerHandler = function (stack) { hit = true; };
|
||||||
g.eval("debugger;");
|
g.eval("debugger;");
|
||||||
assertEq(hit, true);
|
assertEq(hit, true);
|
||||||
|
|
||||||
reportCompare(0, 0, 'ok');
|
|
@ -1,6 +1,4 @@
|
|||||||
// Any copyright is dedicated to the Public Domain.
|
// |jit-test| debug
|
||||||
// http://creativecommons.org/licenses/publicdomain/
|
|
||||||
|
|
||||||
// Debuggers with enabled hooks should not be GC'd even if they are otherwise
|
// Debuggers with enabled hooks should not be GC'd even if they are otherwise
|
||||||
// unreachable.
|
// unreachable.
|
||||||
|
|
||||||
@ -20,5 +18,3 @@ f();
|
|||||||
gc(); gc(); gc();
|
gc(); gc(); gc();
|
||||||
g.eval("debugger;");
|
g.eval("debugger;");
|
||||||
assertEq(actual, expected);
|
assertEq(actual, expected);
|
||||||
|
|
||||||
reportCompare(0, 0, 'ok');
|
|
@ -1,6 +1,4 @@
|
|||||||
// Any copyright is dedicated to the Public Domain.
|
// |jit-test| debug
|
||||||
// http://creativecommons.org/licenses/publicdomain/
|
|
||||||
|
|
||||||
// Test adding hooks during dispatch. The behavior is deterministic and "nice",
|
// 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
|
// 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.
|
// modifying a data structure while we're iterating over it.
|
||||||
@ -35,5 +33,3 @@ assertEq(hits, 4);
|
|||||||
hits = 0;
|
hits = 0;
|
||||||
g.eval("debugger;");
|
g.eval("debugger;");
|
||||||
assertEq(hits, 8);
|
assertEq(hits, 8);
|
||||||
|
|
||||||
reportCompare(0, 0, 'ok');
|
|
@ -1,6 +1,4 @@
|
|||||||
// Any copyright is dedicated to the Public Domain.
|
// |jit-test| debug
|
||||||
// http://creativecommons.org/licenses/publicdomain/
|
|
||||||
|
|
||||||
// Test removing hooks during dispatch.
|
// Test removing hooks during dispatch.
|
||||||
|
|
||||||
var g = newGlobal('new-compartment');
|
var g = newGlobal('new-compartment');
|
||||||
@ -27,5 +25,3 @@ function addDebug(n) {
|
|||||||
addDebug(10);
|
addDebug(10);
|
||||||
g.eval("debugger;");
|
g.eval("debugger;");
|
||||||
assertEq(log, '0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ');
|
assertEq(log, '0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ');
|
||||||
|
|
||||||
reportCompare(0, 0, 'ok');
|
|
@ -1,6 +1,4 @@
|
|||||||
// Any copyright is dedicated to the Public Domain.
|
// |jit-test| debug
|
||||||
// http://creativecommons.org/licenses/publicdomain/
|
|
||||||
|
|
||||||
// Dispatching an event to a debugger must keep enough of it gc-alive to avoid
|
// Dispatching an event to a debugger must keep enough of it gc-alive to avoid
|
||||||
// crashing.
|
// crashing.
|
||||||
|
|
||||||
@ -35,5 +33,3 @@ addDebug();
|
|||||||
hits = 0;
|
hits = 0;
|
||||||
g.eval("debugger;");
|
g.eval("debugger;");
|
||||||
assertEq(hits, 1);
|
assertEq(hits, 1);
|
||||||
|
|
||||||
reportCompare(0, 0, 'ok');
|
|
@ -1,6 +1,4 @@
|
|||||||
// Any copyright is dedicated to the Public Domain.
|
// |jit-test| debug
|
||||||
// http://creativecommons.org/licenses/publicdomain/
|
|
||||||
|
|
||||||
// Q: But who shall debug the debuggers? A: jimb
|
// Q: But who shall debug the debuggers? A: jimb
|
||||||
|
|
||||||
var log = '';
|
var log = '';
|
||||||
@ -24,5 +22,3 @@ for (var i = 0; i < 8; i++) // why have 2 debuggers when you can have 8
|
|||||||
top = addDebug(top, i);
|
top = addDebug(top, i);
|
||||||
base.eval("debugger;");
|
base.eval("debugger;");
|
||||||
assertEq(log, '0123456776543210');
|
assertEq(log, '0123456776543210');
|
||||||
|
|
||||||
reportCompare(0, 0, 'ok');
|
|
@ -1,5 +1,4 @@
|
|||||||
// Any copyright is dedicated to the Public Domain.
|
// |jit-test| debug
|
||||||
// http://creativecommons.org/licenses/publicdomain/
|
|
||||||
|
|
||||||
var desc = Object.getOwnPropertyDescriptor(Debug.prototype, "enabled");
|
var desc = Object.getOwnPropertyDescriptor(Debug.prototype, "enabled");
|
||||||
assertEq(typeof desc.get, 'function');
|
assertEq(typeof desc.get, 'function');
|
||||||
@ -19,5 +18,3 @@ for (var i = 0; i < vals.length; i++) {
|
|||||||
g.eval("debugger;");
|
g.eval("debugger;");
|
||||||
assertEq(hits, vals[i] ? 1 : 0);
|
assertEq(hits, vals[i] ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
reportCompare(0, 0, 'ok');
|
|
@ -1,6 +1,4 @@
|
|||||||
// Any copyright is dedicated to the Public Domain.
|
// |jit-test| debug
|
||||||
// http://creativecommons.org/licenses/publicdomain/
|
|
||||||
|
|
||||||
// Disabling a Debug object causes events to stop being delivered to it
|
// Disabling a Debug object causes events to stop being delivered to it
|
||||||
// immediately, even if we're in the middle of dispatching.
|
// immediately, even if we're in the middle of dispatching.
|
||||||
|
|
||||||
@ -24,5 +22,3 @@ for (var i = 0; i < 4; i++) {
|
|||||||
log = '';
|
log = '';
|
||||||
g.eval("debugger; debugger;");
|
g.eval("debugger; debugger;");
|
||||||
assertEq(log, '0');
|
assertEq(log, '0');
|
||||||
|
|
||||||
reportCompare(0, 0, 'ok');
|
|
@ -1,6 +1,4 @@
|
|||||||
// Any copyright is dedicated to the Public Domain.
|
// |jit-test| debug
|
||||||
// http://creativecommons.org/licenses/publicdomain/
|
|
||||||
|
|
||||||
// Uncaught exceptions in the debugger itself are delivered to the
|
// Uncaught exceptions in the debugger itself are delivered to the
|
||||||
// uncaughtExceptionHook.
|
// uncaughtExceptionHook.
|
||||||
|
|
||||||
@ -22,5 +20,3 @@ dbg.uncaughtExceptionHook = function (exc) {
|
|||||||
log = '';
|
log = '';
|
||||||
g.eval("debugger");
|
g.eval("debugger");
|
||||||
assertEq(log, 'x!');
|
assertEq(log, 'x!');
|
||||||
|
|
||||||
reportCompare(0, 0, 'ok');
|
|
@ -1,8 +1,8 @@
|
|||||||
// Any copyright is dedicated to the Public Domain.
|
// |jit-test| debug
|
||||||
// http://creativecommons.org/licenses/publicdomain/
|
|
||||||
|
|
||||||
// uncaughtExceptionHook returns a resumption value.
|
// uncaughtExceptionHook returns a resumption value.
|
||||||
|
|
||||||
|
load(libdir + "asserts.js");
|
||||||
|
|
||||||
var g = newGlobal('new-compartment');
|
var g = newGlobal('new-compartment');
|
||||||
var dbg = new Debug(g);
|
var dbg = new Debug(g);
|
||||||
var rv;
|
var rv;
|
||||||
@ -19,16 +19,8 @@ g.eval("debugger");
|
|||||||
// case 2: throw
|
// case 2: throw
|
||||||
rv = {throw: 57};
|
rv = {throw: 57};
|
||||||
var result;
|
var result;
|
||||||
try {
|
assertThrowsValue(function () { g.eval("debugger"); }, 57);
|
||||||
g.eval("debugger");
|
|
||||||
result = 'no exception thrown';
|
|
||||||
} catch (exc) {
|
|
||||||
result = 'caught ' + exc;
|
|
||||||
}
|
|
||||||
assertEq(result, 'caught 57');
|
|
||||||
|
|
||||||
// case 3: return
|
// case 3: return
|
||||||
rv = {return: 42};
|
rv = {return: 42};
|
||||||
assertEq(g.eval("debugger;"), 42);
|
assertEq(g.eval("debugger;"), 42);
|
||||||
|
|
||||||
reportCompare(0, 0, 'ok');
|
|
@ -1,6 +1,4 @@
|
|||||||
// Any copyright is dedicated to the Public Domain.
|
// |jit-test| debug
|
||||||
// http://creativecommons.org/licenses/publicdomain/
|
|
||||||
|
|
||||||
// uncaughtExceptionHook resumption value other than undefined causes further
|
// uncaughtExceptionHook resumption value other than undefined causes further
|
||||||
// hooks to be skipped.
|
// hooks to be skipped.
|
||||||
|
|
||||||
@ -28,5 +26,3 @@ for (var i = 0; i < 6; i++)
|
|||||||
log = '';
|
log = '';
|
||||||
assertEq(g.eval("debugger;"), 42);
|
assertEq(g.eval("debugger;"), 42);
|
||||||
assertEq(log, "012");
|
assertEq(log, "012");
|
||||||
|
|
||||||
reportCompare(0, 0, 'ok');
|
|
@ -1,22 +1,20 @@
|
|||||||
// Any copyright is dedicated to the Public Domain.
|
// |jit-test| debug
|
||||||
// http://creativecommons.org/licenses/publicdomain/
|
|
||||||
|
|
||||||
// dumb basics of uncaughtExceptionHook
|
// dumb basics of uncaughtExceptionHook
|
||||||
|
|
||||||
|
load(libdir + 'asserts.js');
|
||||||
|
|
||||||
var desc = Object.getOwnPropertyDescriptor(Debug.prototype, "uncaughtExceptionHook");
|
var desc = Object.getOwnPropertyDescriptor(Debug.prototype, "uncaughtExceptionHook");
|
||||||
assertEq(typeof desc.get, 'function');
|
assertEq(typeof desc.get, 'function');
|
||||||
assertEq(typeof desc.set, 'function');
|
assertEq(typeof desc.set, 'function');
|
||||||
|
|
||||||
assertThrows(function () { Debug.prototype.uncaughtExceptionHook = null; }, TypeError);
|
assertThrowsInstanceOf(function () { Debug.prototype.uncaughtExceptionHook = null; }, TypeError);
|
||||||
|
|
||||||
var g = newGlobal('new-compartment');
|
var g = newGlobal('new-compartment');
|
||||||
var dbg = new Debug(g);
|
var dbg = new Debug(g);
|
||||||
assertEq(desc.get.call(dbg), null);
|
assertEq(desc.get.call(dbg), null);
|
||||||
assertThrows(function () { dbg.uncaughtExceptionHook = []; }, TypeError);
|
assertThrowsInstanceOf(function () { dbg.uncaughtExceptionHook = []; }, TypeError);
|
||||||
assertThrows(function () { dbg.uncaughtExceptionHook = 3; }, TypeError);
|
assertThrowsInstanceOf(function () { dbg.uncaughtExceptionHook = 3; }, TypeError);
|
||||||
dbg.uncaughtExceptionHook = Math.sin;
|
dbg.uncaughtExceptionHook = Math.sin;
|
||||||
assertEq(dbg.uncaughtExceptionHook, Math.sin);
|
assertEq(dbg.uncaughtExceptionHook, Math.sin);
|
||||||
dbg.uncaughtExceptionHook = null;
|
dbg.uncaughtExceptionHook = null;
|
||||||
assertEq(dbg.uncaughtExceptionHook, null);
|
assertEq(dbg.uncaughtExceptionHook, null);
|
||||||
|
|
||||||
reportCompare(0, 0, 'ok');
|
|
@ -103,3 +103,44 @@ BEGIN_TEST(testDebugger_getThisStrict)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
END_TEST(testDebugger_getThisStrict)
|
END_TEST(testDebugger_getThisStrict)
|
||||||
|
|
||||||
|
BEGIN_TEST(testDebugger_debugObjectVsDebugMode)
|
||||||
|
{
|
||||||
|
CHECK(JS_DefineDebugObject(cx, global));
|
||||||
|
JSObject *debuggee = JS_NewCompartmentAndGlobalObject(cx, getGlobalClass(), NULL);
|
||||||
|
CHECK(debuggee);
|
||||||
|
|
||||||
|
{
|
||||||
|
JSAutoEnterCompartment ae;
|
||||||
|
CHECK(ae.enter(cx, debuggee));
|
||||||
|
CHECK(JS_SetDebugMode(cx, true));
|
||||||
|
CHECK(JS_InitStandardClasses(cx, debuggee));
|
||||||
|
}
|
||||||
|
|
||||||
|
JSObject *debuggeeWrapper = debuggee;
|
||||||
|
CHECK(JS_WrapObject(cx, &debuggeeWrapper));
|
||||||
|
jsval v = OBJECT_TO_JSVAL(debuggeeWrapper);
|
||||||
|
CHECK(JS_SetProperty(cx, global, "debuggee", &v));
|
||||||
|
|
||||||
|
EVAL("var dbg = new Debug(debuggee);\n"
|
||||||
|
"var hits = 0;\n"
|
||||||
|
"dbg.hooks = {debuggerHandler: function () { hits++; }};\n"
|
||||||
|
"debuggee.eval('debugger;');\n"
|
||||||
|
"hits;\n",
|
||||||
|
&v);
|
||||||
|
CHECK_SAME(v, JSVAL_ONE);
|
||||||
|
|
||||||
|
{
|
||||||
|
JSAutoEnterCompartment ae;
|
||||||
|
CHECK(ae.enter(cx, debuggee));
|
||||||
|
CHECK(JS_SetDebugMode(cx, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
EVAL("debuggee.eval('debugger; debugger; debugger;');\n"
|
||||||
|
"hits;\n",
|
||||||
|
&v);
|
||||||
|
CHECK_SAME(v, JSVAL_ONE);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
END_TEST(testDebugger_debugObjectVsDebugMode)
|
||||||
|
@ -517,7 +517,10 @@ struct JS_FRIEND_API(JSCompartment) {
|
|||||||
|
|
||||||
const DebugVector &getDebuggers() const { return debuggers; }
|
const DebugVector &getDebuggers() const { return debuggers; }
|
||||||
|
|
||||||
bool addDebug(js::Debug *dbg) { return debuggers.append(dbg); }
|
bool addDebug(js::Debug *dbg) {
|
||||||
|
JS_ASSERT(debugMode);
|
||||||
|
return debuggers.append(dbg);
|
||||||
|
}
|
||||||
void removeDebug(js::Debug *dbg);
|
void removeDebug(js::Debug *dbg);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -306,10 +306,16 @@ void
|
|||||||
Debug::finalize(JSContext *cx, JSObject *obj)
|
Debug::finalize(JSContext *cx, JSObject *obj)
|
||||||
{
|
{
|
||||||
Debug *dbg = (Debug *) obj->getPrivate();
|
Debug *dbg = (Debug *) obj->getPrivate();
|
||||||
if (dbg && dbg->debuggeeCompartment) {
|
if (dbg && dbg->debuggeeCompartment)
|
||||||
dbg->debuggeeCompartment->removeDebug(dbg);
|
dbg->detachFrom(dbg->debuggeeCompartment);
|
||||||
dbg->debuggeeCompartment = NULL;
|
}
|
||||||
}
|
|
||||||
|
void
|
||||||
|
Debug::detachFrom(JSCompartment *c)
|
||||||
|
{
|
||||||
|
JS_ASSERT(c == debuggeeCompartment);
|
||||||
|
c->removeDebug(this);
|
||||||
|
debuggeeCompartment = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
Class Debug::jsclass = {
|
Class Debug::jsclass = {
|
||||||
@ -411,6 +417,13 @@ Debug::construct(JSContext *cx, uintN argc, Value *vp)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check that the target compartment is in debug mode.
|
||||||
|
JSCompartment *debuggeeCompartment = argobj->getProxyPrivate().toObject().compartment();
|
||||||
|
if (!debuggeeCompartment->debugMode) {
|
||||||
|
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NEED_DEBUG_MODE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Get Debug.prototype.
|
// Get Debug.prototype.
|
||||||
Value v;
|
Value v;
|
||||||
jsid prototypeId = ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom);
|
jsid prototypeId = ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom);
|
||||||
@ -426,7 +439,6 @@ Debug::construct(JSContext *cx, uintN argc, Value *vp)
|
|||||||
JSObject *hooks = NewBuiltinClassInstance(cx, &js_ObjectClass);
|
JSObject *hooks = NewBuiltinClassInstance(cx, &js_ObjectClass);
|
||||||
if (!hooks)
|
if (!hooks)
|
||||||
return false;
|
return false;
|
||||||
JSCompartment *debuggeeCompartment = argobj->getProxyPrivate().toObject().compartment();
|
|
||||||
Debug *dbg = cx->new_<Debug>(obj, hooks, debuggeeCompartment);
|
Debug *dbg = cx->new_<Debug>(obj, hooks, debuggeeCompartment);
|
||||||
if (!dbg)
|
if (!dbg)
|
||||||
return false;
|
return false;
|
||||||
|
@ -110,6 +110,7 @@ class Debug {
|
|||||||
static inline Debug *fromJSObject(JSObject *obj);
|
static inline Debug *fromJSObject(JSObject *obj);
|
||||||
|
|
||||||
inline bool observesCompartment(JSCompartment *c) const;
|
inline bool observesCompartment(JSCompartment *c) const;
|
||||||
|
void detachFrom(JSCompartment *c);
|
||||||
|
|
||||||
static inline JSTrapStatus onDebuggerStatement(JSContext *cx, js::Value *vp);
|
static inline JSTrapStatus onDebuggerStatement(JSContext *cx, js::Value *vp);
|
||||||
};
|
};
|
||||||
|
@ -51,6 +51,7 @@
|
|||||||
#include "jsapi.h"
|
#include "jsapi.h"
|
||||||
#include "jscntxt.h"
|
#include "jscntxt.h"
|
||||||
#include "jsversion.h"
|
#include "jsversion.h"
|
||||||
|
#include "jsdbg.h"
|
||||||
#include "jsdbgapi.h"
|
#include "jsdbgapi.h"
|
||||||
#include "jsemit.h"
|
#include "jsemit.h"
|
||||||
#include "jsfun.h"
|
#include "jsfun.h"
|
||||||
@ -193,9 +194,16 @@ JS_SetDebugModeForCompartment(JSContext *cx, JSCompartment *comp, JSBool debug)
|
|||||||
// All scripts compiled from this point on should be in the requested debugMode.
|
// All scripts compiled from this point on should be in the requested debugMode.
|
||||||
comp->debugMode = !!debug;
|
comp->debugMode = !!debug;
|
||||||
|
|
||||||
|
// Detach any debuggers attached to this compartment.
|
||||||
|
if (debug) {
|
||||||
|
JS_ASSERT(comp->getDebuggers().empty());
|
||||||
|
} else {
|
||||||
|
while (!comp->getDebuggers().empty())
|
||||||
|
comp->getDebuggers().back()->detachFrom(comp);
|
||||||
|
}
|
||||||
|
|
||||||
// Discard JIT code for any scripts that change debugMode. This function
|
// Discard JIT code for any scripts that change debugMode. This function
|
||||||
// assumes that 'comp' is in the same thread as 'cx'.
|
// assumes that 'comp' is in the same thread as 'cx'.
|
||||||
|
|
||||||
#ifdef JS_METHODJIT
|
#ifdef JS_METHODJIT
|
||||||
JS::AutoEnterScriptCompartment ac;
|
JS::AutoEnterScriptCompartment ac;
|
||||||
|
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
// Any copyright is dedicated to the Public Domain.
|
|
||||||
// http://creativecommons.org/licenses/publicdomain/
|
|
||||||
|
|
||||||
checkFunction(this, "Debug", 1);
|
|
||||||
|
|
||||||
assertEq(Debug.prototype.constructor, Debug);
|
|
||||||
assertEq(Object.prototype.toString.call(Debug.prototype), "[object Debug]");
|
|
||||||
assertEq(Object.getPrototypeOf(Debug.prototype), Object.prototype);
|
|
||||||
|
|
||||||
reportCompare(0, 0, 'ok');
|
|
@ -1,37 +0,0 @@
|
|||||||
// Any copyright is dedicated to the Public Domain.
|
|
||||||
// http://creativecommons.org/licenses/publicdomain/
|
|
||||||
|
|
||||||
// Debug rejects arguments that aren't cross-compartment wrappers.
|
|
||||||
assertThrows(function () { Debug(); }, TypeError);
|
|
||||||
assertThrows(function () { Debug(null); }, TypeError);
|
|
||||||
assertThrows(function () { Debug(true); }, TypeError);
|
|
||||||
assertThrows(function () { Debug(42); }, TypeError);
|
|
||||||
assertThrows(function () { Debug("bad"); }, TypeError);
|
|
||||||
assertThrows(function () { Debug(function () {}); }, TypeError);
|
|
||||||
assertThrows(function () { Debug(this); }, TypeError);
|
|
||||||
assertThrows(function () { new Debug(); }, TypeError);
|
|
||||||
assertThrows(function () { new Debug(null); }, TypeError);
|
|
||||||
assertThrows(function () { new Debug(true); }, TypeError);
|
|
||||||
assertThrows(function () { new Debug(42); }, TypeError);
|
|
||||||
assertThrows(function () { new Debug("bad"); }, TypeError);
|
|
||||||
assertThrows(function () { new Debug(function () {}); }, TypeError);
|
|
||||||
assertThrows(function () { new Debug(this); }, TypeError);
|
|
||||||
|
|
||||||
// Very basic tests of Debug creation.
|
|
||||||
var g = newGlobal('new-compartment');
|
|
||||||
var dbg = new Debug(g);
|
|
||||||
assertEq(dbg instanceof Debug, true);
|
|
||||||
assertEq(Object.getPrototypeOf(dbg), Debug.prototype);
|
|
||||||
|
|
||||||
// The reverse.
|
|
||||||
var g2 = newGlobal('new-compartment');
|
|
||||||
g2.debuggeeGlobal = this;
|
|
||||||
g2.eval("var dbg = new Debug(debuggeeGlobal);");
|
|
||||||
assertEq(g2.eval("dbg instanceof Debug"), true);
|
|
||||||
|
|
||||||
// The Debug constructor from this compartment will not accept as its argument
|
|
||||||
// an Object from this compartment. Shenanigans won't fool the membrane.
|
|
||||||
g2.outer = this;
|
|
||||||
assertThrows(function () { g2.eval("outer.Debug(outer.Object())"); }, TypeError);
|
|
||||||
|
|
||||||
reportCompare(0, 0, 'ok');
|
|
@ -1,32 +0,0 @@
|
|||||||
// Any copyright is dedicated to the Public Domain.
|
|
||||||
// http://creativecommons.org/licenses/publicdomain/
|
|
||||||
|
|
||||||
// Simple {throw:} resumption.
|
|
||||||
|
|
||||||
var g = newGlobal('new-compartment');
|
|
||||||
|
|
||||||
var dbg = Debug(g);
|
|
||||||
dbg.hooks = {
|
|
||||||
debuggerHandler: function (stack) {
|
|
||||||
return {throw: "oops"};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = 'no exception thrown';
|
|
||||||
try {
|
|
||||||
g.eval("debugger;");
|
|
||||||
} catch (exc) {
|
|
||||||
result = exc;
|
|
||||||
}
|
|
||||||
assertEq(result, "oops");
|
|
||||||
|
|
||||||
g.eval("function f() { debugger; }");
|
|
||||||
result = 'no exception thrown';
|
|
||||||
try {
|
|
||||||
g.f();
|
|
||||||
} catch (exc) {
|
|
||||||
result = exc;
|
|
||||||
}
|
|
||||||
assertEq(result, "oops");
|
|
||||||
|
|
||||||
reportCompare(0, 0, 'ok');
|
|
@ -1,32 +0,0 @@
|
|||||||
// Any copyright is dedicated to the Public Domain.
|
|
||||||
// http://creativecommons.org/licenses/publicdomain/
|
|
||||||
|
|
||||||
// Debug.prototype.hooks
|
|
||||||
|
|
||||||
var g = newGlobal('new-compartment');
|
|
||||||
var dbg = new Debug(g);
|
|
||||||
gc(); // don't assert marking dbg.hooks
|
|
||||||
var h = dbg.hooks;
|
|
||||||
assertEq(typeof h, 'object');
|
|
||||||
assertEq(Object.getOwnPropertyNames(h).length, 0);
|
|
||||||
assertEq(Object.getPrototypeOf(h), Object.prototype);
|
|
||||||
|
|
||||||
assertThrows(function () { dbg.hooks = null; }, TypeError);
|
|
||||||
assertThrows(function () { dbg.hooks = "bad"; }, TypeError);
|
|
||||||
|
|
||||||
assertEq(Object.getOwnPropertyNames(dbg).length, 0);
|
|
||||||
var desc = Object.getOwnPropertyDescriptor(Debug.prototype, "hooks");
|
|
||||||
assertEq(desc.configurable, true);
|
|
||||||
assertEq(desc.enumerable, false);
|
|
||||||
|
|
||||||
assertThrows(function () { desc.get(); }, TypeError);
|
|
||||||
assertThrows(function () { desc.get.call(undefined); }, TypeError);
|
|
||||||
assertThrows(function () { desc.get.call(Debug.prototype); }, TypeError);
|
|
||||||
assertEq(desc.get.call(dbg), h);
|
|
||||||
|
|
||||||
assertThrows(function () { desc.set(); }, TypeError);
|
|
||||||
assertThrows(function () { desc.set.call(dbg); }, TypeError);
|
|
||||||
assertThrows(function () { desc.set.call({}, {}); }, TypeError);
|
|
||||||
assertThrows(function () { desc.set.call(Debug.prototype, {}); }, TypeError);
|
|
||||||
|
|
||||||
reportCompare(0, 0, 'ok');
|
|
@ -42,24 +42,3 @@ script regress-636697.js
|
|||||||
script is-generator.js
|
script is-generator.js
|
||||||
script weakmap.js
|
script weakmap.js
|
||||||
script regress-650753.js
|
script regress-650753.js
|
||||||
skip-if(!xulRuntime.shell) script debug-object-01.js
|
|
||||||
skip-if(!xulRuntime.shell) script debug-object-02.js
|
|
||||||
skip-if(!xulRuntime.shell) script debug-object-03.js
|
|
||||||
skip-if(!xulRuntime.shell) script debug-object-04.js
|
|
||||||
skip-if(!xulRuntime.shell) script debug-object-05.js
|
|
||||||
skip-if(!xulRuntime.shell) script debug-object-06.js
|
|
||||||
skip-if(!xulRuntime.shell) script debug-object-07.js
|
|
||||||
skip-if(!xulRuntime.shell) script debug-object-08.js
|
|
||||||
skip-if(!xulRuntime.shell) script debug-object-09.js
|
|
||||||
skip-if(!xulRuntime.shell) script debug-object-10.js
|
|
||||||
skip-if(!xulRuntime.shell) script debug-object-11.js
|
|
||||||
skip-if(!xulRuntime.shell) script debug-object-12.js
|
|
||||||
skip-if(!xulRuntime.shell) script debug-object-13.js
|
|
||||||
skip-if(!xulRuntime.shell) script debug-object-14.js
|
|
||||||
skip-if(!xulRuntime.shell) script debug-object-15.js
|
|
||||||
skip-if(!xulRuntime.shell) script debug-object-16.js
|
|
||||||
skip-if(!xulRuntime.shell) script debug-object-17.js
|
|
||||||
skip-if(!xulRuntime.shell) script debug-object-18.js
|
|
||||||
skip-if(!xulRuntime.shell) script debug-object-19.js
|
|
||||||
skip-if(!xulRuntime.shell) script debug-object-20.js
|
|
||||||
skip-if(!xulRuntime.shell) script debug-object-21.js
|
|
||||||
|
@ -170,14 +170,3 @@ var Match =
|
|||||||
MatchError: MatchError };
|
MatchError: MatchError };
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// used by several Debug object tests
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user