@@ -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) {
130133return 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 ) {
134143const seen = new SafeSet ( ) ;
135144for ( const probeIndex of pending ) {
@@ -391,6 +400,22 @@ function parseProbeTokens(tokens, args) {
391400probe . maxHit = parseUnsignedInteger ( token . value , 'max-hit' ) ;
392401break ;
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+ }
394419default :
395420if ( probes . length > 0 ) {
396421throw new ERR_DEBUGGER_STARTUP_ERROR (
@@ -410,6 +435,24 @@ 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+ // All probes at the same location must share one condition. We split the
444+ // existence check since if one probe does not have a condition (= undefined),
445+ // then all probes at the location must also omit it.
446+ if ( conditionByLocation . has ( key ) ) {
447+ if ( conditionByLocation . get ( key ) !== condition ) {
448+ throw new ERR_DEBUGGER_STARTUP_ERROR (
449+ `Probes at ${ formatTargetText ( target ) } must use the same --cond (or none)` ) ;
450+ }
451+ } else {
452+ conditionByLocation . set ( key , condition ) ;
453+ }
454+ }
455+
413456const childArgv = ArrayPrototypeSlice ( args , childStartIndex ) ;
414457if ( childArgv . length === 0 ) {
415458throw new ERR_DEBUGGER_STARTUP_ERROR ( 'Probe mode requires a child script' ) ;
@@ -479,8 +522,8 @@ class ProbeInspectorSession {
479522this . resolveCompletion = resolve ;
480523/** @type {Probe[] } */
481524this . probes = ArrayPrototypeMap ( options . probes ,
482- ( { expr, target, maxHit } ) =>
483- ( { expr, target, maxHit : maxHit ?? Infinity , hits : 0 } ) ) ;
525+ ( { expr, target, maxHit, condition } ) =>
526+ ( { expr, target, condition , maxHit : maxHit ?? Infinity , hits : 0 } ) ) ;
484527this . onChildOutput = FunctionPrototypeBind ( this . onChildOutput , this ) ;
485528this . onChildExit = FunctionPrototypeBind ( this . onChildExit , this ) ;
486529this . onClientClose = FunctionPrototypeBind ( this . onClientClose , this ) ;
@@ -887,17 +930,19 @@ class ProbeInspectorSession {
887930const uniqueTargets = new SafeMap ( ) ;
888931
889932for ( 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 ?? '' } ` ;
933+ const { target, condition } = this . probes [ probeIndex ] ;
934+ // Probes at the same location share one V8 breakpoint. parseProbeTokens has
935+ // already ensured they carry the same condition.
936+ const key = locationKey ( target ) ;
892937let entry = uniqueTargets . get ( key ) ;
893938if ( entry === undefined ) {
894- entry = { target, probeIndices : [ ] } ;
939+ entry = { target, condition , probeIndices : [ ] } ;
895940uniqueTargets . set ( key , entry ) ;
896941}
897942ArrayPrototypePush ( entry . probeIndices , probeIndex ) ;
898943}
899944
900- for ( const { target, probeIndices } of uniqueTargets . values ( ) ) {
945+ for ( const { target, condition , probeIndices } of uniqueTargets . values ( ) ) {
901946// On Windows, normalize backslashes to forward slashes so the regex matches
902947// V8 script URLs which always use forward slashes.
903948const normalizedFile = process . platform === 'win32' ?
@@ -918,6 +963,9 @@ class ProbeInspectorSession {
918963// the inspector bind to the first executable column.
919964params . columnNumber = target . column - 1 ;
920965}
966+ if ( condition !== undefined ) {
967+ params . condition = condition ;
968+ }
921969
922970const result = await this . callCdp ( 'Debugger.setBreakpointByUrl' , params ) ;
923971debug ( 'breakpoint set: id=%s urlRegex=%s locations=%j' ,
@@ -943,9 +991,10 @@ class ProbeInspectorSession {
943991code : exitCode ,
944992report : {
945993v : kProbeVersion ,
946- probes : ArrayPrototypeMap ( this . probes , ( { expr, target, maxHit } ) => {
947- // Omit an unlimited maxHit, as Infinity would serialize to null in JSON.
994+ probes : ArrayPrototypeMap ( this . probes , ( { expr, target, maxHit, condition } ) => {
948995const probe = { expr, target } ;
996+ if ( condition !== undefined ) { probe . condition = condition ; }
997+ // Omit an unlimited maxHit, as Infinity would serialize to null in JSON.
949998if ( maxHit !== Infinity ) { probe . maxHit = maxHit ; }
950999return probe ;
9511000} ) ,
0 commit comments