12
12
using System . Threading . Tasks ;
13
13
using System . Threading ;
14
14
using Microsoft . Extensions . Logging ;
15
+ using System . Runtime . CompilerServices ;
15
16
16
17
namespace WebAssembly . Net . Debugging {
17
18
internal class BreakpointRequest {
19
+ public string Id { get ; private set ; }
18
20
public string Assembly { get ; private set ; }
19
21
public string File { get ; private set ; }
20
22
public int Line { get ; private set ; }
21
23
public int Column { get ; private set ; }
22
24
25
+ JObject request ;
26
+
27
+ public bool IsResolved => Assembly != null ;
28
+ public List < Breakpoint > Locations { get ; } = new List < Breakpoint > ( ) ;
29
+
23
30
public override string ToString ( ) {
24
31
return $ "BreakpointRequest Assembly: { Assembly } File: { File } Line: { Line } Column: { Column } ";
25
32
}
26
33
27
- public static BreakpointRequest Parse ( JObject args , DebugStore store )
34
+ public object AsSetBreakpointByUrlResponse ( )
35
+ => new { breakpointId = Id , locations = Locations . Select ( l => l . Location . AsLocation ( ) ) } ;
36
+
37
+ public static BreakpointRequest Parse ( string id , JObject args )
28
38
{
29
- // Events can potentially come out of order, so DebugStore may not be initialized
30
- // The BP being set in these cases are JS ones, which we can safely ignore
31
- if ( args == null || store == null )
32
- return null ;
39
+ var breakRequest = new BreakpointRequest ( ) {
40
+ Id = id ,
41
+ request = args
42
+ } ;
43
+ return breakRequest ;
44
+ }
33
45
34
- var url = args ? [ "url" ] ? . Value < string > ( ) ;
35
- if ( url == null ) {
36
- var urlRegex = args ? [ "urlRegex" ] . Value < string > ( ) ;
37
- var sourceFile = store ? . GetFileByUrlRegex ( urlRegex ) ;
46
+ public BreakpointRequest Clone ( )
47
+ => new BreakpointRequest { Id = Id , request = request } ;
38
48
39
- url = sourceFile ? . DotNetUrl ;
49
+ public bool IsMatch ( SourceFile sourceFile )
50
+ {
51
+ var url = request ? [ "url" ] ? . Value < string > ( ) ;
52
+ if ( url == null ) {
53
+ var urlRegex = request ? [ "urlRegex" ] . Value < string > ( ) ;
54
+ var regex = new Regex ( urlRegex ) ;
55
+ return regex . IsMatch ( sourceFile . Url . ToString ( ) ) || regex . IsMatch ( sourceFile . DocUrl ) ;
40
56
}
41
57
42
- if ( url != null && ! url . StartsWith ( "dotnet://" , StringComparison . Ordinal ) ) {
43
- var sourceFile = store . GetFileByUrl ( url ) ;
44
- url = sourceFile ? . DotNetUrl ;
45
- }
58
+ return sourceFile . Url . ToString ( ) == url || sourceFile . DotNetUrl == url ;
59
+ }
46
60
47
- if ( url == null )
48
- return null ;
61
+ public bool TryResolve ( SourceFile sourceFile )
62
+ {
63
+ if ( ! IsMatch ( sourceFile ) )
64
+ return false ;
49
65
50
- var parts = ParseDocumentUrl ( url ) ;
51
- if ( parts . Assembly == null )
52
- return null ;
66
+ var line = request ? [ "lineNumber" ] ? . Value < int > ( ) ;
67
+ var column = request ? [ "columnNumber" ] ? . Value < int > ( ) ;
53
68
54
- var line = args ? [ "lineNumber" ] ? . Value < int > ( ) ;
55
- var column = args ? [ "columnNumber" ] ? . Value < int > ( ) ;
56
69
if ( line == null || column == null )
57
- return null ;
70
+ return false ;
58
71
59
- return new BreakpointRequest ( ) {
60
- Assembly = parts . Assembly ,
61
- File = parts . DocumentPath ,
62
- Line = line . Value ,
63
- Column = column . Value
64
- } ;
72
+ Assembly = sourceFile . AssemblyName ;
73
+ File = sourceFile . DebuggerFileName ;
74
+ Line = line . Value ;
75
+ Column = column . Value ;
76
+ return true ;
77
+ }
78
+
79
+ public bool TryResolve ( DebugStore store )
80
+ {
81
+ if ( request == null || store == null )
82
+ return false ;
83
+
84
+ return store . AllSources ( ) . FirstOrDefault ( source => TryResolve ( source ) ) != null ;
65
85
}
66
86
67
87
static ( string Assembly , string DocumentPath ) ParseDocumentUrl ( string url )
@@ -99,7 +119,6 @@ public override string ToString ()
99
119
}
100
120
}
101
121
102
-
103
122
internal class CliLocation {
104
123
public CliLocation ( MethodInfo method , int offset )
105
124
{
@@ -111,7 +130,6 @@ public CliLocation (MethodInfo method, int offset)
111
130
public int Offset { get ; private set ; }
112
131
}
113
132
114
-
115
133
internal class SourceLocation {
116
134
SourceId id ;
117
135
int line ;
@@ -268,11 +286,26 @@ public MethodInfo (AssemblyInfo assembly, MethodDefinition methodDef, SourceFile
268
286
this . source = source ;
269
287
270
288
var sps = methodDef . DebugInformation . SequencePoints ;
271
- if ( sps != null && sps . Count > 0 ) {
272
- StartLocation = new SourceLocation ( this , sps [ 0 ] ) ;
273
- EndLocation = new SourceLocation ( this , sps [ sps . Count - 1 ] ) ;
289
+ if ( sps == null || sps . Count ( ) < 1 )
290
+ return ;
291
+
292
+ SequencePoint start = sps [ 0 ] ;
293
+ SequencePoint end = sps [ 0 ] ;
294
+
295
+ foreach ( var sp in sps ) {
296
+ if ( sp . StartLine < start . StartLine )
297
+ start = sp ;
298
+ else if ( sp . StartLine == start . StartLine && sp . StartColumn < start . StartColumn )
299
+ start = sp ;
300
+
301
+ if ( sp . EndLine > end . EndLine )
302
+ end = sp ;
303
+ else if ( sp . EndLine == end . EndLine && sp . EndColumn > end . EndColumn )
304
+ end = sp ;
274
305
}
275
306
307
+ StartLocation = new SourceLocation ( this , start ) ;
308
+ EndLocation = new SourceLocation ( this , end ) ;
276
309
}
277
310
278
311
public SourceLocation GetLocationByIl ( int pos )
@@ -556,7 +589,7 @@ class DebugItem {
556
589
public Task < byte [ ] [ ] > Data { get ; set ; }
557
590
}
558
591
559
- public async Task Load ( SessionId sessionId , string [ ] loaded_files , CancellationToken token )
592
+ public async IAsyncEnumerable < SourceFile > Load ( SessionId sessionId , string [ ] loaded_files , [ EnumeratorCancellation ] CancellationToken token )
560
593
{
561
594
static bool MatchPdb ( string asm , string pdb )
562
595
=> Path . ChangeExtension ( asm , "pdb" ) == pdb ;
@@ -585,20 +618,27 @@ static bool MatchPdb (string asm, string pdb)
585
618
}
586
619
587
620
foreach ( var step in steps ) {
621
+ AssemblyInfo assembly = null ;
588
622
try {
589
623
var bytes = await step . Data ;
590
- assemblies . Add ( new AssemblyInfo ( step . Url , bytes [ 0 ] , bytes [ 1 ] ) ) ;
624
+ assembly = new AssemblyInfo ( step . Url , bytes [ 0 ] , bytes [ 1 ] ) ;
591
625
} catch ( Exception e ) {
592
626
logger . LogDebug ( $ "Failed to Load { step . Url } ({ e . Message } )") ;
593
627
}
628
+ if ( assembly == null )
629
+ continue ;
630
+
631
+ assemblies . Add ( assembly ) ;
632
+ foreach ( var source in assembly . Sources )
633
+ yield return source ;
594
634
}
595
635
}
596
636
597
637
public IEnumerable < SourceFile > AllSources ( )
598
638
=> assemblies . SelectMany ( a => a . Sources ) ;
599
639
600
640
public SourceFile GetFileById ( SourceId id )
601
- => AllSources ( ) . FirstOrDefault ( f => f . SourceId . Equals ( id ) ) ;
641
+ => AllSources ( ) . SingleOrDefault ( f => f . SourceId . Equals ( id ) ) ;
602
642
603
643
public AssemblyInfo GetAssemblyByName ( string name )
604
644
=> assemblies . FirstOrDefault ( a => a . Name . Equals ( name , StringComparison . InvariantCultureIgnoreCase ) ) ;
@@ -612,15 +652,16 @@ static bool Match (SequencePoint sp, SourceLocation start, SourceLocation end)
612
652
var spStart = ( Line : sp . StartLine - 1 , Column : sp . StartColumn - 1 ) ;
613
653
var spEnd = ( Line : sp . EndLine - 1 , Column : sp . EndColumn - 1 ) ;
614
654
615
- if ( start . Line > spStart . Line )
655
+ if ( start . Line > spEnd . Line )
616
656
return false ;
617
- if ( start . Column > spStart . Column && start . Line == sp . StartLine )
657
+
658
+ if ( start . Column > spEnd . Column && start . Line == spEnd . Line )
618
659
return false ;
619
660
620
- if ( end . Line < spEnd . Line )
661
+ if ( end . Line < spStart . Line )
621
662
return false ;
622
663
623
- if ( end . Column < spEnd . Column && end . Line == spEnd . Line )
664
+ if ( end . Column < spStart . Column && end . Line == spStart . Line )
624
665
return false ;
625
666
626
667
return true ;
@@ -629,22 +670,25 @@ static bool Match (SequencePoint sp, SourceLocation start, SourceLocation end)
629
670
public List < SourceLocation > FindPossibleBreakpoints ( SourceLocation start , SourceLocation end )
630
671
{
631
672
//XXX FIXME no idea what todo with locations on different files
632
- if ( start . Id != end . Id )
673
+ if ( start . Id != end . Id ) {
674
+ logger . LogDebug ( $ "FindPossibleBreakpoints: documents differ (start: { start . Id } ) (end { end . Id } ") ;
633
675
return null ;
634
- var src_id = start . Id ;
676
+ }
635
677
636
- var doc = GetFileById ( src_id ) ;
678
+ var sourceId = start . Id ;
679
+
680
+ var doc = GetFileById ( sourceId ) ;
637
681
638
682
var res = new List < SourceLocation > ( ) ;
639
683
if ( doc == null ) {
640
- logger . LogDebug ( $ "Could not find document { src_id } ") ;
684
+ logger . LogDebug ( $ "Could not find document { sourceId } ") ;
641
685
return res ;
642
686
}
643
687
644
- foreach ( var m in doc . Methods ) {
645
- foreach ( var sp in m . methodDef . DebugInformation . SequencePoints ) {
646
- if ( Match ( sp , start , end ) )
647
- res . Add ( new SourceLocation ( m , sp ) ) ;
688
+ foreach ( var method in doc . Methods ) {
689
+ foreach ( var sequencePoint in method . methodDef . DebugInformation . SequencePoints ) {
690
+ if ( ! sequencePoint . IsHidden && Match ( sequencePoint , start , end ) )
691
+ res . Add ( new SourceLocation ( method , sequencePoint ) ) ;
648
692
}
649
693
}
650
694
return res ;
@@ -674,35 +718,26 @@ static bool Match (SequencePoint sp, int line, int column)
674
718
return true ;
675
719
}
676
720
677
- public SourceLocation FindBestBreakpoint ( BreakpointRequest req )
721
+ public IEnumerable < SourceLocation > FindBreakpointLocations ( BreakpointRequest request )
678
722
{
679
- var asm = assemblies . FirstOrDefault ( a => a . Name . Equals ( req . Assembly , StringComparison . OrdinalIgnoreCase ) ) ;
680
- var src = asm ? . Sources ? . FirstOrDefault ( s => s . DebuggerFileName . Equals ( req . File , StringComparison . OrdinalIgnoreCase ) ) ;
723
+ request . TryResolve ( this ) ;
681
724
682
- if ( src == null )
683
- return null ;
725
+ var asm = assemblies . FirstOrDefault ( a => a . Name . Equals ( request . Assembly , StringComparison . OrdinalIgnoreCase ) ) ;
726
+ var sourceFile = asm ? . Sources ? . SingleOrDefault ( s => s . DebuggerFileName . Equals ( request . File , StringComparison . OrdinalIgnoreCase ) ) ;
727
+
728
+ if ( sourceFile == null )
729
+ yield break ;
684
730
685
- foreach ( var m in src . Methods ) {
686
- foreach ( var sp in m . methodDef . DebugInformation . SequencePoints ) {
731
+ foreach ( var method in sourceFile . Methods ) {
732
+ foreach ( var sequencePoint in method . methodDef . DebugInformation . SequencePoints ) {
687
733
//FIXME handle multi doc methods
688
- if ( Match ( sp , req . Line , req . Column ) )
689
- return new SourceLocation ( m , sp ) ;
734
+ if ( ! sequencePoint . IsHidden && Match ( sequencePoint , request . Line , request . Column ) )
735
+ yield return new SourceLocation ( method , sequencePoint ) ;
690
736
}
691
737
}
692
-
693
- return null ;
694
738
}
695
739
696
740
public string ToUrl ( SourceLocation location )
697
741
=> location != null ? GetFileById ( location . Id ) . Url : "" ;
698
-
699
- public SourceFile GetFileByUrlRegex ( string urlRegex )
700
- {
701
- var regex = new Regex ( urlRegex ) ;
702
- return AllSources ( ) . FirstOrDefault ( file => regex . IsMatch ( file . Url . ToString ( ) ) || regex . IsMatch ( file . DocUrl ) ) ;
703
- }
704
-
705
- public SourceFile GetFileByUrl ( string url )
706
- => AllSources ( ) . FirstOrDefault ( file => file . Url . ToString ( ) == url ) ;
707
742
}
708
743
}
0 commit comments