Skip to content

Commit 0111a85

Browse files
committed
Fix Capture Expression on primitives
As CapturedValue are evaluated through Expression Language, we are dropping types in the process and resulting having boxed primitives captured and serialized into the snapshot. We introduce keeping track of the type in EL values by introducing ValueType enum. This way each EL expression evaluated can return a value that keep the existing type (Object, int, long, double, ...) Also lookup and getMember methods from ValueReferenceResolver are now returning CapturedValue to get the type and be able to transfer it to EL values
1 parent d8f2599 commit 0111a85

37 files changed

Lines changed: 456 additions & 228 deletions

dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/CapturedContext.java

Lines changed: 44 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public class CapturedContext implements ValueReferenceResolver {
2424
public static final CapturedContext EMPTY_CONTEXT = new CapturedContext(null);
2525
public static final CapturedContext EMPTY_CAPTURING_CONTEXT =
2626
new CapturedContext(ProbeImplementation.UNKNOWN);
27-
private final transient Map<String, Object> extensions = new HashMap<>();
27+
private final transient Map<String, CapturedValue> extensions = new HashMap<>();
2828

2929
private Map<String, CapturedValue> arguments;
3030
private Map<String, CapturedValue> locals;
@@ -49,7 +49,7 @@ public CapturedContext(
4949
this.throwable = throwable;
5050
}
5151

52-
private CapturedContext(CapturedContext other, Map<String, Object> extensions) {
52+
private CapturedContext(CapturedContext other, Map<String, CapturedValue> extensions) {
5353
this.arguments = other.arguments;
5454
this.locals = other.getLocals();
5555
this.throwable = other.throwable;
@@ -85,11 +85,11 @@ public boolean isCapturing() {
8585
}
8686

8787
@Override
88-
public Object lookup(String name) {
88+
public CapturedValue lookup(String name) {
8989
if (name == null || name.isEmpty()) {
9090
throw new IllegalArgumentException("empty name for lookup operation");
9191
}
92-
Object target;
92+
CapturedValue target;
9393
if (name.startsWith(ValueReferences.SYNTHETIC_PREFIX)) {
9494
String rawName = name.substring(ValueReferences.SYNTHETIC_PREFIX.length());
9595
target = tryRetrieveSynthetic(rawName);
@@ -98,36 +98,39 @@ public Object lookup(String name) {
9898
target = tryRetrieve(name);
9999
checkUndefined(target, name, "Cannot find symbol: ");
100100
}
101-
return target instanceof CapturedValue ? ((CapturedValue) target).getValue() : target;
101+
return target;
102102
}
103103

104-
private void checkUndefined(Object target, String name, String msg) {
105-
if (target == Values.UNDEFINED_OBJECT) {
104+
private void checkUndefined(CapturedValue target, String name, String msg) {
105+
if (target == CapturedValue.UNDEFINED || target.notCapturedReason != null) {
106106
String errorMsg = msg + name;
107107
throw new RuntimeException(errorMsg);
108108
}
109109
}
110110

111111
@Override
112-
public Object getMember(Object target, String memberName) {
112+
public CapturedValue getMember(Object target, String memberName) {
113113
if (target == Values.UNDEFINED_OBJECT) {
114-
return target;
114+
return CapturedValue.UNDEFINED;
115115
}
116116
if (Redaction.isRedactedKeyword(memberName)) {
117-
return REDACTED_VALUE;
117+
return CapturedValue.redacted(memberName, null);
118118
}
119+
CapturedValue result;
119120
if (target instanceof CapturedValue) {
120121
Map<String, CapturedValue> fields = ((CapturedValue) target).fields;
121122
if (fields.containsKey(memberName)) {
122-
target = fields.get(memberName);
123+
result = fields.get(memberName);
123124
} else {
124125
CapturedValue capturedTarget = ((CapturedValue) target);
125-
target = capturedTarget.getValue();
126-
if (target != null) {
126+
Object targetedValue = capturedTarget.getValue();
127+
if (targetedValue != null) {
127128
// resolve to a CapturedValue instance
128-
target = ReflectiveFieldValueResolver.resolve(target, target.getClass(), memberName);
129+
result =
130+
ReflectiveFieldValueResolver.getFieldAsCapturedValue(
131+
targetedValue.getClass(), targetedValue, memberName);
129132
} else {
130-
target = Values.UNDEFINED_OBJECT;
133+
result = CapturedValue.UNDEFINED;
131134
}
132135
}
133136
} else {
@@ -138,25 +141,27 @@ public Object getMember(Object target, String memberName) {
138141
if (specialFieldAccess != null) {
139142
CapturedValue specialField = specialFieldAccess.apply(target);
140143
if (specialField != null && specialField.getName().equals(memberName)) {
141-
return specialField.getValue();
144+
return specialField;
142145
}
143146
}
144147
}
145-
target = ReflectiveFieldValueResolver.resolve(target, target.getClass(), memberName);
148+
result =
149+
ReflectiveFieldValueResolver.getFieldAsCapturedValue(
150+
target.getClass(), target, memberName);
146151
}
147-
checkUndefined(target, memberName, "Cannot dereference field: ");
148-
return target;
152+
checkUndefined(result, memberName, "Cannot dereference field: ");
153+
return result;
149154
}
150155

151-
private Object tryRetrieveSynthetic(String name) {
156+
private CapturedValue tryRetrieveSynthetic(String name) {
152157
if (extensions == null || extensions.isEmpty()) {
153-
return Values.UNDEFINED_OBJECT;
158+
return CapturedValue.UNDEFINED;
154159
}
155-
return extensions.getOrDefault(name, Values.UNDEFINED_OBJECT);
160+
return extensions.getOrDefault(name, CapturedValue.UNDEFINED);
156161
}
157162

158-
private Object tryRetrieve(String name) {
159-
Object result = null;
163+
private CapturedValue tryRetrieve(String name) {
164+
CapturedValue result = null;
160165
if (arguments != null && !arguments.isEmpty()) {
161166
result = arguments.get(name);
162167
}
@@ -174,12 +179,12 @@ private Object tryRetrieve(String name) {
174179
}
175180
CapturedValue thisValue;
176181
if (arguments != null && (thisValue = arguments.get("this")) != null) {
177-
result = getMember(thisValue.getValue(), name);
178-
if (result != Values.UNDEFINED_OBJECT) {
182+
result = getMember(thisValue, name);
183+
if (result != CapturedValue.UNDEFINED) {
179184
return result;
180185
}
181186
}
182-
return result != null ? result : Values.UNDEFINED_OBJECT;
187+
return result != null ? result : CapturedValue.UNDEFINED;
183188
}
184189

185190
public CapturedContext copyWithoutCaptureExpressions() {
@@ -191,12 +196,12 @@ public CapturedContext copyWithoutCaptureExpressions() {
191196
}
192197

193198
@Override
194-
public ValueReferenceResolver withExtensions(Map<String, Object> extensions) {
199+
public ValueReferenceResolver withExtensions(Map<String, CapturedValue> extensions) {
195200
return new CapturedContext(this, extensions);
196201
}
197202

198203
@Override
199-
public void addExtension(String name, Object value) {
204+
public void addExtension(String name, CapturedValue value) {
200205
extensions.put(name, value);
201206
}
202207

@@ -235,8 +240,9 @@ public void addReturn(CapturedValue retValue) {
235240
public void addThrowable(Throwable t) {
236241
addThrowable(new CapturedThrowable(t));
237242
// special local name for throwable
238-
putInLocals(ValueReferences.EXCEPTION_REF, CapturedValue.of(t.getClass().getTypeName(), t));
239-
extensions.put(ValueReferences.EXCEPTION_EXTENSION_NAME, t);
243+
CapturedValue capturedException = CapturedValue.of(t.getClass().getTypeName(), t);
244+
putInLocals(ValueReferences.EXCEPTION_REF, capturedException);
245+
extensions.put(ValueReferences.EXCEPTION_EXTENSION_NAME, capturedException);
240246
}
241247

242248
public void addThrowable(CapturedThrowable capturedThrowable) {
@@ -319,7 +325,8 @@ public Status evaluate(
319325
if (methodLocation == MethodLocation.EXIT && startTimestamp > 0) {
320326
duration = System.nanoTime() - startTimestamp;
321327
addExtension(
322-
ValueReferences.DURATION_EXTENSION_NAME, duration / 1_000_000.0); // convert to ms
328+
ValueReferences.DURATION_EXTENSION_NAME,
329+
CapturedValue.of(duration / 1_000_000.0)); // convert to ms
323330
}
324331
this.thisClassName = thisClassName;
325332
boolean shouldEvaluate =
@@ -446,7 +453,7 @@ public boolean isCapturing() {
446453

447454
/** Stores a captured value */
448455
public static class CapturedValue {
449-
public static final CapturedValue UNDEFINED = CapturedValue.of(null, Values.UNDEFINED_OBJECT);
456+
public static final CapturedValue UNDEFINED = CapturedValue.of(Values.UNDEFINED_OBJECT);
450457

451458
private String name;
452459
private final String declaredType;
@@ -517,6 +524,10 @@ public void setName(String name) {
517524
this.name = name;
518525
}
519526

527+
public static CapturedValue of(Object value) {
528+
return of(null, value);
529+
}
530+
520531
public static CapturedValue of(String declaredType, Object value) {
521532
return build(null, declaredType, value, Limits.DEFAULT, null);
522533
}
@@ -525,10 +536,6 @@ public static CapturedValue of(String name, String declaredType, Object value) {
525536
return build(name, declaredType, value, Limits.DEFAULT, null);
526537
}
527538

528-
public CapturedValue derive(String name, String type, Object value) {
529-
return build(name, type, value, limits, null);
530-
}
531-
532539
public static CapturedValue of(
533540
String name,
534541
String declaredType,
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
package datadog.trace.bootstrap.debugger.el;
22

3+
import datadog.trace.bootstrap.debugger.CapturedContext.CapturedValue;
34
import java.util.Map;
45

56
/** Debugger EL specific value reference resolver. */
67
public interface ValueReferenceResolver {
7-
Object lookup(String name);
8+
CapturedValue lookup(String name);
89

9-
Object getMember(Object target, String name);
10+
CapturedValue getMember(Object target, String name);
1011

11-
default void addExtension(String name, Object value) {}
12+
default void addExtension(String name, CapturedValue value) {}
1213

1314
default void removeExtension(String name) {}
1415

15-
default ValueReferenceResolver withExtensions(Map<String, Object> extensions) {
16+
default ValueReferenceResolver withExtensions(Map<String, CapturedValue> extensions) {
1617
return this;
1718
}
1819
}

dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/BooleanValueExpressionAdapter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public BooleanValue evaluate(ValueReferenceResolver valueRefResolver) {
2020
throw new EvaluationException(
2121
"Boolean expression returning null", PrettyPrintVisitor.print(this));
2222
}
23-
return new BooleanValue(result);
23+
return new BooleanValue(result, ValueType.OBJECT);
2424
}
2525

2626
@Override

dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/DSL.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,11 +127,11 @@ public static IndexExpression index(ValueExpression<?> target, ValueExpression<?
127127
}
128128

129129
public static Literal<Boolean> value(boolean value) {
130-
return new BooleanValue(value);
130+
return new BooleanValue(value, ValueType.BOOLEAN);
131131
}
132132

133133
public static Literal<Number> value(Number value) {
134-
return new NumericValue(value);
134+
return new NumericValue(value, ValueType.OBJECT);
135135
}
136136

137137
public static Literal<String> value(String value) {

dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/Literal.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99
public class Literal<ConstantType>
1010
implements Value<ConstantType>, ValueExpression<Value<ConstantType>> {
1111
protected final ConstantType value;
12+
protected final ValueType type;
1213

13-
protected Literal(ConstantType value) {
14+
protected Literal(ConstantType value, ValueType type) {
1415
this.value = value;
16+
this.type = type;
1517
}
1618

1719
@Override
@@ -34,6 +36,11 @@ public ConstantType getValue() {
3436
return value;
3537
}
3638

39+
@Override
40+
public ValueType getType() {
41+
return type;
42+
}
43+
3744
@Override
3845
public boolean equals(Object o) {
3946
if (this == o) return true;

dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/Value.java

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.datadog.debugger.el.values.SetValue;
1010
import com.datadog.debugger.el.values.StringValue;
1111
import com.datadog.debugger.el.values.UndefinedValue;
12+
import datadog.trace.bootstrap.debugger.CapturedContext;
1213
import datadog.trace.bootstrap.debugger.el.Values;
1314
import datadog.trace.bootstrap.debugger.util.WellKnownClasses;
1415
import java.util.List;
@@ -39,6 +40,10 @@ static <T> Value<T> undefined() {
3940

4041
T getValue();
4142

43+
default ValueType getType() {
44+
return ValueType.OBJECT;
45+
}
46+
4247
default boolean isUndefined() {
4348
return false;
4449
}
@@ -47,7 +52,7 @@ default boolean isNull() {
4752
return false;
4853
}
4954

50-
static Value<?> of(Object value) {
55+
static Value<?> of(Object value, ValueType type) {
5156
if (value == null || value == Values.NULL_OBJECT || value == nullValue()) {
5257
return nullValue();
5358
}
@@ -71,11 +76,11 @@ static Value<?> of(Object value) {
7176
value = longPrimitiveValueFunction.applyAsLong(value);
7277
}
7378
if (value instanceof Boolean) {
74-
return new BooleanValue((Boolean) value);
79+
return new BooleanValue((Boolean) value, type);
7580
} else if (value instanceof Character) {
7681
return new StringValue(value.toString());
7782
} else if (value instanceof Number) {
78-
return new NumericValue((Number) value);
83+
return new NumericValue((Number) value, type);
7984
} else if (value instanceof String) {
8085
return new StringValue((String) value);
8186
} else if (value instanceof List) {
@@ -92,4 +97,26 @@ static Value<?> of(Object value) {
9297
return new ObjectValue(value);
9398
}
9499
}
100+
101+
static CapturedContext.CapturedValue toCapturedSnapshot(String name, Value<?> value) {
102+
return CapturedContext.CapturedValue.of(
103+
name, ValueType.toString(value.getType()), value.getValue());
104+
}
105+
106+
static CapturedContext.CapturedValue toCapturedSnapshot(
107+
String name,
108+
Value<?> value,
109+
int maxReferenceDepth,
110+
int maxCollectionSize,
111+
int maxCount,
112+
int maxFieldCount) {
113+
return CapturedContext.CapturedValue.of(
114+
name,
115+
ValueType.toString(value.getType()),
116+
value.getValue(),
117+
maxReferenceDepth,
118+
maxCollectionSize,
119+
maxCount,
120+
maxFieldCount);
121+
}
95122
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.datadog.debugger.el;
2+
3+
public enum ValueType {
4+
OBJECT,
5+
BOOLEAN,
6+
INT,
7+
LONG,
8+
DOUBLE,
9+
BYTE,
10+
SHORT,
11+
CHAR,
12+
FLOAT;
13+
14+
public static String toString(ValueType type) {
15+
switch (type) {
16+
case BOOLEAN:
17+
case INT:
18+
case LONG:
19+
case DOUBLE:
20+
case BYTE:
21+
case SHORT:
22+
case CHAR:
23+
case FLOAT:
24+
return type.name().toLowerCase();
25+
default:
26+
return Object.class.getTypeName();
27+
}
28+
}
29+
30+
public static ValueType of(String type) {
31+
switch (type) {
32+
case "boolean":
33+
return BOOLEAN;
34+
case "int":
35+
return INT;
36+
case "long":
37+
return LONG;
38+
case "double":
39+
return DOUBLE;
40+
case "byte":
41+
return BYTE;
42+
case "short":
43+
return SHORT;
44+
case "char":
45+
return CHAR;
46+
case "float":
47+
return FLOAT;
48+
default:
49+
return OBJECT;
50+
}
51+
}
52+
}

0 commit comments

Comments
 (0)