66using System . Threading ;
77using System . Threading . Tasks ;
88using Microsoft . Extensions . Logging ;
9- using Newtonsoft . Json ;
109using Newtonsoft . Json . Linq ;
1110using System . IO ;
1211using Microsoft . CodeAnalysis . CSharp . Syntax ;
@@ -82,68 +81,86 @@ public async Task<JObject> GetValueFromObject(JToken objRet, CancellationToken t
8281 return null ;
8382 }
8483
85- public async Task < JObject > TryToRunOnLoadedClasses ( string varName , CancellationToken token )
84+ public async Task < ( JObject containerObject , string remaining ) > ResolveStaticMembersInStaticTypes ( string varName , CancellationToken token )
8685 {
8786 string classNameToFind = "" ;
8887 string [ ] parts = varName . Split ( "." ) ;
89- var typeId = - 1 ;
90- foreach ( string part in parts )
88+ var store = await proxy . LoadStore ( sessionId , token ) ;
89+ var methodInfo = ctx . CallStack . FirstOrDefault ( s => s . Id == scopeId ) . Method . Info ;
90+
91+ int typeId = - 1 ;
92+ for ( int i = 0 ; i < parts . Length ; i ++ )
9193 {
94+ string part = parts [ i ] . Trim ( ) ;
95+
9296 if ( classNameToFind . Length > 0 )
9397 classNameToFind += "." ;
94- classNameToFind += part . Trim ( ) ;
98+ classNameToFind += part ;
99+
95100 if ( typeId != - 1 )
96101 {
97- var fields = await sdbHelper . GetTypeFields ( sessionId , typeId , token ) ;
98- foreach ( var field in fields )
99- {
100- if ( field . Name == part . Trim ( ) )
101- {
102- var isInitialized = await sdbHelper . TypeIsInitialized ( sessionId , typeId , token ) ;
103- if ( isInitialized == 0 )
104- {
105- isInitialized = await sdbHelper . TypeInitialize ( sessionId , typeId , token ) ;
106- }
107- var valueRet = await sdbHelper . GetFieldValue ( sessionId , typeId , field . Id , token ) ;
108- return await GetValueFromObject ( valueRet , token ) ;
109- }
110- }
111- var methodId = await sdbHelper . GetPropertyMethodIdByName ( sessionId , typeId , part . Trim ( ) , token ) ;
112- if ( methodId != - 1 )
102+ string remaining = null ;
103+ JObject memberObject = await FindStaticMemberInType ( part , typeId ) ;
104+ if ( memberObject != null && i < parts . Length - 1 )
105+ remaining = string . Join ( '.' , parts [ ( i + 1 ) ..] ) ;
106+
107+ return ( memberObject , remaining ) ;
108+ }
109+
110+ if ( ! string . IsNullOrEmpty ( methodInfo . TypeInfo . Namespace ) )
111+ typeId = await FindStaticTypeId ( methodInfo . TypeInfo . Namespace + "." + classNameToFind ) ;
112+ if ( typeId == - 1 )
113+ typeId = await FindStaticTypeId ( classNameToFind ) ;
114+ }
115+
116+ return ( null , null ) ;
117+
118+ async Task < JObject > FindStaticMemberInType ( string name , int typeId )
119+ {
120+ var fields = await sdbHelper . GetTypeFields ( sessionId , typeId , token ) ;
121+ foreach ( var field in fields )
122+ {
123+ if ( field . Name != name )
124+ continue ;
125+
126+ var isInitialized = await sdbHelper . TypeIsInitialized ( sessionId , typeId , token ) ;
127+ if ( isInitialized == 0 )
113128 {
114- var commandParamsObj = new MemoryStream ( ) ;
115- var commandParamsObjWriter = new MonoBinaryWriter ( commandParamsObj ) ;
116- commandParamsObjWriter . Write ( 0 ) ; //param count
117- var retMethod = await sdbHelper . InvokeMethod ( sessionId , commandParamsObj . ToArray ( ) , methodId , "methodRet" , token ) ;
118- return await GetValueFromObject ( retMethod , token ) ;
129+ isInitialized = await sdbHelper . TypeInitialize ( sessionId , typeId , token ) ;
119130 }
131+ var valueRet = await sdbHelper . GetFieldValue ( sessionId , typeId , field . Id , token ) ;
132+
133+ return await GetValueFromObject ( valueRet , token ) ;
120134 }
121- var store = await proxy . LoadStore ( sessionId , token ) ;
122- var info = ctx . CallStack . FirstOrDefault ( s => s . Id == scopeId ) . Method . Info ;
123- var classNameToFindWithNamespace =
124- string . IsNullOrEmpty ( info . TypeInfo . Namespace ) ?
125- classNameToFind :
126- info . TypeInfo . Namespace + "." + classNameToFind ;
127135
128- foreach ( var asm in store . assemblies )
136+ var methodId = await sdbHelper . GetPropertyMethodIdByName ( sessionId , typeId , name , token ) ;
137+ if ( methodId != - 1 )
129138 {
130- if ( await TryGetTypeIdFromName ( classNameToFindWithNamespace , asm ) )
131- break ;
132- if ( await TryGetTypeIdFromName ( classNameToFind , asm ) )
133- break ;
139+ var commandParamsObj = new MemoryStream ( ) ;
140+ var commandParamsObjWriter = new MonoBinaryWriter ( commandParamsObj ) ;
141+ commandParamsObjWriter . Write ( 0 ) ; //param count
142+ var retMethod = await sdbHelper . InvokeMethod ( sessionId , commandParamsObj . ToArray ( ) , methodId , "methodRet" , token ) ;
143+ return await GetValueFromObject ( retMethod , token ) ;
134144 }
135145
136- async Task < bool > TryGetTypeIdFromName ( string typeName , AssemblyInfo assembly )
146+ return null ;
147+ }
148+
149+ async Task < int > FindStaticTypeId ( string typeName )
150+ {
151+ foreach ( var asm in store . assemblies )
137152 {
138- var type = assembly . GetTypeByName ( typeName ) ;
153+ var type = asm . GetTypeByName ( typeName ) ;
139154 if ( type == null )
140- return false ;
155+ continue ;
141156
142- typeId = await sdbHelper . GetTypeIdFromToken ( sessionId , assembly . DebugId , type . Token , token ) ;
143- return true ;
157+ int id = await sdbHelper . GetTypeIdFromToken ( sessionId , asm . DebugId , type . Token , token ) ;
158+ if ( id != - 1 )
159+ return id ;
144160 }
161+
162+ return - 1 ;
145163 }
146- return null ;
147164 }
148165
149166 // Checks Locals, followed by `this`
@@ -153,9 +170,6 @@ public async Task<JObject> Resolve(string varName, CancellationToken token)
153170 if ( varName . Contains ( '(' ) )
154171 return null ;
155172
156- string [ ] parts = varName . Split ( "." ) ;
157- JObject rootObject = null ;
158-
159173 if ( scopeCache . MemberReferences . TryGetValue ( varName , out JObject ret ) ) {
160174 return ret ;
161175 }
@@ -164,66 +178,98 @@ public async Task<JObject> Resolve(string varName, CancellationToken token)
164178 return await GetValueFromObject ( valueRet , token ) ;
165179 }
166180
167- foreach ( string part in parts )
181+ string [ ] parts = varName . Split ( "." ) ;
182+ if ( parts . Length == 0 )
183+ return null ;
184+
185+ JObject retObject = await ResolveAsLocalOrThisMember ( parts [ 0 ] ) ;
186+ if ( retObject != null && parts . Length > 1 )
187+ retObject = await ResolveAsInstanceMember ( string . Join ( '.' , parts [ 1 ..] ) , retObject ) ;
188+
189+ if ( retObject == null )
168190 {
169- string partTrimmed = part . Trim ( ) ;
170- if ( partTrimmed == "" )
171- return null ;
172- if ( rootObject != null )
191+ ( retObject , string remaining ) = await ResolveStaticMembersInStaticTypes ( varName , token ) ;
192+ if ( ! string . IsNullOrEmpty ( remaining ) )
173193 {
174- if ( rootObject ? [ "subtype" ] ? . Value < string > ( ) == "null" )
175- return null ;
176- if ( DotnetObjectId . TryParse ( rootObject ? [ "objectId" ] ? . Value < string > ( ) , out DotnetObjectId objectId ) )
194+ if ( retObject ? [ "subtype" ] ? . Value < string > ( ) == "null" )
177195 {
178- var rootResObj = await proxy . RuntimeGetPropertiesInternal ( sessionId , objectId , null , token ) ;
179- var objRet = rootResObj . FirstOrDefault ( objPropAttr => objPropAttr [ "name" ] . Value < string > ( ) == partTrimmed ) ;
180- if ( objRet == null )
181- return null ;
182-
183- rootObject = await GetValueFromObject ( objRet , token ) ;
196+ // NRE on null.$remaining
197+ retObject = null ;
198+ }
199+ else
200+ {
201+ retObject = await ResolveAsInstanceMember ( remaining , retObject ) ;
184202 }
185- continue ;
186203 }
204+ }
205+
206+ scopeCache . MemberReferences [ varName ] = retObject ;
207+ return retObject ;
208+
209+ async Task < JObject > ResolveAsLocalOrThisMember ( string name )
210+ {
187211 if ( scopeCache . Locals . Count == 0 && ! localsFetched )
188212 {
189213 Result scope_res = await proxy . GetScopeProperties ( sessionId , scopeId , token ) ;
190214 if ( scope_res . IsErr )
191215 throw new Exception ( $ "BUG: Unable to get properties for scope: { scopeId } . { scope_res } ") ;
192216 localsFetched = true ;
193217 }
194- if ( scopeCache . Locals . TryGetValue ( partTrimmed , out JObject obj ) )
195- {
196- rootObject = obj [ "value" ] ? . Value < JObject > ( ) ;
197- }
198- else if ( scopeCache . Locals . TryGetValue ( "this" , out JObject objThis ) )
218+
219+ if ( scopeCache . Locals . TryGetValue ( name , out JObject obj ) )
220+ return obj [ "value" ] ? . Value < JObject > ( ) ;
221+
222+ if ( ! scopeCache . Locals . TryGetValue ( "this" , out JObject objThis ) )
223+ return null ;
224+
225+ if ( ! DotnetObjectId . TryParse ( objThis ? [ "value" ] ? [ "objectId" ] ? . Value < string > ( ) , out DotnetObjectId objectId ) )
226+ return null ;
227+
228+ var rootResObj = await proxy . RuntimeGetPropertiesInternal ( sessionId , objectId , null , token ) ;
229+ var objRet = rootResObj . FirstOrDefault ( objPropAttr => objPropAttr [ "name" ] . Value < string > ( ) == name ) ;
230+ if ( objRet != null )
231+ return await GetValueFromObject ( objRet , token ) ;
232+
233+ return null ;
234+ }
235+
236+ async Task < JObject > ResolveAsInstanceMember ( string expr , JObject baseObject )
237+ {
238+ JObject resolvedObject = baseObject ;
239+ string [ ] parts = expr . Split ( '.' ) ;
240+ for ( int i = 0 ; i < parts . Length ; i ++ )
199241 {
200- if ( partTrimmed == "this" )
201- {
202- rootObject = objThis ? [ "value" ] . Value < JObject > ( ) ;
203- }
204- else if ( DotnetObjectId . TryParse ( objThis ? [ "value" ] ? [ "objectId" ] ? . Value < string > ( ) , out DotnetObjectId objectId ) )
242+ string partTrimmed = parts [ i ] . Trim ( ) ;
243+ if ( partTrimmed . Length == 0 )
244+ return null ;
245+
246+ if ( ! DotnetObjectId . TryParse ( resolvedObject ? [ "objectId" ] ? . Value < string > ( ) , out DotnetObjectId objectId ) )
247+ return null ;
248+
249+ var resolvedResObj = await proxy . RuntimeGetPropertiesInternal ( sessionId , objectId , null , token ) ;
250+ var objRet = resolvedResObj . FirstOrDefault ( objPropAttr => objPropAttr [ "name" ] . Value < string > ( ) == partTrimmed ) ;
251+ if ( objRet == null )
252+ return null ;
253+
254+ resolvedObject = await GetValueFromObject ( objRet , token ) ;
255+ if ( resolvedObject == null )
256+ return null ;
257+
258+ if ( resolvedObject [ "subtype" ] ? . Value < string > ( ) == "null" )
205259 {
206- var rootResObj = await proxy . RuntimeGetPropertiesInternal ( sessionId , objectId , null , token ) ;
207- var objRet = rootResObj . FirstOrDefault ( objPropAttr => objPropAttr [ "name" ] . Value < string > ( ) == partTrimmed ) ;
208- if ( objRet != null )
260+ if ( i < parts . Length - 1 )
209261 {
210- rootObject = await GetValueFromObject ( objRet , token ) ;
211- }
212- else
213- {
214- rootObject = await TryToRunOnLoadedClasses ( varName , token ) ;
215- return rootObject ;
262+ // there is some parts remaining, and can't
263+ // do null.$remaining
264+ return null ;
216265 }
266+
267+ return resolvedObject ;
217268 }
218269 }
270+
271+ return resolvedObject ;
219272 }
220- if ( rootObject == null )
221- {
222- rootObject = await TryToRunOnLoadedClasses ( varName , token ) ;
223- return rootObject ;
224- }
225- scopeCache . MemberReferences [ varName ] = rootObject ;
226- return rootObject ;
227273 }
228274
229275 public async Task < JObject > Resolve ( ElementAccessExpressionSyntax elementAccess , Dictionary < string , JObject > memberAccessValues , JObject indexObject , CancellationToken token )
0 commit comments