mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1096739 - Clean up ThreadActor.prototype._setBreakpoint. r=ejpbruel,past
This commit is contained in:
parent
884af473bd
commit
e94753d0c8
@ -5,7 +5,9 @@ function secondCall() {
|
||||
// This comment is useful: ☺
|
||||
eval("debugger;");
|
||||
function foo() {}
|
||||
if (true) {
|
||||
if (x) {
|
||||
foo();
|
||||
}
|
||||
}
|
||||
|
||||
var x = true;
|
||||
|
@ -176,6 +176,27 @@ BreakpointStore.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Move the breakpoint to the new location.
|
||||
*
|
||||
* @param Object aBreakpoint
|
||||
* The breakpoint being moved. See `addBreakpoint` for a description of
|
||||
* its expected properties.
|
||||
* @param Object aNewLocation
|
||||
* The location to move the breakpoint to. Properties:
|
||||
* - line
|
||||
* - column (optional; omission implies whole line breakpoint)
|
||||
*/
|
||||
moveBreakpoint: function (aBreakpoint, aNewLocation) {
|
||||
const existingBreakpoint = this.getBreakpoint(aBreakpoint);
|
||||
this.removeBreakpoint(existingBreakpoint);
|
||||
|
||||
const { line, column } = aNewLocation;
|
||||
existingBreakpoint.line = line;
|
||||
existingBreakpoint.column = column;
|
||||
this.addBreakpoint(existingBreakpoint);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a breakpoint from the breakpoint store. Will throw an error if the
|
||||
* breakpoint is not found.
|
||||
@ -1426,12 +1447,132 @@ ThreadActor.prototype = {
|
||||
return this._setBreakpoint(aLocation);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Set a breakpoint using the jsdbg2 API. If the line on which the breakpoint
|
||||
* is being set contains no code, then the breakpoint will slide down to the
|
||||
* next line that has runnable code. In this case the server breakpoint cache
|
||||
* will be updated, so callers that iterate over the breakpoint cache should
|
||||
* take that into account.
|
||||
* Get or create the BreakpointActor for the breakpoint at the given location.
|
||||
*
|
||||
* NB: This will override a pre-existing BreakpointActor's condition with
|
||||
* the given the location's condition.
|
||||
*
|
||||
* @param Object location
|
||||
* The breakpoint location. See BreakpointStore.prototype.addBreakpoint
|
||||
* for more information.
|
||||
* @returns BreakpointActor
|
||||
*/
|
||||
_getOrCreateBreakpointActor: function (location) {
|
||||
let actor;
|
||||
const storedBp = this.breakpointStore.getBreakpoint(location);
|
||||
|
||||
if (storedBp.actor) {
|
||||
actor = storedBp.actor;
|
||||
actor.condition = location.condition;
|
||||
return actor;
|
||||
}
|
||||
|
||||
storedBp.actor = actor = new BreakpointActor(this, {
|
||||
url: location.url,
|
||||
line: location.line,
|
||||
column: location.column,
|
||||
condition: location.condition
|
||||
});
|
||||
this.threadLifetimePool.addActor(actor);
|
||||
return actor;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set breakpoints at the offsets closest to our target location's column.
|
||||
*
|
||||
* @param Array scripts
|
||||
* The set of Debugger.Script instances to consider.
|
||||
* @param Object location
|
||||
* The target location.
|
||||
* @param BreakpointActor actor
|
||||
* The BreakpointActor to handle hitting the breakpoints we set.
|
||||
* @returns Object
|
||||
* The RDP response.
|
||||
*/
|
||||
_setBreakpointAtColumn: function (scripts, location, actor) {
|
||||
// Debugger.Script -> array of offset mappings
|
||||
const scriptsAndOffsetMappings = new Map();
|
||||
|
||||
for (let script of scripts) {
|
||||
this._findClosestOffsetMappings(location, script, scriptsAndOffsetMappings);
|
||||
}
|
||||
|
||||
for (let [script, mappings] of scriptsAndOffsetMappings) {
|
||||
for (let offsetMapping of mappings) {
|
||||
script.setBreakpoint(offsetMapping.offset, actor);
|
||||
}
|
||||
actor.addScript(script, this);
|
||||
}
|
||||
|
||||
return {
|
||||
actor: actor.actorID
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Find the scripts which contain offsets that are an entry point to the given
|
||||
* line.
|
||||
*
|
||||
* @param Array scripts
|
||||
* The set of Debugger.Scripts to consider.
|
||||
* @param Number line
|
||||
* The line we are searching for entry points into.
|
||||
* @returns Array of objects of the form { script, offsets } where:
|
||||
* - script is a Debugger.Script
|
||||
* - offsets is an array of offsets that are entry points into the
|
||||
* given line.
|
||||
*/
|
||||
_findEntryPointsForLine: function (scripts, line) {
|
||||
const entryPoints = [];
|
||||
for (let script of scripts) {
|
||||
const offsets = script.getLineOffsets(line);
|
||||
if (offsets.length) {
|
||||
entryPoints.push({ script, offsets });
|
||||
}
|
||||
}
|
||||
return entryPoints;
|
||||
},
|
||||
|
||||
/**
|
||||
* Find the first line that is associated with bytecode offsets, and is
|
||||
* greater than or equal to the given start line.
|
||||
*
|
||||
* @param Array scripts
|
||||
* The set of Debugger.Script instances to consider.
|
||||
* @param Number startLine
|
||||
* The target line.
|
||||
* @return Object|null
|
||||
* If we can't find a line matching our constraints, return
|
||||
* null. Otherwise, return an object of the form:
|
||||
* {
|
||||
* line: Number,
|
||||
* entryPoints: [
|
||||
* { script: Debugger.Script, offsets: [offset, ...] },
|
||||
* ...
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
_findNextLineWithOffsets: function (scripts, startLine) {
|
||||
const maxLine = Math.max(...scripts.map(s => s.startLine + s.lineCount));
|
||||
|
||||
for (let line = startLine; line < maxLine; line++) {
|
||||
const entryPoints = this._findEntryPointsForLine(scripts, line);
|
||||
if (entryPoints.length) {
|
||||
return { line, entryPoints };
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set a breakpoint using the Debugger API. If the line on which the
|
||||
* breakpoint is being set contains no code, then the breakpoint will slide
|
||||
* down to the next line that has runnable code. In this case the server
|
||||
* breakpoint cache will be updated, so callers that iterate over the
|
||||
* breakpoint cache should take that into account.
|
||||
*
|
||||
* @param object aLocation
|
||||
* The location of the breakpoint (in the generated source, if source
|
||||
@ -1441,170 +1582,134 @@ ThreadActor.prototype = {
|
||||
* nowhere else.
|
||||
*/
|
||||
_setBreakpoint: function (aLocation, aOnlyThisScript=null) {
|
||||
let location = {
|
||||
const location = {
|
||||
url: aLocation.url,
|
||||
line: aLocation.line,
|
||||
column: aLocation.column,
|
||||
condition: aLocation.condition
|
||||
};
|
||||
|
||||
let actor;
|
||||
let storedBp = this.breakpointStore.getBreakpoint(location);
|
||||
if (storedBp.actor) {
|
||||
actor = storedBp.actor;
|
||||
actor.condition = location.condition;
|
||||
} else {
|
||||
storedBp.actor = actor = new BreakpointActor(this, {
|
||||
url: location.url,
|
||||
line: location.line,
|
||||
column: location.column,
|
||||
condition: location.condition
|
||||
});
|
||||
this.threadLifetimePool.addActor(actor);
|
||||
}
|
||||
|
||||
// Find all scripts matching the given location
|
||||
let scripts = this.dbg.findScripts(location);
|
||||
if (scripts.length == 0) {
|
||||
// Since we did not find any scripts to set the breakpoint on now, return
|
||||
// early. When a new script that matches this breakpoint location is
|
||||
// introduced, the breakpoint actor will already be in the breakpoint store
|
||||
// and will be set at that time.
|
||||
return {
|
||||
actor: actor.actorID
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* For each script, if the given line has at least one entry point, set a
|
||||
* breakpoint on the bytecode offets for each of them.
|
||||
*/
|
||||
|
||||
// Debugger.Script -> array of offset mappings
|
||||
let scriptsAndOffsetMappings = new Map();
|
||||
|
||||
for (let script of scripts) {
|
||||
this._findClosestOffsetMappings(location,
|
||||
script,
|
||||
scriptsAndOffsetMappings);
|
||||
}
|
||||
|
||||
if (scriptsAndOffsetMappings.size > 0) {
|
||||
for (let [script, mappings] of scriptsAndOffsetMappings) {
|
||||
if (aOnlyThisScript && script !== aOnlyThisScript) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let offsetMapping of mappings) {
|
||||
script.setBreakpoint(offsetMapping.offset, actor);
|
||||
}
|
||||
actor.addScript(script, this);
|
||||
}
|
||||
|
||||
return {
|
||||
actor: actor.actorID
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* If we get here, no breakpoint was set. This is because the given line
|
||||
* has no entry points, for example because it is empty. As a fallback
|
||||
* strategy, we try to set the breakpoint on the smallest line greater
|
||||
* than or equal to the given line that as at least one entry point.
|
||||
*/
|
||||
|
||||
// Find all innermost scripts matching the given location
|
||||
scripts = this.dbg.findScripts({
|
||||
url: aLocation.url,
|
||||
line: aLocation.line,
|
||||
innermost: true
|
||||
const actor = location.actor = this._getOrCreateBreakpointActor(location);
|
||||
const scripts = this.dbg.findScripts({
|
||||
url: location.url,
|
||||
// Although we will automatically slide the breakpoint down to the first
|
||||
// line with code when the requested line doesn't have any, we want to
|
||||
// restrict the sliding to within functions that contain the requested
|
||||
// line.
|
||||
line: location.line
|
||||
});
|
||||
|
||||
/**
|
||||
* For each innermost script, look for the smallest line greater than or
|
||||
* equal to the given line that has one or more entry points. If found, set
|
||||
* a breakpoint on the bytecode offset for each of its entry points.
|
||||
*/
|
||||
let actualLocation;
|
||||
let found = false;
|
||||
for (let script of scripts) {
|
||||
let offsets = script.getAllOffsets();
|
||||
|
||||
for (let line = location.line; line < offsets.length; ++line) {
|
||||
if (offsets[line]) {
|
||||
if (!aOnlyThisScript || script === aOnlyThisScript) {
|
||||
for (let offset of offsets[line]) {
|
||||
script.setBreakpoint(offset, actor);
|
||||
}
|
||||
actor.addScript(script, this);
|
||||
}
|
||||
|
||||
if (!actualLocation) {
|
||||
actualLocation = {
|
||||
url: location.url,
|
||||
line: line
|
||||
};
|
||||
}
|
||||
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (scripts.length === 0) {
|
||||
// Since we did not find any scripts to set the breakpoint on now, return
|
||||
// early. When a new script that matches this breakpoint location is
|
||||
// introduced, the breakpoint actor will already be in the breakpoint
|
||||
// store and the breakpoint will be set at that time. This is similar to
|
||||
// GDB's "pending" breakpoints for shared libraries that aren't loaded
|
||||
// yet.
|
||||
return {
|
||||
actor: actor.actorID
|
||||
};
|
||||
}
|
||||
|
||||
if (found) {
|
||||
let existingBp = this.breakpointStore.hasBreakpoint(actualLocation);
|
||||
if (location.column) {
|
||||
return this._setBreakpointAtColumn(scripts, location, actor);
|
||||
}
|
||||
|
||||
if (existingBp && existingBp.actor) {
|
||||
/**
|
||||
* We already have a breakpoint actor for the actual location, so actor
|
||||
* we created earlier is now redundant. Delete it, update the breakpoint
|
||||
* store, and return the actor for the actual location.
|
||||
*/
|
||||
// Select the first line that has offsets, and is greater than or equal to
|
||||
// the requested line. Set breakpoints on each of the offsets that is an
|
||||
// entry point to our selected line.
|
||||
|
||||
const result = this._findNextLineWithOffsets(scripts, location.line);
|
||||
if (!result) {
|
||||
return {
|
||||
error: "noCodeAtLineColumn",
|
||||
actor: actor.actorID
|
||||
};
|
||||
}
|
||||
|
||||
const { line, entryPoints } = result;
|
||||
const actualLocation = line !== location.line
|
||||
? { url: location.url, line }
|
||||
: undefined;
|
||||
|
||||
if (actualLocation) {
|
||||
// Check whether we already have a breakpoint actor for the actual
|
||||
// location. If we do have an existing actor, then the actor we created
|
||||
// above is redundant and must be destroyed. If we do not have an existing
|
||||
// actor, we need to update the breakpoint store with the new location.
|
||||
|
||||
const existingBreakpoint = this.breakpointStore.hasBreakpoint(actualLocation);
|
||||
if (existingBreakpoint && existingBreakpoint.actor) {
|
||||
actor.onDelete();
|
||||
this.breakpointStore.removeBreakpoint(location);
|
||||
return {
|
||||
actor: existingBp.actor.actorID,
|
||||
actualLocation: actualLocation
|
||||
actor: existingBreakpoint.actor.actorID,
|
||||
actualLocation
|
||||
};
|
||||
} else {
|
||||
actor.location = actualLocation;
|
||||
this.breakpointStore.moveBreakpoint(location, actualLocation);
|
||||
}
|
||||
|
||||
/**
|
||||
* We don't have a breakpoint actor for the actual location yet. Instead
|
||||
* or creating a new actor, reuse the actor we created earlier, and update
|
||||
* the breakpoint store.
|
||||
*/
|
||||
actor.location = actualLocation;
|
||||
this.breakpointStore.addBreakpoint({
|
||||
actor: actor,
|
||||
url: actualLocation.url,
|
||||
line: actualLocation.line,
|
||||
column: actualLocation.column
|
||||
});
|
||||
this.breakpointStore.removeBreakpoint(location);
|
||||
return {
|
||||
actor: actor.actorID,
|
||||
actualLocation: actualLocation
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* If we get here, no line matching the given line was found, so just fail
|
||||
* epically.
|
||||
*/
|
||||
this._setBreakpointOnEntryPoints(
|
||||
actor,
|
||||
aOnlyThisScript
|
||||
? entryPoints.filter(o => o.script === aOnlyThisScript)
|
||||
: entryPoints
|
||||
);
|
||||
|
||||
return {
|
||||
error: "noCodeAtLineColumn",
|
||||
actor: actor.actorID
|
||||
actor: actor.actorID,
|
||||
actualLocation
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Set breakpoints on all the given entry points with the given
|
||||
* BreakpointActor as the handler.
|
||||
*
|
||||
* @param BreakpointActor actor
|
||||
* The actor handling the breakpoint hits.
|
||||
* @param Array entryPoints
|
||||
* An array of objects of the form `{ script, offsets }`.
|
||||
*/
|
||||
_setBreakpointOnEntryPoints: function (actor, entryPoints) {
|
||||
for (let { script, offsets } of entryPoints) {
|
||||
for (let offset of offsets) {
|
||||
script.setBreakpoint(offset, actor);
|
||||
}
|
||||
actor.addScript(script, this);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Find all of the offset mappings associated with `aScript` that are closest
|
||||
* to `aTargetLocation`. If new offset mappings are found that are closer to
|
||||
* `aTargetOffset` than the existing offset mappings inside
|
||||
* `aScriptsAndOffsetMappings`, we empty that map and only consider the
|
||||
* closest offset mappings. If there is no column in `aTargetLocation`, we add
|
||||
* all offset mappings that are on the given line.
|
||||
* closest offset mappings.
|
||||
*
|
||||
* In many cases, but not all, this method finds only one closest offset.
|
||||
* Consider the following case, where multiple offsets will be found:
|
||||
*
|
||||
* 0 1 2 3
|
||||
* 0123456789012345678901234567890
|
||||
* +-------------------------------
|
||||
* 1|function f() {
|
||||
* 2| return g() + h();
|
||||
* 3|}
|
||||
*
|
||||
* The Debugger reports three offsets on line 2 upon which we could set a
|
||||
* breakpoint: the `return` statement at column 2, the call expression `g()`
|
||||
* at column 9, and the call expression `h()` at column 15. (Careful readers
|
||||
* will note that complete source location information isn't saved by
|
||||
* SpiderMonkey's frontend, and we don't get an offset associated specifically
|
||||
* with the `+` operation.)
|
||||
*
|
||||
* If our target location is line 2 column 12, the offset for the call to `g`
|
||||
* is 3 columns to the left and the offset for the call to `h` is 3 columns to
|
||||
* the right. Because they are equally close, we will return both offsets to
|
||||
* have breakpoints set upon them.
|
||||
*
|
||||
* @param Object aTargetLocation
|
||||
* An object of the form { url, line[, column] }.
|
||||
@ -1617,21 +1722,6 @@ ThreadActor.prototype = {
|
||||
_findClosestOffsetMappings: function (aTargetLocation,
|
||||
aScript,
|
||||
aScriptsAndOffsetMappings) {
|
||||
// If we are given a column, we will try and break only at that location,
|
||||
// otherwise we will break anytime we get on that line.
|
||||
|
||||
if (aTargetLocation.column == null) {
|
||||
let offsetMappings = aScript.getLineOffsets(aTargetLocation.line)
|
||||
.map(o => ({
|
||||
line: aTargetLocation.line,
|
||||
offset: o
|
||||
}));
|
||||
if (offsetMappings.length) {
|
||||
aScriptsAndOffsetMappings.set(aScript, offsetMappings);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let offsetMappings = aScript.getAllColumnOffsets()
|
||||
.filter(({ lineNumber }) => lineNumber === aTargetLocation.line);
|
||||
|
||||
|
@ -16,10 +16,10 @@ Components.utils.import('resource:///modules/devtools/SourceMap.jsm');
|
||||
function run_test()
|
||||
{
|
||||
initTestTracerServer();
|
||||
gDebuggee = addTestGlobal("test-tracer-actor");
|
||||
gDebuggee = addTestGlobal("test-breakpoints");
|
||||
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
gClient.connect(function() {
|
||||
attachTestThread(gClient, "test-tracer-actor", testBreakpoint);
|
||||
attachTestThread(gClient, "test-breakpoints", testBreakpoint);
|
||||
});
|
||||
do_test_pending();
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ function run_test()
|
||||
test_remove_breakpoint();
|
||||
test_find_breakpoints();
|
||||
test_duplicate_breakpoints();
|
||||
test_move_breakpoint();
|
||||
}
|
||||
|
||||
function test_has_breakpoint() {
|
||||
@ -180,3 +181,31 @@ function test_duplicate_breakpoints() {
|
||||
do_check_eq(bpStore.size, 1, "We should have only 1 whole line breakpoint");
|
||||
bpStore.removeBreakpoint(location);
|
||||
}
|
||||
|
||||
function test_move_breakpoint() {
|
||||
let bpStore = new BreakpointStore();
|
||||
|
||||
let oldLocation = {
|
||||
url: "http://example.com/foo.js",
|
||||
line: 10
|
||||
};
|
||||
|
||||
let newLocation = {
|
||||
url: "http://example.com/foo.js",
|
||||
line: 12
|
||||
};
|
||||
|
||||
bpStore.addBreakpoint(oldLocation);
|
||||
bpStore.moveBreakpoint(oldLocation, newLocation);
|
||||
|
||||
equal(bpStore.size, 1, "Moving a breakpoint maintains the correct size.");
|
||||
|
||||
let bp = bpStore.getBreakpoint(newLocation);
|
||||
ok(bp, "We should be able to get a breakpoint at the new location.");
|
||||
equal(bp.line, newLocation.line,
|
||||
"We should get the moved line.");
|
||||
|
||||
equal(bpStore.hasBreakpoint({ url: "http://example.com/foo.js", line: 10 }),
|
||||
null,
|
||||
"And we shouldn't be able to get any BP at the old location.");
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user