@@ -33,9 +33,9 @@ import {V8Inspector} from './v8inspector';
3333
3434export class BreakpointData {
3535 constructor (
36- public id : string , public active : boolean ,
36+ public id : inspector . Debugger . BreakpointId ,
3737 public apiBreakpoint : apiTypes . Breakpoint ,
38- public parsedCondition : estree . Node ,
38+ public parsedCondition : estree . Node , public locationStr : string ,
3939 public compile : null | ( ( src : string ) => string ) ) { }
4040}
4141
@@ -46,9 +46,17 @@ export class InspectorDebugApi implements debugapi.DebugApi {
4646 breakpoints : { [ id : string ] : BreakpointData } = { } ;
4747 sourcemapper : SourceMapper ;
4848 session : inspector . Session ;
49- scriptmapper : { [ id : string ] : any } = { } ;
50-
49+ // TODO: listeners, scrpitmapper, location mapper and breakpointmapper can use
50+ // Map in the future after resolving Map.prototype.get(key) returns V or
51+ // undefined.
5152 listeners : { [ id : string ] : utils . Listener } = { } ;
53+ // scriptmapper maps scriptId to actual script path.
54+ scriptMapper : { [ id : string ] : any } = { } ;
55+ // locationmapper maps location string to a list of stackdriver breakpoint id.
56+ locationMapper : { [ id : string ] : apiTypes . BreakpointId [ ] } = { } ;
57+ // breakpointmapper maps v8/inspector breakpoint id to a list of
58+ // stackdriver breakpoint id.
59+ breakpointMapper : { [ id : string ] : apiTypes . BreakpointId [ ] } = { } ;
5260 numBreakpoints = 0 ;
5361 v8Inspector : V8Inspector ;
5462 constructor (
@@ -61,7 +69,7 @@ export class InspectorDebugApi implements debugapi.DebugApi {
6169 this . session = new inspector . Session ( ) ;
6270 this . session . connect ( ) ;
6371 this . session . on ( 'Debugger.scriptParsed' , ( script ) => {
64- this . scriptmapper [ script . params . scriptId ] = script . params ;
72+ this . scriptMapper [ script . params . scriptId ] = script . params ;
6573 } ) ;
6674 this . session . post ( 'Debugger.enable' ) ;
6775 this . session . post ( 'Debugger.setBreakpointsActive' , { active : true } ) ;
@@ -127,11 +135,29 @@ export class InspectorDebugApi implements debugapi.DebugApi {
127135 cb , breakpoint , StatusMessage . BREAKPOINT_CONDITION ,
128136 utils . messages . V8_BREAKPOINT_CLEAR_ERROR ) ;
129137 }
130- const id = breakpoint . id ;
131- const result = this . v8Inspector . removeBreakpoint ( breakpointData . id ) ;
132- const breakpointId = this . breakpoints [ id ] . id ;
133- delete this . breakpoints [ id ] ;
134- delete this . listeners [ breakpointId ] ;
138+ let locationStr = breakpointData . locationStr ;
139+ const v8BreakpointId = breakpointData . id ;
140+
141+ // delete current breakpoint from locationmapper and breakpointmapper.
142+ utils . removeFirstOccurrenceInArray (
143+ this . locationMapper [ locationStr ] , breakpoint . id ) ;
144+ if ( this . locationMapper [ locationStr ] . length === 0 ) {
145+ delete this . locationMapper [ locationStr ] ;
146+ }
147+ utils . removeFirstOccurrenceInArray (
148+ this . breakpointMapper [ v8BreakpointId ] , breakpoint . id ) ;
149+ if ( this . breakpointMapper [ v8BreakpointId ] . length === 0 ) {
150+ delete this . breakpointMapper [ v8BreakpointId ] ;
151+ }
152+
153+ let result : { error ?: Error } = { } ;
154+ if ( ! this . breakpointMapper [ breakpointData . id ] ) {
155+ // When breakpointmapper does not countain current v8/inspector breakpoint
156+ // id, we should remove this breakpoint from v8.
157+ result = this . v8Inspector . removeBreakpoint ( breakpointData . id ) ;
158+ }
159+ delete this . breakpoints [ breakpoint . id ] ;
160+ delete this . listeners [ breakpoint . id ] ;
135161 this . numBreakpoints -- ;
136162 setImmediate ( function ( ) {
137163 if ( result . error ) {
@@ -143,25 +169,23 @@ export class InspectorDebugApi implements debugapi.DebugApi {
143169
144170 wait ( breakpoint : apiTypes . Breakpoint , callback : ( err ?: Error ) => void ) : void {
145171 // TODO: Address the case whree `breakpoint.id` is `null`.
146- const bp = this . breakpoints [ breakpoint . id as string ] ;
147172 const listener =
148173 this . onBreakpointHit . bind ( this , breakpoint , ( err : Error ) => {
149- this . listeners [ bp . id ] . enabled = false ;
174+ this . listeners [ breakpoint . id ] . enabled = false ;
150175 // This method is called from the debug event listener, which
151176 // swallows all exception. We defer the callback to make sure
152177 // the user errors aren't silenced.
153178 setImmediate ( function ( ) {
154179 callback ( err ) ;
155180 } ) ;
156181 } ) ;
157- this . listeners [ bp . id ] = { enabled : true , listener : listener } ;
182+ this . listeners [ breakpoint . id ] = { enabled : true , listener : listener } ;
158183 }
159184
160185 log ( breakpoint : apiTypes . Breakpoint ,
161186 print : ( format : string , exps : string [ ] ) => void ,
162187 shouldStop : ( ) => boolean ) : void {
163188 // TODO: Address the case whree `breakpoint.id` is `null`.
164- const bpId = this . breakpoints [ breakpoint . id as string ] . id ;
165189 let logsThisSecond = 0 ;
166190 let timesliceEnd = Date . now ( ) + 1000 ;
167191 // TODO: Determine why the Error argument is not used.
@@ -181,29 +205,30 @@ export class InspectorDebugApi implements debugapi.DebugApi {
181205 ( obj : any ) => JSON . stringify ( obj ) ) ) ;
182206 logsThisSecond ++ ;
183207 if ( shouldStop ( ) ) {
184- this . listeners [ bpId ] . enabled = false ;
208+ this . listeners [ breakpoint . id ] . enabled = false ;
185209 } else {
186210 if ( logsThisSecond >= this . config . log . maxLogsPerSecond ) {
187- this . listeners [ bpId ] . enabled = false ;
211+ this . listeners [ breakpoint . id ] . enabled = false ;
188212 setTimeout ( ( ) => {
189213 // listeners[num] may have been deleted by `clear` during the
190214 // async hop. Make sure it is valid before setting a property
191215 // on it.
192- if ( ! shouldStop ( ) && this . listeners [ bpId ] ) {
193- this . listeners [ bpId ] . enabled = true ;
216+ if ( ! shouldStop ( ) && this . listeners [ breakpoint . id ] ) {
217+ this . listeners [ breakpoint . id ] . enabled = true ;
194218 }
195219 } , this . config . log . logDelaySeconds * 1000 ) ;
196220 }
197221 }
198222 } ) ;
199- this . listeners [ bpId ] = { enabled : true , listener : listener } ;
223+ this . listeners [ breakpoint . id ] = { enabled : true , listener : listener } ;
200224 }
201225
202226 disconnect ( ) : void {
203227 this . session . disconnect ( ) ;
204228 }
205229
206230 numBreakpoints_ ( ) : number {
231+ // Tracks the number of stackdriver breakpoints.
207232 return Object . keys ( this . breakpoints ) . length ;
208233 }
209234
@@ -312,37 +337,68 @@ export class InspectorDebugApi implements debugapi.DebugApi {
312337 if ( line === 1 ) {
313338 column += debugapi . MODULE_WRAP_PREFIX_LENGTH - 1 ;
314339 }
315- let condition ;
316- if ( breakpoint . condition ) condition = breakpoint . condition ;
317- let res = this . v8Inspector . setBreakpointByUrl (
318- line - 1 , matchingScript , undefined , column - 1 , condition ) ;
319- if ( res . error || ! res . response ) {
340+
341+ let result =
342+ this . setAndStoreBreakpoint ( breakpoint , line , column , matchingScript ) ;
343+ if ( ! result ) {
320344 return utils . setErrorStatusAndCallback (
321345 cb , breakpoint , StatusMessage . BREAKPOINT_SOURCE_LOCATION ,
322346 utils . messages . V8_BREAKPOINT_ERROR ) ;
323347 }
324- this . breakpoints [ breakpoint . id as string ] = new BreakpointData (
325- res . response . breakpointId , true , breakpoint , ast as estree . Program ,
326- compile ) ;
348+
349+ this . breakpoints [ breakpoint . id ] = new BreakpointData (
350+ result . v8BreakpointId , breakpoint , ast as estree . Program ,
351+ result . locationStr , compile ) ;
352+
327353 this . numBreakpoints ++ ;
328354 setImmediate ( function ( ) {
329355 cb ( null ) ;
330356 } ) ; // success.
331357 }
332358
359+ private setAndStoreBreakpoint (
360+ breakpoint : apiTypes . Breakpoint , line : number , column : number ,
361+ matchingScript : string ) :
362+ { v8BreakpointId : inspector . Debugger . BreakpointId ,
363+ locationStr : string } | null {
364+ // location Str will be a JSON string of Stackdriver breakpoint location.
365+ // It will be used as key at locationmapper to ensure there will be no
366+ // duplicate breakpoints at the same location.
367+ const locationStr = JSON . stringify ( breakpoint . location ) ;
368+ let v8BreakpointId ; // v8/inspector breakpoint id
369+ if ( ! this . locationMapper [ locationStr ] ) {
370+ // The first time when a breakpoint was set to this location.
371+
372+ let res = this . v8Inspector . setBreakpointByUrl ( {
373+ lineNumber : line - 1 ,
374+ url : matchingScript ,
375+ columnNumber : column - 1 ,
376+ condition : breakpoint . condition || undefined
377+ } ) ;
378+ if ( res . error || ! res . response ) {
379+ // Error case.
380+ return null ;
381+ }
382+ v8BreakpointId = res . response . breakpointId ;
383+ this . locationMapper [ locationStr ] = [ ] ;
384+ this . breakpointMapper [ v8BreakpointId ] = [ ] ;
385+ } else {
386+ // Breakpoint found at this location. Acquire the v8/inspector breakpoint
387+ // id.
388+ v8BreakpointId = this . breakpoints [ this . locationMapper [ locationStr ] [ 0 ] ] . id ;
389+ }
390+
391+ // Adding current stackdriver breakpoint id to location mapper and
392+ // breakpoint mapper.
393+ this . locationMapper [ locationStr ] . push ( breakpoint . id ) ;
394+ this . breakpointMapper [ v8BreakpointId ] . push ( breakpoint . id ) ;
395+
396+ return { v8BreakpointId, locationStr} ;
397+ }
398+
333399 private onBreakpointHit (
334400 breakpoint : apiTypes . Breakpoint , callback : ( err : Error | null ) => void ,
335401 callFrames : inspector . Debugger . CallFrame [ ] ) : void {
336- // TODO: Address the situation where `breakpoint.id` is `null`.
337- const bp = this . breakpoints [ breakpoint . id as string ] ;
338- if ( ! bp . active ) {
339- // Breakpoint exists, but not active. We never disable breakpoints, so
340- // this is theoretically not possible. Perhaps this is possible if there
341- // is a second debugger present? Regardless, report the error.
342- return utils . setErrorStatusAndCallback (
343- callback , breakpoint , StatusMessage . BREAKPOINT_SOURCE_LOCATION ,
344- utils . messages . V8_BREAKPOINT_DISABLED ) ;
345- }
346402 // Breakpoint Hit
347403 const start = process . hrtime ( ) ;
348404 try {
@@ -363,15 +419,14 @@ export class InspectorDebugApi implements debugapi.DebugApi {
363419 const expressionErrors : Array < apiTypes . Variable | null > = [ ] ;
364420 const that = this ;
365421 // TODO: Address the case where `breakpoint.id` is `null`.
366- if ( breakpoint . expressions &&
367- this . breakpoints [ breakpoint . id as string ] . compile ) {
422+ if ( breakpoint . expressions && this . breakpoints [ breakpoint . id ] . compile ) {
368423 for ( let i = 0 ; i < breakpoint . expressions . length ; i ++ ) {
369424 try {
370425 // TODO: Address the case where `breakpoint.id` is `null`.
371426 breakpoint . expressions [ i ] =
372427 // TODO: Address the case where `compile` is `null`.
373- ( this . breakpoints [ breakpoint . id as string ] . compile as
374- ( text : string ) => string ) ( breakpoint . expressions [ i ] ) ;
428+ ( this . breakpoints [ breakpoint . id ] . compile as ( text : string ) =>
429+ string ) ( breakpoint . expressions [ i ] ) ;
375430 } catch ( e ) {
376431 this . logger . info (
377432 'Unable to compile watch expression >> ' +
@@ -394,17 +449,20 @@ export class InspectorDebugApi implements debugapi.DebugApi {
394449 } else {
395450 const frame = callFrames [ 0 ] ;
396451 const evaluatedExpressions = breakpoint . expressions . map ( function ( exp ) {
397- const result = state . evaluate ( exp , frame , that . v8Inspector ) ;
398- // TODO: Address the case where `result.mirror` is `undefined`.
399- return result . error ?
400- result . error :
401- ( result . object as inspector . Runtime . RemoteObject ) . value ( ) ;
452+ // returnByValue is set to true here so that the JSON string of the
453+ // value will be returned to log.
454+ const result = state . evaluate ( exp , frame , that . v8Inspector , true ) ;
455+ if ( result . error ) {
456+ return result . error ;
457+ } else {
458+ return ( result . object as inspector . Runtime . RemoteObject ) . value ;
459+ }
402460 } ) ;
403461 breakpoint . evaluatedExpressions = evaluatedExpressions ;
404462 }
405463 } else {
406464 const captured = state . capture (
407- callFrames , breakpoint , this . config , this . scriptmapper ,
465+ callFrames , breakpoint , this . config , this . scriptMapper ,
408466 this . v8Inspector ) ;
409467 if ( breakpoint . location ) {
410468 breakpoint . location . line = callFrames [ 0 ] . location . lineNumber + 1 ;
@@ -422,11 +480,13 @@ export class InspectorDebugApi implements debugapi.DebugApi {
422480 inspector . Debugger . PausedEventDataType ) {
423481 try {
424482 if ( ! params . hitBreakpoints ) return ;
425- const bpId : string = params . hitBreakpoints [ 0 ] ;
426- if ( this . listeners [ bpId ] . enabled ) {
427- this . logger . info ( '>>>breakpoint hit<<< number: ' + bpId ) ;
428- this . listeners [ bpId ] . listener ( params . callFrames ) ;
429- }
483+ const v8BreakpointId : string = params . hitBreakpoints [ 0 ] ;
484+ this . breakpointMapper [ v8BreakpointId ] . forEach ( ( id : string ) => {
485+ if ( this . listeners [ id ] . enabled ) {
486+ this . logger . info ( '>>>breakpoint hit<<< number: ' + id ) ;
487+ this . listeners [ id ] . listener ( params . callFrames ) ;
488+ }
489+ } ) ;
430490 } catch ( e ) {
431491 this . logger . warn ( 'Internal V8 error on breakpoint event: ' + e ) ;
432492 }
0 commit comments