Skip to content

Commit f9685ed

Browse files
knightburtonyichoi
authored andcommitted
Add function breakpoint request
Implement the setFunctionBreakPointRequest in the DebugSession. This will allow the users to set a breakpoint to a function (in iotjs and jerry this means the first statement in the function core). IoT.js-VSCode-DCO-1.0-Signed-off-by: Imre Kiss [email protected]
1 parent 53eb470 commit f9685ed

File tree

3 files changed

+264
-2
lines changed

3 files changed

+264
-2
lines changed

src/IotjsDebugger.ts

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ class IotjsDebugSession extends DebugSession {
7272
): void {
7373
// This debug adapter implements the configurationDoneRequest.
7474
response.body.supportsConfigurationDoneRequest = true;
75-
response.body.supportsFunctionBreakpoints = false;
75+
response.body.supportsFunctionBreakpoints = true;
7676
response.body.supportsEvaluateForHovers = false;
7777
response.body.supportsStepBack = false;
7878
response.body.supportsRestartRequest = false;
@@ -259,6 +259,76 @@ class IotjsDebugSession extends DebugSession {
259259
this.sendResponse(response);
260260
}
261261

262+
protected async setFunctionBreakPointsRequest(
263+
response: DebugProtocol.SetFunctionBreakpointsResponse, args: DebugProtocol.SetFunctionBreakpointsArguments
264+
): Promise<void> {
265+
const vscodeFunctionBreakpoints: DebugProtocol.FunctionBreakpoint[] = args.breakpoints;
266+
267+
try {
268+
let persistingFBreakpoints: TemporaryBreakpoint[] = [];
269+
let newFBreakpoints: TemporaryBreakpoint[] = [];
270+
let undefinedFBreakpoins: TemporaryBreakpoint[] = [];
271+
272+
await Promise.all(this._protocolhandler.getSources().map(async (src, id) => {
273+
const scriptId = id + 1;
274+
const inactiveFBps: Breakpoint[] = this._protocolhandler.getInactiveFunctionBreakpointsByScriptId(scriptId);
275+
const vscodeFunctionBreakpointNames: string[] = vscodeFunctionBreakpoints.map(b => b.name);
276+
277+
const newFBs = inactiveFBps.filter(b => vscodeFunctionBreakpointNames.indexOf(b.func.name) !== -1);
278+
279+
// Get the new breakpoints.
280+
newFBreakpoints = [
281+
...newFBreakpoints,
282+
...await Promise.all(newFBs.map(async (breakpoint) => {
283+
try {
284+
await this._protocolhandler.updateBreakpoint(breakpoint, true);
285+
return <TemporaryBreakpoint>{verified: true, line: breakpoint.line};
286+
} catch (error) {
287+
this.log(error.message, LOG_LEVEL.ERROR);
288+
return <TemporaryBreakpoint>{verified: false, line: breakpoint.line, message: (<Error>error).message};
289+
}
290+
}))
291+
];
292+
293+
// Get the persists breakpoints.
294+
const possibleFBs = this._protocolhandler.getPossibleFunctionBreakpointsByScriptId(scriptId);
295+
persistingFBreakpoints = [
296+
...persistingFBreakpoints,
297+
...possibleFBs.filter(b => {
298+
return newFBs.map(b => b.func.name).indexOf(b.func.name) === -1 &&
299+
vscodeFunctionBreakpointNames.indexOf(b.func.name) !== -1;
300+
}).map(b => <TemporaryBreakpoint>{verified: true, line: b.line})
301+
];
302+
303+
// Get the removalbe breakpoints.
304+
const activeFBs: Breakpoint[] = this._protocolhandler.getActiveFunctionBreakpointsByScriptId(scriptId);
305+
const removeBps: Breakpoint[] = activeFBs.filter(b => {
306+
return vscodeFunctionBreakpointNames.indexOf(b.func.name) === -1;
307+
});
308+
309+
removeBps.forEach(async b => {
310+
const jerryBreakpoint = this._protocolhandler.findBreakpoint(scriptId, b.line);
311+
await this._protocolhandler.updateBreakpoint(jerryBreakpoint, false);
312+
});
313+
314+
undefinedFBreakpoins = [
315+
...undefinedFBreakpoins,
316+
...vscodeFunctionBreakpoints.filter(b => {
317+
return possibleFBs.map(p => p.func.name).indexOf(b.name) === -1;
318+
}).map(b => <TemporaryBreakpoint>{verified: false, message: 'No function found'})
319+
];
320+
}));
321+
322+
response.body = { breakpoints: [...persistingFBreakpoints, ...newFBreakpoints, ...undefinedFBreakpoins] };
323+
} catch (error) {
324+
this.log(error, LOG_LEVEL.ERROR);
325+
this.sendErrorResponse(response, <Error>error);
326+
return;
327+
}
328+
329+
this.sendResponse(response);
330+
}
331+
262332
protected async evaluateRequest(
263333
response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments
264334
): Promise<void> {

src/JerryProtocolHandler.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,11 @@ export class JerryDebugProtocolHandler {
254254
return array;
255255
}
256256

257+
public getSources(): ParsedSource[] {
258+
// The first element is a dummy because sources is 1-indexed
259+
return this.sources.slice(1);
260+
}
261+
257262
public getSource(scriptId: number): string {
258263
if (scriptId < this.sources.length) {
259264
return this.sources[scriptId].source || '';
@@ -646,6 +651,25 @@ export class JerryDebugProtocolHandler {
646651
return this.activeBreakpoints.filter(b => b.scriptId === scriptId);
647652
}
648653

654+
public getActiveFunctionBreakpointsByScriptId(scriptId: number): Breakpoint[] {
655+
return this.getPossibleFunctionBreakpointsByScriptId(scriptId).filter(b => b.activeIndex !== -1);
656+
}
657+
658+
public getInactiveFunctionBreakpointsByScriptId(scriptId: number): Breakpoint[] {
659+
return this.getPossibleFunctionBreakpointsByScriptId(scriptId).filter(b => b.activeIndex === -1);
660+
}
661+
662+
public getPossibleFunctionBreakpointsByScriptId(scriptId: number): Breakpoint[] {
663+
if (scriptId <= 0 || scriptId >= this.lineLists.length) {
664+
throw new Error('invalid script id');
665+
}
666+
667+
const keys: string[] = Object.keys(this.functions).filter(f => this.functions[f].scriptId === scriptId);
668+
const bps: Breakpoint[] = keys.map(key => this.functions[key].lines[Object.keys(this.functions[key].lines)[0]]);
669+
670+
return bps.length ? bps.filter(b => b.func.name !== '') : [];
671+
}
672+
649673
public evaluate(expression: string): Promise<any> {
650674
if (!this.lastBreakpointHit) {
651675
return Promise.reject(new Error('attempted eval while not at breakpoint'));
@@ -691,7 +715,7 @@ export class JerryDebugProtocolHandler {
691715
throw new Error('no breakpoint found');
692716
}
693717

694-
public updateBreakpoint(breakpoint: Breakpoint, enable: boolean): Promise<number> {
718+
public updateBreakpoint(breakpoint: Breakpoint, enable: boolean): Promise<void> {
695719
let breakpointId;
696720

697721
if (enable) {

src/test/JerryProtocolHandler.test.ts

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,40 @@ suite('JerryProtocolHandler', () => {
366366
});
367367
});
368368

369+
suite('getSources', () => {
370+
test('returns a sources array', () => {
371+
const sources = [{
372+
// Dummy, because the sources is 1-indexed
373+
}, {
374+
name: 'ble.js',
375+
source: 'console.log("This is a module...");'
376+
}, {
377+
name: 'led-stripe.js',
378+
source: 'const x = 10;'
379+
}];
380+
381+
const handler = new JerryDebugProtocolHandler({});
382+
(handler as any).sources = sources;
383+
384+
const result = handler.getSources();
385+
assert.strictEqual(result[0].name, 'ble.js');
386+
assert.strictEqual(result[1].source, 'const x = 10;');
387+
assert.strictEqual(result.length, 2);
388+
});
389+
390+
test('returns an empty array', () => {
391+
const sources = [{
392+
// Dummy, because the sources is 1-indexed
393+
}];
394+
395+
const handler = new JerryDebugProtocolHandler({});
396+
(handler as any).sources = sources;
397+
398+
const result = handler.getSources();
399+
assert.strictEqual(result.length, 0);
400+
});
401+
});
402+
369403
suite('evaluate', () => {
370404
test('sends single eval packet for short expressions', () => {
371405
const debugClient = {
@@ -455,6 +489,140 @@ suite('JerryProtocolHandler', () => {
455489
});
456490
});
457491

492+
suite('getActiveFunctionBreakpointsByScriptId', () => {
493+
test('return a breakpoints array', () => {
494+
const name = 'crane.js';
495+
const scriptId = 11;
496+
const func = {
497+
scriptId,
498+
lines: [
499+
{
500+
activeIndex: 3,
501+
func: { name }
502+
},
503+
{
504+
activeIndex: -1,
505+
func: { name }
506+
},
507+
{
508+
activeIndex: -1,
509+
func: { name }
510+
},
511+
],
512+
};
513+
const handler = new JerryDebugProtocolHandler({});
514+
(handler as any).functions = [ func ];
515+
(handler as any).lineLists = {
516+
[scriptId]: [[func], ['a', func], [func, 'b']],
517+
};
518+
const breakpoints = handler.getActiveFunctionBreakpointsByScriptId(scriptId);
519+
assert.strictEqual(breakpoints[0].activeIndex, 3);
520+
assert.strictEqual(breakpoints[0].func.name, name);
521+
assert.strictEqual(breakpoints.length, 1);
522+
});
523+
524+
test('throws error on invalid scriptId (0)', () => {
525+
const scriptId = 0;
526+
const handler = new JerryDebugProtocolHandler({});
527+
assert.throws(() => handler.getActiveFunctionBreakpointsByScriptId(scriptId), 'invalid script id');
528+
});
529+
530+
test('throws error on invalid scriptId (greater than linelist length)', () => {
531+
const scriptId = 4;
532+
const handler = new JerryDebugProtocolHandler({});
533+
assert.throws(() => handler.getActiveFunctionBreakpointsByScriptId(scriptId), 'invalid script id');
534+
});
535+
536+
test('return an empty array in case of no active functionbreakpoints', () => {
537+
const name = 'lumbermill.js';
538+
const scriptId = 1;
539+
const func = {
540+
scriptId,
541+
lines: [
542+
{
543+
activeIndex: -1,
544+
func: { name }
545+
},
546+
],
547+
};
548+
const handler = new JerryDebugProtocolHandler({});
549+
(handler as any).functions = [ func ];
550+
(handler as any).lineLists = {
551+
[scriptId]: [[func], ['a', func], [func, 'b']],
552+
};
553+
554+
const breakpoints = handler.getActiveFunctionBreakpointsByScriptId(scriptId);
555+
assert.strictEqual(breakpoints.length, 0);
556+
});
557+
});
558+
559+
suite('getInactiveFunctionBreakpointsByScriptId', () => {
560+
test('return a breakpoins array', () => {
561+
const name = 'missing-data.js';
562+
const scriptId = 9;
563+
const func = {
564+
scriptId,
565+
lines: [
566+
{
567+
activeIndex: -1,
568+
func: { name }
569+
},
570+
{
571+
activeIndex: 4,
572+
func: { name }
573+
},
574+
{
575+
activeIndex: 5,
576+
func: { name }
577+
},
578+
],
579+
};
580+
const handler = new JerryDebugProtocolHandler({});
581+
(handler as any).functions = [ func ];
582+
(handler as any).lineLists = {
583+
[scriptId]: [[func], ['a', func], [func, 'b']],
584+
};
585+
const breakpoints = handler.getInactiveFunctionBreakpointsByScriptId(scriptId);
586+
assert.strictEqual(breakpoints[0].activeIndex, -1);
587+
assert.strictEqual(breakpoints[0].func.name, name);
588+
assert.strictEqual(breakpoints.length, 1);
589+
});
590+
591+
test('throws error on invalid scriptId (0)', () => {
592+
const scriptId = 0;
593+
const handler = new JerryDebugProtocolHandler({});
594+
assert.throws(() => handler.getInactiveFunctionBreakpointsByScriptId(scriptId), 'invalid script id');
595+
});
596+
597+
test('throws error on invalid scriptId (greater than linelist length)', () => {
598+
const scriptId = 10;
599+
const handler = new JerryDebugProtocolHandler({});
600+
assert.throws(() => handler.getInactiveFunctionBreakpointsByScriptId(scriptId), 'invalid script id');
601+
});
602+
603+
test('return an empty array in case of no inactive functionbreakpoints', () => {
604+
const name = 'dust.js';
605+
const scriptId = 7;
606+
const func = {
607+
scriptId,
608+
lines: [
609+
{
610+
activeIndex: 4,
611+
func: { name }
612+
},
613+
],
614+
};
615+
const handler = new JerryDebugProtocolHandler({});
616+
(handler as any).functions = [ func ];
617+
(handler as any).lineLists = {
618+
[scriptId]: [[func], ['a', func], [func, 'b']],
619+
};
620+
621+
const breakpoints = handler.getInactiveFunctionBreakpointsByScriptId(scriptId);
622+
assert.strictEqual(breakpoints.length, 0);
623+
});
624+
});
625+
458626
suite('updateBreakpoint', () => {
459627
const debugClient = {
460628
send: sinon.spy(),

0 commit comments

Comments
 (0)