@@ -21,6 +21,7 @@ const {
2121 StringPrototypeIncludes,
2222 StringPrototypeSlice,
2323 StringPrototypeStartsWith,
24+ StringPrototypeTrim,
2425 Symbol,
2526} = primordials ;
2627
@@ -87,6 +88,8 @@ const kInspectPortRegex = /^--inspect-port=(\d+)$/;
8788 * @typedef {object } Probe
8889 * @property {string } expr Expression to evaluate on hit.
8990 * @property {ProbeTarget } target User's original --probe request shape.
91+ * @property {string } [condition] Condition from --cond. V8 only breaks when it is truthy.
92+ * Scoped to the location, so probes sharing a location all carry the same value.
9093 * @property {number } maxHit Per-probe hit limit from --max-hit. Infinity when unlimited.
9194 * @property {number } hits Count of hits observed.
9295 */
@@ -130,6 +133,12 @@ function formatTargetText(target) {
130133 return column === undefined ? `${ suffix } :${ line } ` : `${ suffix } :${ line } :${ column } ` ;
131134}
132135
136+ // Identity of a probe location. Probes sharing a key share one V8 breakpoint,
137+ // so this must stay in sync between condition validation and breakpoint setup.
138+ function locationKey ( target ) {
139+ return `${ target . suffix } \n${ target . line } \n${ target . column ?? '' } ` ;
140+ }
141+
133142function formatPendingProbeLocations ( probes , pending ) {
134143 const seen = new SafeSet ( ) ;
135144 for ( const probeIndex of pending ) {
@@ -391,6 +400,22 @@ function parseProbeTokens(tokens, args) {
391400 probe . maxHit = parseUnsignedInteger ( token . value , 'max-hit' ) ;
392401 break ;
393402 }
403+ case 'cond' : {
404+ if ( probes . length === 0 ) {
405+ throw new ERR_DEBUGGER_STARTUP_ERROR ( 'Unexpected --cond before --probe' ) ;
406+ }
407+ // A blank condition does not act as a real predicate in V8 (an empty
408+ // string always breaks), so reject it rather than silently mislead.
409+ if ( token . value === undefined || StringPrototypeTrim ( token . value ) === '' ) {
410+ throw new ERR_DEBUGGER_STARTUP_ERROR ( `Missing value for ${ token . rawName } ` ) ;
411+ }
412+ const probe = probes [ probes . length - 1 ] ;
413+ if ( probe . condition !== undefined ) {
414+ throw new ERR_DEBUGGER_STARTUP_ERROR ( 'A --probe can have at most one --cond' ) ;
415+ }
416+ probe . condition = token . value ;
417+ break ;
418+ }
394419 default :
395420 if ( probes . length > 0 ) {
396421 throw new ERR_DEBUGGER_STARTUP_ERROR (
@@ -410,6 +435,21 @@ function parseProbeTokens(tokens, args) {
410435 'Probe mode requires at least one --probe <loc> --expr <expr> group' ) ;
411436 }
412437
438+ // V8 allows only one breakpoint per location, so probes sharing a location
439+ // cannot carry different conditions.
440+ const conditionByLocation = new SafeMap ( ) ;
441+ for ( const { target, condition } of probes ) {
442+ const key = locationKey ( target ) ;
443+ if ( conditionByLocation . has ( key ) ) {
444+ if ( conditionByLocation . get ( key ) !== condition ) {
445+ throw new ERR_DEBUGGER_STARTUP_ERROR (
446+ `Probes at ${ formatTargetText ( target ) } must use the same --cond (or none)` ) ;
447+ }
448+ } else {
449+ conditionByLocation . set ( key , condition ) ;
450+ }
451+ }
452+
413453 const childArgv = ArrayPrototypeSlice ( args , childStartIndex ) ;
414454 if ( childArgv . length === 0 ) {
415455 throw new ERR_DEBUGGER_STARTUP_ERROR ( 'Probe mode requires a child script' ) ;
@@ -479,8 +519,8 @@ class ProbeInspectorSession {
479519 this . resolveCompletion = resolve ;
480520 /** @type {Probe[] } */
481521 this . probes = ArrayPrototypeMap ( options . probes ,
482- ( { expr, target, maxHit } ) =>
483- ( { expr, target, maxHit : maxHit ?? Infinity , hits : 0 } ) ) ;
522+ ( { expr, target, maxHit, condition } ) =>
523+ ( { expr, target, condition , maxHit : maxHit ?? Infinity , hits : 0 } ) ) ;
484524 this . onChildOutput = FunctionPrototypeBind ( this . onChildOutput , this ) ;
485525 this . onChildExit = FunctionPrototypeBind ( this . onChildExit , this ) ;
486526 this . onClientClose = FunctionPrototypeBind ( this . onClientClose , this ) ;
@@ -887,17 +927,19 @@ class ProbeInspectorSession {
887927 const uniqueTargets = new SafeMap ( ) ;
888928
889929 for ( let probeIndex = 0 ; probeIndex < this . probes . length ; probeIndex ++ ) {
890- const { target } = this . probes [ probeIndex ] ;
891- const key = `${ target . suffix } \n${ target . line } \n${ target . column ?? '' } ` ;
930+ const { target, condition } = this . probes [ probeIndex ] ;
931+ // Probes at the same location share one V8 breakpoint. parseProbeTokens has
932+ // already ensured they carry the same condition.
933+ const key = locationKey ( target ) ;
892934 let entry = uniqueTargets . get ( key ) ;
893935 if ( entry === undefined ) {
894- entry = { target, probeIndices : [ ] } ;
936+ entry = { target, condition , probeIndices : [ ] } ;
895937 uniqueTargets . set ( key , entry ) ;
896938 }
897939 ArrayPrototypePush ( entry . probeIndices , probeIndex ) ;
898940 }
899941
900- for ( const { target, probeIndices } of uniqueTargets . values ( ) ) {
942+ for ( const { target, condition , probeIndices } of uniqueTargets . values ( ) ) {
901943 // On Windows, normalize backslashes to forward slashes so the regex matches
902944 // V8 script URLs which always use forward slashes.
903945 const normalizedFile = process . platform === 'win32' ?
@@ -918,6 +960,9 @@ class ProbeInspectorSession {
918960 // the inspector bind to the first executable column.
919961 params . columnNumber = target . column - 1 ;
920962 }
963+ if ( condition !== undefined ) {
964+ params . condition = condition ;
965+ }
921966
922967 const result = await this . callCdp ( 'Debugger.setBreakpointByUrl' , params ) ;
923968 debug ( 'breakpoint set: id=%s urlRegex=%s locations=%j' ,
@@ -943,9 +988,10 @@ class ProbeInspectorSession {
943988 code : exitCode ,
944989 report : {
945990 v : kProbeVersion ,
946- probes : ArrayPrototypeMap ( this . probes , ( { expr, target, maxHit } ) => {
947- // Omit an unlimited maxHit, as Infinity would serialize to null in JSON.
991+ probes : ArrayPrototypeMap ( this . probes , ( { expr, target, maxHit, condition } ) => {
948992 const probe = { expr, target } ;
993+ if ( condition !== undefined ) { probe . condition = condition ; }
994+ // Omit an unlimited maxHit, as Infinity would serialize to null in JSON.
949995 if ( maxHit !== Infinity ) { probe . maxHit = maxHit ; }
950996 return probe ;
951997 } ) ,
0 commit comments