Skip to content
This repository was archived by the owner on Oct 12, 2022. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 88 additions & 11 deletions src/node/nodeDebug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ export class NodeDebugSession extends DebugSession {
private static EXCEPTION_REASON = "exception";
private static DEBUGGER_REASON = "debugger statement";
private static USER_REQUEST_REASON = "user request";
private static FIRST_LINE_REASON = "first line break";

private static ANON_FUNCTION = "(anonymous function)";

Expand All @@ -176,6 +177,7 @@ export class NodeDebugSession extends DebugSession {
public _variableHandles = new Handles<Expandable>();
public _frameHandles = new Handles<any>();
private _refCache = new Map<number, any>();
private _breakpointMap = new Map<string, Array<InternalBreakpoint>>();

private _externalConsole: boolean;
private _isTerminated: boolean;
Expand All @@ -193,6 +195,7 @@ export class NodeDebugSession extends DebugSession {
private _needContinue: boolean;
private _needBreakpointEvent: boolean;
private _lazy: boolean; // whether node is in 'lazy' mode
private _firstLineBreakId: number;

private _gotEntryEvent: boolean;
private _entryPath: string;
Expand All @@ -211,6 +214,8 @@ export class NodeDebugSession extends DebugSession {
this._lastStoppedEvent = this.createStoppedEvent(event.body);
if (this._lastStoppedEvent.body.reason === NodeDebugSession.ENTRY_REASON) {
if (NodeDebugSession.TRACE_INITIALISATION) console.error('_init: supressed stop-on-entry event');
} else if (this._lastStoppedEvent.body.reason === NodeDebugSession.FIRST_LINE_REASON) {
this._handleFirstLineBreak(event);
} else {
this.sendEvent(this._lastStoppedEvent);
}
Expand Down Expand Up @@ -245,6 +250,48 @@ export class NodeDebugSession extends DebugSession {
this._refCache = new Map<number, any>();
}

private _handleFirstLineBreak(breakEvent: NodeV8Event) {
this._node.command('scripts', {
ids: [breakEvent.body.script.id],
includeSource: true,
}, (sourceResponse: NodeV8Response) => {
let source = sourceResponse.body[0];
this._sourceMaps.ParseGeneratedSource(source.name, source.source);
let mr = this._sourceMaps.MapToSource(source.name, 0, 0);

if (!mr) {
this._node.command('continue');
return;
}

let originalPath = mr.path;
let lbs = this._breakpointMap.get(originalPath);
this._breakpointMap.delete(originalPath);

if (!lbs || !lbs.length) {
this._node.command('continue');
return;
}

for (let i = 0; i < lbs.length; i++) {
const mr = this._sourceMaps.MapFromSource(originalPath, lbs[i].line, lbs[i].column);
if (mr) {
lbs[i].line = mr.line;
lbs[i].column = mr.column;
lbs[i].ignore = false;
} else {
lbs[i].ignore = true;
}
}

this._clearAllBreakpointsFromPathOrScript(source.name, null, response => {
this._setBreakpoints(0, source.name, null, lbs, true, () => {
this._node.command('continue');
});
});
});
}

/**
* The debug session has terminated.
*/
Expand Down Expand Up @@ -650,10 +697,10 @@ export class NodeDebugSession extends DebugSession {
if (NodeDebugSession.TRACE_INITIALISATION) console.error('_init: 2nd retrieve node pid: OK');
this._nodeProcessId = parseInt(resp.body.value);
}
this._middleInitialize(stopped);
this._addFirstLineBreaks(stopped);
});
} else {
this._middleInitialize(stopped);
this._addFirstLineBreaks(stopped);
}
} else {
if (NodeDebugSession.TRACE_INITIALISATION) console.error(`_init: no entry event after ${n} retries; give up`);
Expand All @@ -667,11 +714,22 @@ export class NodeDebugSession extends DebugSession {
this.rememberEntryLocation(s.name, resp.body.line, resp.body.column);
}

this._middleInitialize(stopped);
this._addFirstLineBreaks(stopped);
});
}
}

private _addFirstLineBreaks(stopped: boolean): void {
let debugSession = this;
this._node.command('setbreakpoint', {
type: 'scriptRegExp',
target: '.*(?!\\.js$)\\.[^.]+$'
}, (data: any) => {
debugSession._firstLineBreakId = data.body.breakpoint;
debugSession._middleInitialize(stopped);
});
}

private _middleInitialize(stopped: boolean): void {
// request UI to send breakpoints
if (NodeDebugSession.TRACE_INITIALISATION) console.error('_init: -> fire initialize event');
Expand Down Expand Up @@ -816,12 +874,20 @@ export class NodeDebugSession extends DebugSession {
}
path = p;
}
else if (!NodeDebugSession.isJavaScript(path)) {
// ignore all breakpoints for this source
for (let lb of lbs) {
lb.ignore = true;
else {
// we might get a sourcemap for this path later
if (path) {
this._breakpointMap.set(path, lbs);
}

if (!NodeDebugSession.isJavaScript(path)) {
// ignore all breakpoints for this source
for (let lb of lbs) {
lb.ignore = true;
}
}
}

this._clearAllBreakpoints(response, path, -1, lbs, sourcemap);
return;
}
Expand Down Expand Up @@ -855,8 +921,17 @@ export class NodeDebugSession extends DebugSession {
private _clearAllBreakpoints(response: DebugProtocol.SetBreakpointsResponse, path: string, scriptId: number, lbs: InternalBreakpoint[], sourcemap: boolean): void {

// clear all existing breakpoints for the given path or script ID
this._node.command('listbreakpoints', null, (nodeResponse: NodeV8Response) => {
this._clearAllBreakpointsFromPathOrScript(path, scriptId, nodeResponse => {
if (nodeResponse.success) {
this._finishSetBreakpoints(response, path, scriptId, lbs, sourcemap);
} else {
this.sendNodeResponse(response, nodeResponse);
}
})
}

private _clearAllBreakpointsFromPathOrScript(path: string, scriptId: number, done: (NodeV8Response) => void) : void {
this._node.command('listbreakpoints', null, (nodeResponse: NodeV8Response) => {
if (nodeResponse.success) {
const toClear = new Array<number>();

Expand All @@ -880,15 +955,15 @@ export class NodeDebugSession extends DebugSession {
}

this._clearBreakpoints(toClear, 0, () => {
this._finishSetBreakpoints(response, path, scriptId, lbs, sourcemap);
done(nodeResponse);
});

} else {
this.sendNodeResponse(response, nodeResponse);
done(nodeResponse);
}

});
}
}

/**
* Recursive function for deleting node breakpoints.
Expand Down Expand Up @@ -1920,6 +1995,8 @@ export class NodeDebugSession extends DebugSession {
const line = body.sourceLine;
const column = body.sourceColumn;
this.rememberEntryLocation(path, line, column);
} else if (id === this._firstLineBreakId) {
reason = NodeDebugSession.FIRST_LINE_REASON;
} else {
reason = NodeDebugSession.BREAKPOINT_REASON;
}
Expand Down
134 changes: 71 additions & 63 deletions src/node/sourceMaps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ export interface ISourceMaps {
* line and column are 0 based.
*/
MapToSource(path: string, line: number, column: number): MappingResult;

/*
* Parse generated source and save mappings for that source if found.
*/
ParseGeneratedSource(pathToGenerated: string, generatedSource: string): void;
}


Expand All @@ -41,6 +46,7 @@ export class SourceMaps implements ISourceMaps {
public static TRACE = false;

private static SOURCE_MAPPING_MATCHER = new RegExp("//[#@] ?sourceMappingURL=(.+)$");
private static DATA_URI_MATCHER = new RegExp('data:application\/json(?:;(.*?|\b)),(.*)$');

private _generatedToSourceMaps: { [id: string] : SourceMap; } = {}; // generated -> source file
private _sourceToGeneratedMaps: { [id: string] : SourceMap; } = {}; // source file -> generated
Expand Down Expand Up @@ -84,6 +90,51 @@ export class SourceMaps implements ISourceMaps {
return null;
}

public ParseGeneratedSource(pathToGenerated: string, generatedSource: string): SourceMap {
let map: SourceMap = null;

// try to find a source map URL in the generated source
let map_path: string = null;
const uri = this._findSourceMapInGeneratedSource(generatedSource);
if (uri) {
const uriParts = uri.match(SourceMaps.DATA_URI_MATCHER);
if (uriParts) {
const json = new Buffer(uriParts[2], uriParts[1] || 'utf8').toString();
try {
if (json) {
map = new SourceMap(pathToGenerated, json);
this._generatedToSourceMaps[pathToGenerated] = map;
return;
}
}
catch (e) {
console.error(`FindGeneratedToSourceMapping: exception while processing data url (${e})`);
return;
}
}
else {
map_path = uri;
}
}

// if path is relative make it absolute
if (map_path && !Path.isAbsolute(map_path)) {
map_path = PathUtils.makePathAbsolute(pathToGenerated, map_path);
}

if (map_path === null || !FS.existsSync(map_path)) {
// try to find map file next to the generated source
map_path = pathToGenerated + ".map";
}

if (FS.existsSync(map_path)) {
map = this._createSourceMap(map_path, pathToGenerated);
if (map) {
this._generatedToSourceMaps[pathToGenerated] = map;
}
}
}

//---- private -----------------------------------------------------------------------

private _findSourceToGeneratedMapping(pathToSource: string): SourceMap {
Expand Down Expand Up @@ -150,60 +201,22 @@ export class SourceMaps implements ISourceMaps {
}

private _findGeneratedToSourceMapping(pathToGenerated: string): SourceMap {
if (pathToGenerated in this._generatedToSourceMaps) {
return this._generatedToSourceMaps[pathToGenerated];
}

if (pathToGenerated) {

if (pathToGenerated in this._generatedToSourceMaps) {
return this._generatedToSourceMaps[pathToGenerated];
}

let map: SourceMap = null;

// try to find a source map URL in the generated source
let map_path: string = null;
const uri = this._findSourceMapInGeneratedSource(pathToGenerated);
if (uri) {
if (uri.indexOf("data:application/json;base64,") >= 0) {
const pos = uri.indexOf(',');
if (pos > 0) {
const data = uri.substr(pos+1);
try {
const buffer = new Buffer(data, 'base64');
const json = buffer.toString();
if (json) {
map = new SourceMap(pathToGenerated, json);
this._generatedToSourceMaps[pathToGenerated] = map;
return map;
}
}
catch (e) {
console.error(`FindGeneratedToSourceMapping: exception while processing data url (${e})`);
}
}
} else {
map_path = uri;
}
}

// if path is relative make it absolute
if (map_path && !Path.isAbsolute(map_path)) {
map_path = PathUtils.makePathAbsolute(pathToGenerated, map_path);
}

if (map_path === null || !FS.existsSync(map_path)) {
// try to find map file next to the generated source
map_path = pathToGenerated + ".map";
let contents;
try {
contents = FS.readFileSync(pathToGenerated).toString();
} catch (e) {
return null;
}

if (FS.existsSync(map_path)) {
map = this._createSourceMap(map_path, pathToGenerated);
if (map) {
this._generatedToSourceMaps[pathToGenerated] = map;
return map;
}
}
this.ParseGeneratedSource(pathToGenerated, contents);
}
return null;

return this._generatedToSourceMaps[pathToGenerated] || null;
}

private _createSourceMap(map_path: string, path: string): SourceMap {
Expand All @@ -218,21 +231,16 @@ export class SourceMaps implements ISourceMaps {
}

// find "//# sourceMappingURL=<url>"
private _findSourceMapInGeneratedSource(pathToGenerated: string): string {

try {
const contents = FS.readFileSync(pathToGenerated).toString();
const lines = contents.split('\n');
for (let line of lines) {
const matches = SourceMaps.SOURCE_MAPPING_MATCHER.exec(line);
if (matches && matches.length === 2) {
const uri = matches[1].trim();
return uri;
}
private _findSourceMapInGeneratedSource(contents: string): string {
const lines = contents.split('\n');
for (let line of lines) {
const matches = SourceMaps.SOURCE_MAPPING_MATCHER.exec(line);
if (matches && matches.length === 2) {
const uri = matches[1].trim();
return uri;
}
} catch (e) {
// ignore exception
}

return null;
}
}
Expand Down Expand Up @@ -283,7 +291,7 @@ class SourceMap {
*/
public doesOriginateFrom(absPath: string): boolean {
for (let name of this._sources) {
const p = Path.join(this._sourceRoot, name);
const p = Path.resolve(this._sourceRoot, name);
if (p === absPath) {
return true;
}
Expand Down