Skip to content

Commit

Permalink
[Dynamic Instrumentation] DEBUG-2365 Support string lexicographic com…
Browse files Browse the repository at this point in the history
…parison (#5538)

## Summary of changes
Allow the customer to use `<` `<=` `>` `>=` with strings in the EL

## Test coverage
See tests in the PR
  • Loading branch information
dudikeleti authored Aug 5, 2024
1 parent bbac240 commit 9a4bfb5
Show file tree
Hide file tree
Showing 61 changed files with 874 additions and 237 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ private Expression BinaryOperation(JsonTextReader reader, List<ParameterExpressi
return ReturnDefaultValueExpression();
}

if (left.Type == typeof(string) && right.Type == typeof(string))
{
return StringLexicographicComparison(left, right, operand);
}

HandleDurationBinaryOperation(ref left, ref right);

switch (operand)
Expand Down Expand Up @@ -98,4 +103,28 @@ private void HandleDurationBinaryOperation(ref Expression left, ref Expression r
left = ConvertToDouble(left);
}
}

private Expression StringLexicographicComparison(Expression left, Expression right, string operand)
{
switch (operand)
{
case "==":
return Expression.Equal(left, right);
case "!=":
return Expression.NotEqual(left, right);
}

var compareOrdinal = Expression.Call(CompareOrdinalMethod(), new[] { left, right });
var zeroConstant = Expression.Constant(0);
return operand switch
{
">" => Expression.GreaterThan(compareOrdinal, zeroConstant),
">=" => Expression.GreaterThanOrEqual(compareOrdinal, zeroConstant),
"<" => Expression.LessThan(compareOrdinal, zeroConstant),
"<=" => Expression.LessThanOrEqual(compareOrdinal, zeroConstant),
_ => throw new ArgumentException("Unknown operand" + operand, nameof(operand))
};

System.Reflection.MethodInfo CompareOrdinalMethod() => ProbeExpressionParserHelper.GetMethodByReflection(typeof(string), "CompareOrdinal", new[] { typeof(string), typeof(string) });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Datadog.Trace.Debugger.Snapshots;
using Datadog.Trace.Util;
using static Datadog.Trace.Debugger.Expressions.ProbeExpressionParserHelper;
using Enumerable = System.Linq.Enumerable;

namespace Datadog.Trace.Debugger.Expressions;

Expand Down Expand Up @@ -125,14 +126,21 @@ private Expression DumpFieldsExpression(Expression expression, List<ParameterExp
{
const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly;
var getTypeMethod = GetMethodByReflection(typeof(object), nameof(object.GetType), Type.EmptyTypes);
var getFieldsMethod = GetMethodByReflection(typeof(Type), nameof(Type.GetFields), new[] { typeof(BindingFlags) });
var getFields = Expression.Call(Expression.Call(expression, getTypeMethod), getFieldsMethod, Expression.Constant(flags));
var stringBuilderAppend = GetMethodByReflection(typeof(StringBuilder), nameof(StringBuilder.Append), new[] { typeof(string) });
var getFieldsMethod = GetMethodByReflection(typeof(Type), nameof(Type.GetFields), [typeof(BindingFlags)]);
var orderByMethod = GetMethodByReflection(typeof(System.Linq.Enumerable), nameof(System.Linq.Enumerable.OrderBy), [typeof(IEnumerable<>), typeof(Func<,>)], [typeof(FieldInfo), typeof(int)]);
var toArray = GetMethodByReflection(typeof(System.Linq.Enumerable), nameof(System.Linq.Enumerable.ToArray), [typeof(IEnumerable<>)], [typeof(FieldInfo)]);

ParameterExpression parameterExp = Expression.Parameter(typeof(FieldInfo), "fieldInfo");
MemberExpression propertyExp = Expression.Property(parameterExp, "MetadataToken");
Expression<Func<FieldInfo, int>> lambdaExp = Expression.Lambda<Func<FieldInfo, int>>(propertyExp, parameterExp);
var fieldInfoArray = Expression.Call(Expression.Call(expression, getTypeMethod), getFieldsMethod, Expression.Constant(flags));
var fieldInfoOrderedArray = Expression.Call(null, toArray, Expression.Call(null, orderByMethod, fieldInfoArray, lambdaExp));
var stringBuilderAppend = GetMethodByReflection(typeof(StringBuilder), nameof(StringBuilder.Append), [typeof(string)]);
var expressions = new List<Expression>();

var fields = Expression.Variable(typeof(FieldInfo[]), "fieldsArray");
scopeMembers.Add(fields);
expressions.Add(Expression.Assign(fields, getFields));
expressions.Add(Expression.Assign(fields, fieldInfoOrderedArray));

var result = Expression.Variable(typeof(StringBuilder), "fieldValues");
scopeMembers.Add(result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

Expand All @@ -16,19 +17,35 @@ internal static class ProbeExpressionParserHelper

internal static readonly Type UndefinedValueType = typeof(UndefinedValue);

internal static MethodInfo GetMethodByReflection(Type type, string name, Type[] parametersTypes)
internal static MethodInfo GetMethodByReflection(Type type, string name, Type[] parametersTypes, Type[] genericArguments = null)
{
const BindingFlags bindingFlags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.InvokeMethod |
BindingFlags.NonPublic | BindingFlags.Public;

var reflectionMethodIdentifier = new ReflectionMethodIdentifier(type, name, parametersTypes);
var reflectionMethodIdentifier = new ReflectionMethodIdentifier(type, name, parametersTypes, genericArguments);
return Methods.GetOrAdd(reflectionMethodIdentifier, GetMethodByReflectionInternal);

MethodInfo GetMethodByReflectionInternal(ReflectionMethodIdentifier methodIdentifier)
{
var method = parametersTypes == null ?
MethodInfo method = null;
if (genericArguments != null)
{
method = methodIdentifier.Type.GetMethods().
Where(m =>
m.Name == methodIdentifier.MethodName &&
m.GetParameters().Length == methodIdentifier.Parameters.Length &&
m.ContainsGenericParameters &&
m.GetGenericArguments().Length == methodIdentifier.GenericArguments.Length).
SingleOrDefault(m => m.GetParameters().Select(pi => pi.ParameterType.Name).SequenceEqual(methodIdentifier.Parameters.Select(p => p.Name)));

method = method?.MakeGenericMethod(methodIdentifier.GenericArguments);
}
else
{
method = parametersTypes == null ?
methodIdentifier.Type.GetMethod(methodIdentifier.MethodName, bindingFlags) :
methodIdentifier.Type.GetMethod(methodIdentifier.MethodName, bindingFlags, null, methodIdentifier.Parameters, null);
}

if (method == null)
{
Expand All @@ -41,18 +58,21 @@ MethodInfo GetMethodByReflectionInternal(ReflectionMethodIdentifier methodIdenti

internal readonly record struct ReflectionMethodIdentifier
{
internal ReflectionMethodIdentifier(Type type, string methodName, Type[] parameters)
internal ReflectionMethodIdentifier(Type type, string methodName, Type[] parameters, Type[] genericArguments)
{
Type = type;
MethodName = methodName;
Parameters = parameters;
GenericArguments = genericArguments;
}

internal Type Type { get; }

internal string MethodName { get; }

internal Type[] Parameters { get; }

internal Type[] GenericArguments { get; }
}

internal readonly ref struct ExpressionBodyAndParameters
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ public DebuggerExpressionLanguageTests()
IntNumber = 42,
DoubleNumber = 3.14159,
String = "Hello world!",
Char = 'C',
AnotherChar = 'A',
BooleanValue = true,
Null = null,
Nested = new TestStruct.NestedObject { NestedString = "Hello from nested object", Nested = new TestStruct.NestedObject { NestedString = "Hello from another nested object" } },
Expand Down Expand Up @@ -192,7 +194,7 @@ private string GetJsonPart(string json)

private MethodScopeMembers CreateScopeMembers()
{
var scope = new MethodScopeMembers(5, 5);
var scope = new MethodScopeMembers(10, 5);

// Add locals
scope.AddMember(new ScopeMember("IntLocal", TestObject.IntNumber.GetType(), TestObject.IntNumber, ScopeMemberKind.Local));
Expand All @@ -203,6 +205,8 @@ private MethodScopeMembers CreateScopeMembers()
scope.AddMember(new ScopeMember("NestedObjectLocal", TestObject.Nested.GetType(), TestObject.Nested, ScopeMemberKind.Local));
scope.AddMember(new ScopeMember("NullLocal", TestObject.Nested.GetType(), TestObject.Null, ScopeMemberKind.Local));
scope.AddMember(new ScopeMember("BooleanValue", TestObject.BooleanValue.GetType(), TestObject.BooleanValue, ScopeMemberKind.Local));
scope.AddMember(new ScopeMember("Char", TestObject.Char.GetType(), TestObject.Char, ScopeMemberKind.Local));
scope.AddMember(new ScopeMember("AnotherChar", TestObject.AnotherChar.GetType(), TestObject.AnotherChar, ScopeMemberKind.Local));

// Add arguments
scope.AddMember(new ScopeMember("IntArg", TestObject.IntNumber.GetType(), TestObject.IntNumber, ScopeMemberKind.Argument));
Expand Down Expand Up @@ -328,6 +332,10 @@ internal struct TestStruct

public string String;

public char Char;

public char AnotherChar;

public NestedObject Nested;

public ChildNestedObject ChildNested;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@ Expressions:
var NestedObjectLocal = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[5].Value;
var NullLocal = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[6].Value;
var BooleanValue = (bool)scopeMemberArray[7].Value;
var IntArg = (int)scopeMemberArray[8].Value;
var DoubleArg = (double)scopeMemberArray[9].Value;
var StringArg = (string)scopeMemberArray[10].Value;
var CollectionArg = (List<string>)scopeMemberArray[11].Value;
var NestedObjectArg = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[12].Value;
var Char = (char)scopeMemberArray[8].Value;
var AnotherChar = (char)scopeMemberArray[9].Value;
var IntArg = (int)scopeMemberArray[10].Value;
var DoubleArg = (double)scopeMemberArray[11].Value;
var StringArg = (string)scopeMemberArray[12].Value;
var CollectionArg = (List<string>)scopeMemberArray[13].Value;
var NestedObjectArg = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[14].Value;
var $dd_el_result = this.Null.NestedString;

return $dd_el_result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@ Expressions:
var NestedObjectLocal = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[5].Value;
var NullLocal = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[6].Value;
var BooleanValue = (bool)scopeMemberArray[7].Value;
var IntArg = (int)scopeMemberArray[8].Value;
var DoubleArg = (double)scopeMemberArray[9].Value;
var StringArg = (string)scopeMemberArray[10].Value;
var CollectionArg = (List<string>)scopeMemberArray[11].Value;
var NestedObjectArg = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[12].Value;
var Char = (char)scopeMemberArray[8].Value;
var AnotherChar = (char)scopeMemberArray[9].Value;
var IntArg = (int)scopeMemberArray[10].Value;
var DoubleArg = (double)scopeMemberArray[11].Value;
var StringArg = (string)scopeMemberArray[12].Value;
var CollectionArg = (List<string>)scopeMemberArray[13].Value;
var NestedObjectArg = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[14].Value;

return "UndefinedValue";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@ Expressions:
var NestedObjectLocal = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[5].Value;
var NullLocal = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[6].Value;
var BooleanValue = (bool)scopeMemberArray[7].Value;
var IntArg = (int)scopeMemberArray[8].Value;
var DoubleArg = (double)scopeMemberArray[9].Value;
var StringArg = (string)scopeMemberArray[10].Value;
var CollectionArg = (List<string>)scopeMemberArray[11].Value;
var NestedObjectArg = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[12].Value;
var Char = (char)scopeMemberArray[8].Value;
var AnotherChar = (char)scopeMemberArray[9].Value;
var IntArg = (int)scopeMemberArray[10].Value;
var DoubleArg = (double)scopeMemberArray[11].Value;
var StringArg = (string)scopeMemberArray[12].Value;
var CollectionArg = (List<string>)scopeMemberArray[13].Value;
var NestedObjectArg = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[14].Value;

return "UndefinedValue";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ Expressions:
var NestedObjectLocal = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[5].Value;
var NullLocal = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[6].Value;
var BooleanValue = (bool)scopeMemberArray[7].Value;
var IntArg = (int)scopeMemberArray[8].Value;
var DoubleArg = (double)scopeMemberArray[9].Value;
var StringArg = (string)scopeMemberArray[10].Value;
var CollectionArg = (List<string>)scopeMemberArray[11].Value;
var NestedObjectArg = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[12].Value;
var Char = (char)scopeMemberArray[8].Value;
var AnotherChar = (char)scopeMemberArray[9].Value;
var IntArg = (int)scopeMemberArray[10].Value;
var DoubleArg = (double)scopeMemberArray[11].Value;
var StringArg = (string)scopeMemberArray[12].Value;
var CollectionArg = (List<string>)scopeMemberArray[13].Value;
var NestedObjectArg = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[14].Value;
var $dd_el_result =
{
enumerator = this.Collection.GetEnumerator();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@ Expressions:
var NestedObjectLocal = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[5].Value;
var NullLocal = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[6].Value;
var BooleanValue = (bool)scopeMemberArray[7].Value;
var IntArg = (int)scopeMemberArray[8].Value;
var DoubleArg = (double)scopeMemberArray[9].Value;
var StringArg = (string)scopeMemberArray[10].Value;
var CollectionArg = (List<string>)scopeMemberArray[11].Value;
var NestedObjectArg = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[12].Value;
var Char = (char)scopeMemberArray[8].Value;
var AnotherChar = (char)scopeMemberArray[9].Value;
var IntArg = (int)scopeMemberArray[10].Value;
var DoubleArg = (double)scopeMemberArray[11].Value;
var StringArg = (string)scopeMemberArray[12].Value;
var CollectionArg = (List<string>)scopeMemberArray[13].Value;
var NestedObjectArg = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[14].Value;
var $dd_el_result = CollectionLocal.Count.ToString();

return $dd_el_result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@ Expressions:
var NestedObjectLocal = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[5].Value;
var NullLocal = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[6].Value;
var BooleanValue = (bool)scopeMemberArray[7].Value;
var IntArg = (int)scopeMemberArray[8].Value;
var DoubleArg = (double)scopeMemberArray[9].Value;
var StringArg = (string)scopeMemberArray[10].Value;
var CollectionArg = (List<string>)scopeMemberArray[11].Value;
var NestedObjectArg = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[12].Value;
var Char = (char)scopeMemberArray[8].Value;
var AnotherChar = (char)scopeMemberArray[9].Value;
var IntArg = (int)scopeMemberArray[10].Value;
var DoubleArg = (double)scopeMemberArray[11].Value;
var StringArg = (string)scopeMemberArray[12].Value;
var CollectionArg = (List<string>)scopeMemberArray[13].Value;
var NestedObjectArg = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[14].Value;
var $dd_el_result = (string)CollectionLocal[0];

return $dd_el_result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@ Expressions:
var NestedObjectLocal = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[5].Value;
var NullLocal = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[6].Value;
var BooleanValue = (bool)scopeMemberArray[7].Value;
var IntArg = (int)scopeMemberArray[8].Value;
var DoubleArg = (double)scopeMemberArray[9].Value;
var StringArg = (string)scopeMemberArray[10].Value;
var CollectionArg = (List<string>)scopeMemberArray[11].Value;
var NestedObjectArg = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[12].Value;
var Char = (char)scopeMemberArray[8].Value;
var AnotherChar = (char)scopeMemberArray[9].Value;
var IntArg = (int)scopeMemberArray[10].Value;
var DoubleArg = (double)scopeMemberArray[11].Value;
var StringArg = (string)scopeMemberArray[12].Value;
var CollectionArg = (List<string>)scopeMemberArray[13].Value;
var NestedObjectArg = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[14].Value;
var $dd_el_result = (string)CollectionLocal[100];

return $dd_el_result;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
Condition:
Json:
{
"gt": [
{
"getmember": [
{
"ref": "this"
},
"String"
]
},
{
"getmember": [
{
"ref": "this"
},
"IntNumber"
]
}
]
}
Expression: (
scopeMember,
scopeMember,
scopeMember,
exception,
scopeMemberArray) =>
{
bool $dd_el_result;
var this = (DebuggerExpressionLanguageTests.TestStruct)scopeMember.Value;
var @return = (string)scopeMember.Value;
var @duration = (TimeSpan)scopeMember.Value;
var @exception = exception;
var IntLocal = (int)scopeMemberArray[0].Value;
var DoubleLocal = (double)scopeMemberArray[1].Value;
var StringLocal = (string)scopeMemberArray[2].Value;
var CollectionLocal = (List<string>)scopeMemberArray[3].Value;
var DictionaryLocal = (Dictionary<string, string>)scopeMemberArray[4].Value;
var NestedObjectLocal = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[5].Value;
var NullLocal = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[6].Value;
var BooleanValue = (bool)scopeMemberArray[7].Value;
var Char = (char)scopeMemberArray[8].Value;
var AnotherChar = (char)scopeMemberArray[9].Value;
var IntArg = (int)scopeMemberArray[10].Value;
var DoubleArg = (double)scopeMemberArray[11].Value;
var StringArg = (string)scopeMemberArray[12].Value;
var CollectionArg = (List<string>)scopeMemberArray[13].Value;
var NestedObjectArg = (DebuggerExpressionLanguageTests.TestStruct.NestedObject)scopeMemberArray[14].Value;

return true;
}
Result: True
Errors:
EvaluationError { Expression = this.String > this.IntNumber, Message = The binary operator GreaterThan is not defined for the types 'System.String' and 'System.Int32'. }
Loading

0 comments on commit 9a4bfb5

Please sign in to comment.