Skip to content

Commit 5b2d11a

Browse files
committed
support non generic dictionary
1 parent 01153bf commit 5b2d11a

2 files changed

Lines changed: 109 additions & 3 deletions

File tree

tracer/src/Datadog.Trace/Debugger/Expressions/ProbeExpressionParser.Collection.cs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ private Expression Predicate(JsonTextReader reader, List<ParameterExpression> pa
4747
ReturnDefaultValueExpression();
4848
}
4949

50-
if (!IsSafeCollection(source.Type))
50+
if (!IsSafeCollection(source.Type) && !IsSafeNonGenericDictionary(source.Type))
5151
{
52-
throw new InvalidOperationException("Source must be an array or implement ICollection or IReadOnlyCollection");
52+
throw new InvalidOperationException("Source must be an array or implement ICollection, IReadOnlyCollection, or IDictionary");
5353
}
5454

5555
var itParameterType = GetIteratorParameterType(source.Type);
@@ -63,7 +63,7 @@ private Expression Predicate(JsonTextReader reader, List<ParameterExpression> pa
6363
var predicate = ParseTree(reader, new List<ParameterExpression> { Expression.Parameter(source.Type) }, itParameter);
6464
var lambda = Expression.Lambda(predicate, itParameter);
6565
var genericPredicateMethod = predicateMethod.MakeGenericMethod(itParameterType);
66-
callExpression = Expression.Call(null, genericPredicateMethod, source, lambda);
66+
callExpression = Expression.Call(null, genericPredicateMethod, PredicateSource(source, itParameterType), lambda);
6767
if (IsIEnumerable(callExpression.Type))
6868
{
6969
var toListMethod = ProbeExpressionParserHelper.GetMethodByReflection(typeof(Enumerable), nameof(Enumerable.ToList), null);
@@ -237,6 +237,17 @@ private Type GetIteratorParameterType(Type sourceType)
237237
throw new InvalidOperationException("Fail to determined the iterator parameter type");
238238
}
239239

240+
private Expression PredicateSource(Expression source, Type itParameterType)
241+
{
242+
if (itParameterType != typeof(DictionaryEntry) || !IsSafeNonGenericDictionary(source.Type))
243+
{
244+
return source;
245+
}
246+
247+
var castMethod = ProbeExpressionParserHelper.GetMethodByReflection(typeof(Enumerable), nameof(Enumerable.Cast), [typeof(IEnumerable)], [typeof(DictionaryEntry)]);
248+
return Expression.Call(null, castMethod, source);
249+
}
250+
240251
private bool TryGetCollectionIteratorProperty(ParameterExpression itParameter, string propertyName, out MemberExpression propertyExpression)
241252
{
242253
propertyExpression = null;
@@ -266,6 +277,11 @@ private bool IsSafeCollection(Type type)
266277
return type.IsArray || (IsMicrosoftType(type) && IsCollection(type));
267278
}
268279

280+
private bool IsSafeNonGenericDictionary(Type type)
281+
{
282+
return type != null && IsMicrosoftType(type) && typeof(IDictionary).IsAssignableFrom(type);
283+
}
284+
269285
private bool IsIEnumerable(Type type)
270286
{
271287
return IsSafeCollection(type) || type.GetInterface(nameof(IEnumerable)) != null;

tracer/test/Datadog.Trace.Tests/Debugger/DebuggerExpressionLanguageTests.cs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// </copyright>
55

66
using System;
7+
using System.Collections;
78
using System.Collections.Generic;
89
using System.IO;
910
using System.Linq;
@@ -216,6 +217,95 @@ public void ProbeExpressionParser_ValueTypeNull_UsesDefaultValue()
216217
Assert.True(compiled.Errors == null || compiled.Errors.Length == 0);
217218
}
218219

220+
[Theory]
221+
[InlineData("""
222+
{
223+
"any": [
224+
{
225+
"ref": "HashtableLocal"
226+
},
227+
{
228+
"and": [
229+
{
230+
"eq": [
231+
"@key",
232+
"hello"
233+
]
234+
},
235+
{
236+
"eq": [
237+
"@value",
238+
"world"
239+
]
240+
}
241+
]
242+
}
243+
]
244+
}
245+
""")]
246+
[InlineData("""
247+
{
248+
"all": [
249+
{
250+
"ref": "HashtableLocal"
251+
},
252+
{
253+
"ne": [
254+
"@value",
255+
"sun"
256+
]
257+
}
258+
]
259+
}
260+
""")]
261+
[InlineData("""
262+
{
263+
"any": [
264+
{
265+
"filter": [
266+
{
267+
"ref": "HashtableLocal"
268+
},
269+
{
270+
"eq": [
271+
"@key",
272+
"hello"
273+
]
274+
}
275+
]
276+
},
277+
{
278+
"eq": [
279+
"@value",
280+
"world"
281+
]
282+
}
283+
]
284+
}
285+
""")]
286+
public void ProbeExpressionParser_NonGenericDictionaryPredicates_CanUseKeyAndValue(string json)
287+
{
288+
var hashtable = new Hashtable
289+
{
290+
{ "hello", "world" },
291+
{ "goodbye", "moon" },
292+
};
293+
294+
var scopeMembers = CreateScopeMembers();
295+
scopeMembers.AddMember(new ScopeMember("HashtableLocal", typeof(Hashtable), hashtable, ScopeMemberKind.Local));
296+
297+
var compiled = ProbeExpressionParser<bool>.ParseExpression(json, scopeMembers);
298+
var result = compiled.Delegate(
299+
scopeMembers.InvocationTarget,
300+
scopeMembers.Return,
301+
scopeMembers.Duration,
302+
scopeMembers.Exception,
303+
scopeMembers.Members);
304+
305+
Assert.True(result);
306+
Assert.True(compiled.Errors == null || compiled.Errors.Length == 0);
307+
}
308+
219309
private async Task Test(string expressionTestFilePath)
220310
{
221311
// Arrange

0 commit comments

Comments
 (0)