22
33import java .lang .ref .Cleaner ;
44import java .lang .ref .WeakReference ;
5+ import java .util .Collection ;
56import java .util .ArrayList ;
67import java .util .Arrays ;
78import java .util .HashMap ;
89import java .util .HashSet ;
910import java .util .List ;
11+ import java .util .Set ;
1012import java .util .concurrent .CompletableFuture ;
1113import java .util .concurrent .ConcurrentHashMap ;
1214import java .util .concurrent .atomic .AtomicBoolean ;
2426import java .util .concurrent .ExecutorService ;
2527import java .util .concurrent .Executors ;
2628
29+ import static luceedebug .coreinject .Iife .iife ;
30+
2731import luceedebug .*;
2832
2933public class LuceeVm implements ILuceeVm {
@@ -122,6 +126,20 @@ private static class ReplayableCfBreakpointRequest {
122126 * and ask it if it itself is bound? Does `isEnabled` yield that, or is that just "we asked for it to be enabled"?
123127 */
124128 final BreakpointRequest maybeNull_jdwpBreakpointRequest ;
129+
130+ @ Override
131+ public boolean equals (Object vv ) {
132+ if (!(vv instanceof ReplayableCfBreakpointRequest )) {
133+ return false ;
134+ }
135+ var v = (ReplayableCfBreakpointRequest )vv ;
136+
137+ return ideAbsPath .equals (v .ideAbsPath )
138+ && serverAbsPath .equals (v .serverAbsPath )
139+ && line == v .line
140+ && id == v .id
141+ && expr .equals (v .expr );
142+ }
125143
126144 ReplayableCfBreakpointRequest (String ideAbsPath , String serverAbsPath , int line , int id , String expr ) {
127145 this .ideAbsPath = ideAbsPath ;
@@ -141,15 +159,15 @@ private static class ReplayableCfBreakpointRequest {
141159 this .maybeNull_jdwpBreakpointRequest = jdwpBreakpointRequest ;
142160 }
143161
144- static List <BreakpointRequest > getJdwpRequests (ArrayList <ReplayableCfBreakpointRequest > vs ) {
162+ static List <BreakpointRequest > getJdwpRequests (Collection <ReplayableCfBreakpointRequest > vs ) {
145163 return vs
146164 .stream ()
147165 .filter (v -> v .maybeNull_jdwpBreakpointRequest != null )
148166 .map (v -> v .maybeNull_jdwpBreakpointRequest )
149167 .collect (Collectors .toList ());
150168 }
151169
152- static BpLineAndId [] getLineInfo (ArrayList <ReplayableCfBreakpointRequest > vs ) {
170+ static BpLineAndId [] getLineInfo (Collection <ReplayableCfBreakpointRequest > vs ) {
153171 return vs
154172 .stream ()
155173 .map (v -> new BpLineAndId (v .ideAbsPath , v .serverAbsPath , v .line , v .id , v .expr ))
@@ -159,8 +177,16 @@ static BpLineAndId[] getLineInfo(ArrayList<ReplayableCfBreakpointRequest> vs) {
159177
160178 private final ThreadMap threadMap_ = new ThreadMap ();
161179 private final ExecutorService stepHandlerExecutor = Executors .newSingleThreadExecutor ();
162- private final ConcurrentHashMap </*canonical sourceAbsPath*/ String , ArrayList <ReplayableCfBreakpointRequest >> replayableBreakpointRequestsByAbsPath_ = new ConcurrentHashMap <>();
163- private final ConcurrentHashMap </*canonical absPath*/ String , KlassMap > klassMap_ = new ConcurrentHashMap <>();
180+ private final ConcurrentHashMap </*canonical sourceAbsPath*/ String , Set <ReplayableCfBreakpointRequest >> replayableBreakpointRequestsByAbsPath_ = new ConcurrentHashMap <>();
181+
182+ /**
183+ * Mapping of "abspath on disk" -> "class file info"
184+ * Where a single path on disk can map to zero-or-more associated class files.
185+ * Usually there is only 1 classfile per abspath, but runtime mappings can mean that a single file
186+ * like "/app/foo.cfc" maps to "myapp.foo" as well as "someOtherMapping.foo", where each mapping
187+ * is represented by a separate classfile.
188+ */
189+ private final ConcurrentHashMap </*canonical absPath*/ String , Set <KlassMap >> klassMap_ = new ConcurrentHashMap <>();
164190 private long JDWP_WORKER_CLASS_ID = 0 ;
165191 private ThreadReference JDWP_WORKER_THREADREF = null ;
166192
@@ -570,8 +596,12 @@ private void trackClassRef(ReferenceType refType) {
570596
571597 final var klassMap = maybeNull_klassMap ; // definitely non-null
572598
573- var replayableBreakpointRequests = replayableBreakpointRequestsByAbsPath_ .get (klassMap .sourceName .transformed );
574- klassMap_ .put (klassMap .sourceName .transformed , klassMap );
599+ Set <ReplayableCfBreakpointRequest > replayableBreakpointRequests = replayableBreakpointRequestsByAbsPath_ .get (klassMap .sourceName .transformed );
600+
601+ klassMap_
602+ .computeIfAbsent (klassMap .sourceName .transformed , _z -> new HashSet <>())
603+ .add (klassMap );
604+
575605 if (replayableBreakpointRequests != null ) {
576606 rebindBreakpoints (klassMap .sourceName .transformed , replayableBreakpointRequests );
577607 }
@@ -737,16 +767,22 @@ static KlassMap maybeNull_tryBuildKlassMap(Config config, ReferenceType refType)
737767 // unreachable
738768 return null ;
739769 }
770+
771+ @ Override
772+ public boolean equals (Object e ) {
773+ if (e instanceof KlassMap ) {
774+ return ((KlassMap )e ).sourceName .equals (this .sourceName );
775+ }
776+ return false ;
777+ }
740778 }
741779
742780 private AtomicInteger breakpointID = new AtomicInteger ();
743781 private int nextBreakpointID () {
744782 return breakpointID .incrementAndGet ();
745783 }
746784
747- public void rebindBreakpoints (String serverAbsPath , ArrayList <ReplayableCfBreakpointRequest > cfBpRequests ) {
748- System .out .println ("Rebinding breakpoints for " + serverAbsPath );
749-
785+ public void rebindBreakpoints (String serverAbsPath , Collection <ReplayableCfBreakpointRequest > cfBpRequests ) {
750786 var changedBreakpoints = __internal__bindBreakpoints (serverAbsPath , ReplayableCfBreakpointRequest .getLineInfo (cfBpRequests ));
751787
752788 if (breakpointsChangedCallback != null ) {
@@ -777,8 +813,25 @@ private BpLineAndId[] freshBpLineAndIdRecordsFromLines(OriginalAndTransformedStr
777813 }
778814
779815 var result = new BpLineAndId [lines .length ];
816+
817+ Set <ReplayableCfBreakpointRequest > bpInfo = replayableBreakpointRequestsByAbsPath_ .get (absPath .transformed );
818+
780819 for (var i = 0 ; i < lines .length ; ++i ) {
781- result [i ] = new BpLineAndId (absPath .original , absPath .transformed , lines [i ], nextBreakpointID (), exprs [i ]);
820+ final int line = lines [i ];
821+
822+ int id = iife (() -> {
823+ if (bpInfo == null ) {
824+ return nextBreakpointID ();
825+ }
826+ for (var z : bpInfo ) {
827+ if (z .line == line ) {
828+ return z .id ;
829+ }
830+ }
831+ return nextBreakpointID ();
832+ });
833+
834+ result [i ] = new BpLineAndId (absPath .original , absPath .transformed , line , id , exprs [i ]);
782835 }
783836 return result ;
784837 }
@@ -792,12 +845,12 @@ public IBreakpoint[] bindBreakpoints(OriginalAndTransformedString absPath, int[]
792845 * i.e. the IDE might say "/foo/bar/baz.cfc" but we are only aware of "/app-host-container/foo/bar/baz.cfc" or etc.
793846 */
794847 private IBreakpoint [] __internal__bindBreakpoints (String serverAbsPath , BpLineAndId [] lineInfo ) {
795- final var klassMap = klassMap_ .get (serverAbsPath );
848+ final Set < KlassMap > klassMapSet = klassMap_ .get (serverAbsPath );
796849
797- if (klassMap == null ) {
798- var replayable = new ArrayList < ReplayableCfBreakpointRequest >( );
850+ if (klassMapSet == null ) {
851+ var replayable = replayableBreakpointRequestsByAbsPath_ . computeIfAbsent ( serverAbsPath , _z -> new HashSet <>() );
799852
800- var result = new Breakpoint [lineInfo .length ];
853+ IBreakpoint [] result = new Breakpoint [lineInfo .length ];
801854 for (int i = 0 ; i < lineInfo .length ; i ++) {
802855 final var ideAbsPath = lineInfo [i ].ideAbsPath ;
803856 final var shadow_serverAbsPath = lineInfo [i ].serverAbsPath ; // should be same as first arg to this method, kind of redundant
@@ -809,12 +862,19 @@ private IBreakpoint[] __internal__bindBreakpoints(String serverAbsPath, BpLineAn
809862 replayable .add (new ReplayableCfBreakpointRequest (ideAbsPath , shadow_serverAbsPath , line , id , expr ));
810863 }
811864
812- replayableBreakpointRequestsByAbsPath_ .put (serverAbsPath , replayable );
813-
814865 return result ;
815866 }
816867
817- return __internal__idempotentBindBreakpoints (klassMap , lineInfo );
868+ IBreakpoint [] bpListPerMapping = new IBreakpoint [0 ];
869+
870+ clearExistingBreakpoints (serverAbsPath );
871+
872+ for (KlassMap mapping : klassMapSet ) {
873+ bpListPerMapping = __internal__idempotentBindBreakpoints (mapping , lineInfo );
874+ }
875+
876+ // return just the last one
877+ return bpListPerMapping ;
818878 }
819879
820880
@@ -823,9 +883,7 @@ private IBreakpoint[] __internal__bindBreakpoints(String serverAbsPath, BpLineAn
823883 * Seems we're not allowed to inspect the jdwp-native id, but we can attach our own
824884 */
825885 private IBreakpoint [] __internal__idempotentBindBreakpoints (KlassMap klassMap , BpLineAndId [] lineInfo ) {
826- clearExistingBreakpoints (klassMap .sourceName .transformed );
827-
828- final var replayable = new ArrayList <ReplayableCfBreakpointRequest >();
886+ final var replayable = replayableBreakpointRequestsByAbsPath_ .computeIfAbsent (klassMap .sourceName .transformed , _z -> new HashSet <>());
829887 final var result = new ArrayList <IBreakpoint >();
830888
831889 for (int i = 0 ; i < lineInfo .length ; ++i ) {
@@ -864,7 +922,7 @@ private IBreakpoint[] __internal__idempotentBindBreakpoints(KlassMap klassMap, B
864922 * returns an array of the line numbers the old breakpoints were bound to
865923 */
866924 private void clearExistingBreakpoints (String absPath ) {
867- var replayable = replayableBreakpointRequestsByAbsPath_ .get (absPath );
925+ Set < ReplayableCfBreakpointRequest > replayable = replayableBreakpointRequestsByAbsPath_ .get (absPath );
868926
869927 // "just do it" in all cases
870928 replayableBreakpointRequestsByAbsPath_ .remove (absPath );
@@ -1030,7 +1088,9 @@ public String dumpAsJSON(int dapVariablesReference) {
10301088 public String [] getTrackedCanonicalFileNames () {
10311089 final var result = new ArrayList <String >();
10321090 for (var klassMap : klassMap_ .values ()) {
1033- result .add (klassMap .sourceName .transformed );
1091+ for (var mapping : klassMap ) {
1092+ result .add (mapping .sourceName .transformed );
1093+ }
10341094 }
10351095 return result .toArray (size -> new String [size ]);
10361096 }
0 commit comments