From 2ac84598eb2c3a53a507a242412e0dd2006950c0 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Bempel Date: Mon, 12 Oct 2026 10:17:10 +0200 Subject: [PATCH] Fix equals and hashCode for Expressions equals ans hashCode are missing for most expressions which fails the ConfigurationComparer. The issue was hidden by RC client that is not calling debugger is configuration has not been changed. --- .../el/BooleanValueExpressionAdapter.java | 15 + .../el/expressions/BinaryExpression.java | 18 ++ .../el/expressions/ComparisonExpression.java | 18 ++ .../el/expressions/ContainsExpression.java | 16 + .../FilterCollectionExpression.java | 17 ++ .../el/expressions/IndexExpression.java | 16 + .../el/expressions/IsDefinedExpression.java | 16 + .../el/expressions/IsEmptyExpression.java | 16 + .../el/expressions/LenExpression.java | 16 + .../el/expressions/MatchingExpression.java | 18 ++ .../el/expressions/NotExpression.java | 16 + .../StringPredicateExpression.java | 19 ++ .../el/expressions/SubStringExpression.java | 18 ++ .../com/datadog/debugger/probe/LogProbe.java | 19 ++ .../datadog/debugger/probe/LogProbeTest.java | 281 ++++++++++++++++++ 15 files changed, 519 insertions(+) diff --git a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/BooleanValueExpressionAdapter.java b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/BooleanValueExpressionAdapter.java index e7986715e24..89035a3d480 100644 --- a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/BooleanValueExpressionAdapter.java +++ b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/BooleanValueExpressionAdapter.java @@ -3,6 +3,7 @@ import com.datadog.debugger.el.expressions.BooleanExpression; import com.datadog.debugger.el.expressions.ValueExpression; import com.datadog.debugger.el.values.BooleanValue; +import java.util.Objects; public class BooleanValueExpressionAdapter implements ValueExpression { @@ -26,4 +27,18 @@ public BooleanValue evaluate(EvalContext evalContext) { public R accept(Visitor visitor) { return visitor.visit(booleanExpression); } + + @Generated + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + BooleanValueExpressionAdapter that = (BooleanValueExpressionAdapter) o; + return Objects.equals(booleanExpression, that.booleanExpression); + } + + @Generated + @Override + public int hashCode() { + return Objects.hashCode(booleanExpression); + } } diff --git a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/BinaryExpression.java b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/BinaryExpression.java index 3c955d21b74..3a3afe6b19d 100644 --- a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/BinaryExpression.java +++ b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/BinaryExpression.java @@ -1,7 +1,9 @@ package com.datadog.debugger.el.expressions; import com.datadog.debugger.el.EvalContext; +import com.datadog.debugger.el.Generated; import com.datadog.debugger.el.Visitor; +import java.util.Objects; /** * Takes two {@linkplain BooleanExpression} instances and combines them with the given {@link @@ -52,4 +54,20 @@ public BooleanExpression getRight() { public BinaryOperator getOperator() { return operator; } + + @Generated + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + BinaryExpression that = (BinaryExpression) o; + return Objects.equals(left, that.left) + && Objects.equals(right, that.right) + && operator == that.operator; + } + + @Generated + @Override + public int hashCode() { + return Objects.hash(left, right, operator); + } } diff --git a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/ComparisonExpression.java b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/ComparisonExpression.java index e14c47a67be..5579faaf409 100644 --- a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/ComparisonExpression.java +++ b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/ComparisonExpression.java @@ -5,9 +5,11 @@ import com.datadog.debugger.el.EvalContext; import com.datadog.debugger.el.EvaluationException; import com.datadog.debugger.el.EvaluationTimeOutException; +import com.datadog.debugger.el.Generated; import com.datadog.debugger.el.PrettyPrintVisitor; import com.datadog.debugger.el.Value; import com.datadog.debugger.el.Visitor; +import java.util.Objects; /** * Takes two {@linkplain ValueExpression} instances and compares them using the given {@link @@ -62,4 +64,20 @@ public ValueExpression getRight() { public ComparisonOperator getOperator() { return operator; } + + @Generated + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + ComparisonExpression that = (ComparisonExpression) o; + return Objects.equals(left, that.left) + && Objects.equals(right, that.right) + && operator == that.operator; + } + + @Generated + @Override + public int hashCode() { + return Objects.hash(left, right, operator); + } } diff --git a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/ContainsExpression.java b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/ContainsExpression.java index bde8654b551..c7ad7dbe23f 100644 --- a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/ContainsExpression.java +++ b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/ContainsExpression.java @@ -5,10 +5,12 @@ import com.datadog.debugger.el.EvalContext; import com.datadog.debugger.el.EvaluationException; +import com.datadog.debugger.el.Generated; import com.datadog.debugger.el.PrettyPrintVisitor; import com.datadog.debugger.el.Value; import com.datadog.debugger.el.Visitor; import com.datadog.debugger.el.values.CollectionValue; +import java.util.Objects; public class ContainsExpression implements BooleanExpression { private final ValueExpression target; @@ -71,4 +73,18 @@ public ValueExpression getTarget() { public ValueExpression getValue() { return value; } + + @Generated + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + ContainsExpression that = (ContainsExpression) o; + return Objects.equals(target, that.target) && Objects.equals(value, that.value); + } + + @Generated + @Override + public int hashCode() { + return Objects.hash(target, value); + } } diff --git a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/FilterCollectionExpression.java b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/FilterCollectionExpression.java index cd4c8ef1c89..3a0eff4ae13 100644 --- a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/FilterCollectionExpression.java +++ b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/FilterCollectionExpression.java @@ -9,6 +9,7 @@ import com.datadog.debugger.el.EvalContext; import com.datadog.debugger.el.EvaluationException; +import com.datadog.debugger.el.Generated; import com.datadog.debugger.el.Value; import com.datadog.debugger.el.Visitor; import com.datadog.debugger.el.values.CollectionValue; @@ -23,6 +24,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Objects; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -124,4 +126,19 @@ public ValueExpression getSource() { public BooleanExpression getFilterExpression() { return filterExpression; } + + @Generated + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + FilterCollectionExpression that = (FilterCollectionExpression) o; + return Objects.equals(source, that.source) + && Objects.equals(filterExpression, that.filterExpression); + } + + @Generated + @Override + public int hashCode() { + return Objects.hash(source, filterExpression); + } } diff --git a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/IndexExpression.java b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/IndexExpression.java index 8c718eb282d..e26d52aee31 100644 --- a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/IndexExpression.java +++ b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/IndexExpression.java @@ -6,12 +6,14 @@ import com.datadog.debugger.el.EvalContext; import com.datadog.debugger.el.EvaluationException; +import com.datadog.debugger.el.Generated; import com.datadog.debugger.el.PrettyPrintVisitor; import com.datadog.debugger.el.Value; import com.datadog.debugger.el.Visitor; import com.datadog.debugger.el.values.ListValue; import com.datadog.debugger.el.values.MapValue; import datadog.trace.bootstrap.debugger.util.Redaction; +import java.util.Objects; public class IndexExpression implements ValueExpression> { @@ -81,4 +83,18 @@ public ValueExpression getTarget() { public ValueExpression getKey() { return key; } + + @Generated + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + IndexExpression that = (IndexExpression) o; + return Objects.equals(target, that.target) && Objects.equals(key, that.key); + } + + @Generated + @Override + public int hashCode() { + return Objects.hash(target, key); + } } diff --git a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/IsDefinedExpression.java b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/IsDefinedExpression.java index 24ac98be2d8..b949f1fdc32 100644 --- a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/IsDefinedExpression.java +++ b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/IsDefinedExpression.java @@ -5,8 +5,10 @@ import com.datadog.debugger.el.EvalContext; import com.datadog.debugger.el.EvaluationException; import com.datadog.debugger.el.EvaluationTimeOutException; +import com.datadog.debugger.el.Generated; import com.datadog.debugger.el.Value; import com.datadog.debugger.el.Visitor; +import java.util.Objects; /** Check whether a {@linkplain Value} was resolved correctly or symbol exists.
*/ public class IsDefinedExpression implements BooleanExpression { @@ -41,4 +43,18 @@ public R accept(Visitor visitor) { public ValueExpression getValueExpression() { return valueExpression; } + + @Generated + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + IsDefinedExpression that = (IsDefinedExpression) o; + return Objects.equals(valueExpression, that.valueExpression); + } + + @Generated + @Override + public int hashCode() { + return Objects.hashCode(valueExpression); + } } diff --git a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/IsEmptyExpression.java b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/IsEmptyExpression.java index 1c4b43eef51..4b502d33c76 100644 --- a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/IsEmptyExpression.java +++ b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/IsEmptyExpression.java @@ -4,11 +4,13 @@ import com.datadog.debugger.el.EvalContext; import com.datadog.debugger.el.EvaluationException; +import com.datadog.debugger.el.Generated; import com.datadog.debugger.el.PrettyPrintVisitor; import com.datadog.debugger.el.Value; import com.datadog.debugger.el.Visitor; import com.datadog.debugger.el.values.CollectionValue; import com.datadog.debugger.el.values.StringValue; +import java.util.Objects; /** * Checks whether a {@linkplain Value} is empty.
@@ -50,4 +52,18 @@ public R accept(Visitor visitor) { public ValueExpression getValueExpression() { return valueExpression; } + + @Generated + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + IsEmptyExpression that = (IsEmptyExpression) o; + return Objects.equals(valueExpression, that.valueExpression); + } + + @Generated + @Override + public int hashCode() { + return Objects.hashCode(valueExpression); + } } diff --git a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/LenExpression.java b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/LenExpression.java index 3033d693cf1..24581d784fb 100644 --- a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/LenExpression.java +++ b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/LenExpression.java @@ -4,6 +4,7 @@ import com.datadog.debugger.el.EvalContext; import com.datadog.debugger.el.EvaluationException; +import com.datadog.debugger.el.Generated; import com.datadog.debugger.el.PrettyPrintVisitor; import com.datadog.debugger.el.Value; import com.datadog.debugger.el.ValueType; @@ -11,6 +12,7 @@ import com.datadog.debugger.el.values.CollectionValue; import com.datadog.debugger.el.values.NumericValue; import com.datadog.debugger.el.values.StringValue; +import java.util.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,4 +63,18 @@ public ValueExpression getSource() { public R accept(Visitor visitor) { return visitor.visit(this); } + + @Generated + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + LenExpression that = (LenExpression) o; + return Objects.equals(source, that.source); + } + + @Generated + @Override + public int hashCode() { + return Objects.hashCode(source); + } } diff --git a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/MatchingExpression.java b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/MatchingExpression.java index 9c0138617c4..bb8731075f9 100644 --- a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/MatchingExpression.java +++ b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/MatchingExpression.java @@ -1,5 +1,8 @@ package com.datadog.debugger.el.expressions; +import com.datadog.debugger.el.Generated; +import java.util.Objects; + /** Abstract super class for expressions performing matching of a value against a predicate. */ abstract class MatchingExpression implements BooleanExpression { protected final ValueExpression valueExpression; @@ -19,4 +22,19 @@ public ValueExpression getValueExpression() { public BooleanExpression getFilterPredicateExpression() { return filterPredicateExpression; } + + @Generated + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + MatchingExpression that = (MatchingExpression) o; + return Objects.equals(valueExpression, that.valueExpression) + && Objects.equals(filterPredicateExpression, that.filterPredicateExpression); + } + + @Generated + @Override + public int hashCode() { + return Objects.hash(valueExpression, filterPredicateExpression); + } } diff --git a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/NotExpression.java b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/NotExpression.java index 5cf8fad7607..92a30c0f170 100644 --- a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/NotExpression.java +++ b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/NotExpression.java @@ -3,7 +3,9 @@ import static com.datadog.debugger.el.expressions.ExpressionHelper.checkTimeout; import com.datadog.debugger.el.EvalContext; +import com.datadog.debugger.el.Generated; import com.datadog.debugger.el.Visitor; +import java.util.Objects; /** Will negate the resolved {@linkplain BooleanExpression} */ public final class NotExpression implements BooleanExpression { @@ -28,4 +30,18 @@ public R accept(Visitor visitor) { public BooleanExpression getPredicate() { return predicate; } + + @Generated + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + NotExpression that = (NotExpression) o; + return Objects.equals(predicate, that.predicate); + } + + @Generated + @Override + public int hashCode() { + return Objects.hashCode(predicate); + } } diff --git a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/StringPredicateExpression.java b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/StringPredicateExpression.java index efb1a4f3269..36e880de564 100644 --- a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/StringPredicateExpression.java +++ b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/StringPredicateExpression.java @@ -2,9 +2,11 @@ import com.datadog.debugger.el.EvalContext; import com.datadog.debugger.el.EvaluationException; +import com.datadog.debugger.el.Generated; import com.datadog.debugger.el.PrettyPrintVisitor; import com.datadog.debugger.el.Value; import com.datadog.debugger.el.values.StringValue; +import java.util.Objects; import java.util.function.BiPredicate; public class StringPredicateExpression implements BooleanExpression { @@ -60,4 +62,21 @@ public BiPredicate getPredicate() { public String getName() { return name; } + + @Generated + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + StringPredicateExpression that = (StringPredicateExpression) o; + // predicate is excluded: lambda equality is identity-based, the class and name identify it + return Objects.equals(sourceString, that.sourceString) + && Objects.equals(str, that.str) + && Objects.equals(name, that.name); + } + + @Generated + @Override + public int hashCode() { + return Objects.hash(sourceString, str, name); + } } diff --git a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/SubStringExpression.java b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/SubStringExpression.java index 40e7d258e33..fe326c535ff 100644 --- a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/SubStringExpression.java +++ b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/SubStringExpression.java @@ -4,10 +4,12 @@ import com.datadog.debugger.el.EvalContext; import com.datadog.debugger.el.EvaluationException; +import com.datadog.debugger.el.Generated; import com.datadog.debugger.el.PrettyPrintVisitor; import com.datadog.debugger.el.Value; import com.datadog.debugger.el.ValueType; import com.datadog.debugger.el.Visitor; +import java.util.Objects; public class SubStringExpression implements ValueExpression> { private final ValueExpression source; @@ -64,4 +66,20 @@ public int getStartIndex() { public int getEndIndex() { return endIndex; } + + @Generated + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + SubStringExpression that = (SubStringExpression) o; + return startIndex == that.startIndex + && endIndex == that.endIndex + && Objects.equals(source, that.source); + } + + @Generated + @Override + public int hashCode() { + return Objects.hash(source, startIndex, endIndex); + } } diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/LogProbe.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/LogProbe.java index 16025bb7e1f..548086744b2 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/LogProbe.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/LogProbe.java @@ -304,6 +304,23 @@ public Capture getCapture() { return capture; } + @Generated + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CaptureExpression that = (CaptureExpression) o; + return Objects.equals(name, that.name) + && Objects.equals(expr, that.expr) + && Objects.equals(capture, that.capture); + } + + @Generated + @Override + public int hashCode() { + return Objects.hash(name, expr, capture); + } + @Generated @Override public String toString() { @@ -1181,6 +1198,7 @@ public boolean equals(Object o) { && Objects.equals(template, that.template) && Objects.equals(segments, that.segments) && Objects.equals(captureSnapshot, that.captureSnapshot) + && Objects.equals(captureExpressions, that.captureExpressions) && Objects.equals(probeCondition, that.probeCondition) && Objects.equals(capture, that.capture) && Objects.equals(sampling, that.sampling); @@ -1200,6 +1218,7 @@ public int hashCode() { template, segments, captureSnapshot, + captureExpressions, probeCondition, capture, sampling); diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/probe/LogProbeTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/probe/LogProbeTest.java index b67ec331ed6..38fb79e6985 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/probe/LogProbeTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/probe/LogProbeTest.java @@ -1,6 +1,29 @@ package com.datadog.debugger.probe; import static com.datadog.debugger.agent.CapturingTestBase.getConfig; +import static com.datadog.debugger.el.DSL.and; +import static com.datadog.debugger.el.DSL.bool; +import static com.datadog.debugger.el.DSL.contains; +import static com.datadog.debugger.el.DSL.endsWith; +import static com.datadog.debugger.el.DSL.eq; +import static com.datadog.debugger.el.DSL.filter; +import static com.datadog.debugger.el.DSL.ge; +import static com.datadog.debugger.el.DSL.getMember; +import static com.datadog.debugger.el.DSL.gt; +import static com.datadog.debugger.el.DSL.index; +import static com.datadog.debugger.el.DSL.instanceOf; +import static com.datadog.debugger.el.DSL.isDefined; +import static com.datadog.debugger.el.DSL.isEmpty; +import static com.datadog.debugger.el.DSL.le; +import static com.datadog.debugger.el.DSL.len; +import static com.datadog.debugger.el.DSL.lt; +import static com.datadog.debugger.el.DSL.matches; +import static com.datadog.debugger.el.DSL.not; +import static com.datadog.debugger.el.DSL.or; +import static com.datadog.debugger.el.DSL.ref; +import static com.datadog.debugger.el.DSL.startsWith; +import static com.datadog.debugger.el.DSL.subString; +import static com.datadog.debugger.el.DSL.value; import static com.datadog.debugger.util.LogProbeTestHelper.parseTemplate; import static datadog.trace.api.debugger.DebuggerMetricCollector.SkippedReason.EVALUATION_TIME_OUT; import static datadog.trace.api.debugger.DebuggerMetricCollector.SkippedReason.RATE_LIMIT; @@ -9,6 +32,7 @@ import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -25,6 +49,8 @@ import com.datadog.debugger.el.EvaluationTimeOutException; import com.datadog.debugger.el.ProbeCondition; import com.datadog.debugger.el.ValueScript; +import com.datadog.debugger.el.expressions.ValueExpression; +import com.datadog.debugger.el.values.StringValue; import com.datadog.debugger.probe.LogProbe.Builder; import com.datadog.debugger.probe.LogProbe.LogStatus; import com.datadog.debugger.sink.DebuggerSink; @@ -44,6 +70,11 @@ import datadog.trace.bootstrap.instrumentation.api.AgentTracer.TracerAPI; import datadog.trace.bootstrap.instrumentation.api.Tags; import datadog.trace.core.CoreTracer; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; import java.util.stream.IntStream; import java.util.stream.Stream; import javax.annotation.Nonnull; @@ -76,6 +107,256 @@ public void testSampling() { assertEquals(0.25, snapshotProbe.getSampling().getEventsPerSecond(), 0.01); } + @ParameterizedTest(name = "{0}") + @MethodSource("valueExpressions") + public void equalsSegment(String dsl, Supplier> exprSupplier) { + LogProbe.Segment segment1 = new LogProbe.Segment(new ValueScript(exprSupplier.get(), dsl)); + LogProbe.Segment segment2 = new LogProbe.Segment(new ValueScript(exprSupplier.get(), dsl)); + assertEquals(segment1, segment2); + } + + private static Stream valueExpressions() { + return Stream.of( + valueExpression("field", () -> ref("field")), + valueExpression("obj.field", () -> getMember(ref("obj"), "field")), + valueExpression("list[0]", () -> index(ref("list"), value(0))), + valueExpression("map['key']", () -> index(ref("map"), value("key"))), + valueExpression("len(list)", () -> len(ref("list"))), + valueExpression("substring(str, 0, 2)", () -> subString(ref("str"), 0, 2)), + valueExpression( + "filter(list, {len(@it) > 0})", + () -> filter(ref("list"), gt(len(ref("@it")), value(0)))), + valueExpression( + "any(list, {@it == 1})", () -> bool(DSL.any(ref("list"), eq(ref("@it"), value(1))))), + valueExpression( + "all(list, {@it == 1})", () -> bool(DSL.all(ref("list"), eq(ref("@it"), value(1))))), + valueExpression("isEmpty(list)", () -> bool(isEmpty(ref("list")))), + valueExpression("isDefined(field)", () -> bool(isDefined(ref("field")))), + valueExpression( + "startsWith(str, 'foo')", () -> bool(startsWith(ref("str"), new StringValue("foo")))), + valueExpression( + "endsWith(str, 'foo')", () -> bool(endsWith(ref("str"), new StringValue("foo")))), + valueExpression( + "matches(str, '[a-z]+')", () -> bool(matches(ref("str"), new StringValue("[a-z]+")))), + valueExpression("contains(str, 'foo')", () -> bool(contains(ref("str"), value("foo")))), + valueExpression( + "instanceof(obj, 'java.lang.String')", + () -> bool(instanceOf(ref("obj"), value("java.lang.String")))), + valueExpression("field == 1", () -> bool(eq(ref("field"), value(1)))), + valueExpression("field > 1", () -> bool(gt(ref("field"), value(1)))), + valueExpression("field >= 1", () -> bool(ge(ref("field"), value(1)))), + valueExpression("field < 1", () -> bool(lt(ref("field"), value(1)))), + valueExpression("field <= 1", () -> bool(le(ref("field"), value(1)))), + valueExpression("not(isEmpty(list))", () -> bool(not(isEmpty(ref("list"))))), + valueExpression( + "isEmpty(list) && isDefined(field)", + () -> bool(and(isEmpty(ref("list")), isDefined(ref("field"))))), + valueExpression( + "isEmpty(list) || isDefined(field)", + () -> bool(or(isEmpty(ref("list")), isDefined(ref("field")))))); + } + + private static Arguments valueExpression(String dsl, Supplier> expr) { + return Arguments.of(dsl, expr); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("valueExpressions") + public void hashCodeSegment(String dsl, Supplier> exprSupplier) { + LogProbe.Segment segment1 = new LogProbe.Segment(new ValueScript(exprSupplier.get(), dsl)); + LogProbe.Segment segment2 = new LogProbe.Segment(new ValueScript(exprSupplier.get(), dsl)); + assertEquals(segment1.hashCode(), segment2.hashCode()); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("differentValueExpressions") + public void notEqualsSegment(String name, ValueExpression expr1, ValueExpression expr2) { + // same dsl string on both sides so that only the expression differs + LogProbe.Segment segment1 = new LogProbe.Segment(new ValueScript(expr1, "dsl")); + LogProbe.Segment segment2 = new LogProbe.Segment(new ValueScript(expr2, "dsl")); + assertNotEquals(segment1, segment2); + } + + private static Stream differentValueExpressions() { + return Stream.of( + Arguments.of("ref", ref("field1"), ref("field2")), + Arguments.of("getMember", getMember(ref("obj"), "field1"), getMember(ref("obj"), "field2")), + Arguments.of("index key", index(ref("list"), value(0)), index(ref("list"), value(1))), + Arguments.of("index target", index(ref("list1"), value(0)), index(ref("list2"), value(0))), + Arguments.of("len", len(ref("list1")), len(ref("list2"))), + Arguments.of("substring start", subString(ref("str"), 0, 2), subString(ref("str"), 1, 2)), + Arguments.of("substring end", subString(ref("str"), 0, 2), subString(ref("str"), 0, 3)), + Arguments.of( + "substring source", subString(ref("str1"), 0, 2), subString(ref("str2"), 0, 2)), + Arguments.of( + "filter predicate", + filter(ref("list"), gt(len(ref("@it")), value(0))), + filter(ref("list"), gt(len(ref("@it")), value(1)))), + Arguments.of( + "filter source", + filter(ref("list1"), gt(len(ref("@it")), value(0))), + filter(ref("list2"), gt(len(ref("@it")), value(0)))), + Arguments.of( + "any vs all", + bool(DSL.any(ref("list"), eq(ref("@it"), value(1)))), + bool(DSL.all(ref("list"), eq(ref("@it"), value(1))))), + Arguments.of( + "any predicate", + bool(DSL.any(ref("list"), eq(ref("@it"), value(1)))), + bool(DSL.any(ref("list"), eq(ref("@it"), value(2))))), + Arguments.of("isEmpty", bool(isEmpty(ref("list1"))), bool(isEmpty(ref("list2")))), + Arguments.of("isDefined", bool(isDefined(ref("field1"))), bool(isDefined(ref("field2")))), + Arguments.of( + "startsWith vs endsWith", + bool(startsWith(ref("str"), new StringValue("foo"))), + bool(endsWith(ref("str"), new StringValue("foo")))), + Arguments.of( + "startsWith str", + bool(startsWith(ref("str"), new StringValue("foo"))), + bool(startsWith(ref("str"), new StringValue("bar")))), + Arguments.of( + "matches pattern", + bool(matches(ref("str"), new StringValue("[a-z]+"))), + bool(matches(ref("str"), new StringValue("[0-9]+")))), + Arguments.of( + "contains value", + bool(contains(ref("str"), value("foo"))), + bool(contains(ref("str"), value("bar")))), + Arguments.of( + "instanceof", + bool(instanceOf(ref("obj"), value("java.lang.String"))), + bool(instanceOf(ref("obj"), value("java.lang.Integer")))), + Arguments.of( + "comparison operator", + bool(gt(ref("field"), value(1))), + bool(ge(ref("field"), value(1)))), + Arguments.of( + "comparison left", + bool(eq(ref("field1"), value(1))), + bool(eq(ref("field2"), value(1)))), + Arguments.of( + "comparison right", bool(eq(ref("field"), value(1))), bool(eq(ref("field"), value(2)))), + Arguments.of("not", bool(not(isEmpty(ref("list1")))), bool(not(isEmpty(ref("list2"))))), + Arguments.of( + "and vs or", + bool(and(isEmpty(ref("list")), isDefined(ref("field")))), + bool(or(isEmpty(ref("list")), isDefined(ref("field"))))), + Arguments.of( + "binary left", + bool(and(isEmpty(ref("list1")), isDefined(ref("field")))), + bool(and(isEmpty(ref("list2")), isDefined(ref("field"))))), + Arguments.of( + "binary right", + bool(and(isEmpty(ref("list")), isDefined(ref("field1")))), + bool(and(isEmpty(ref("list")), isDefined(ref("field2")))))); + } + + @Test + public void notEqualsCaptureExpression() { + LogProbe.CaptureExpression base = + new LogProbe.CaptureExpression( + "expr", new ValueScript(ref("field"), "field"), new LogProbe.Capture(1, 2, 3, 4)); + assertNotEquals( + base, + new LogProbe.CaptureExpression( + "other", new ValueScript(ref("field"), "field"), new LogProbe.Capture(1, 2, 3, 4))); + assertNotEquals( + base, + new LogProbe.CaptureExpression( + "expr", new ValueScript(ref("other"), "field"), new LogProbe.Capture(1, 2, 3, 4))); + assertNotEquals( + base, + new LogProbe.CaptureExpression( + "expr", new ValueScript(ref("field"), "field"), new LogProbe.Capture(5, 2, 3, 4))); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("valueExpressions") + public void equalsCaptureExpression(String dsl, Supplier> exprSupplier) { + LogProbe.CaptureExpression captureExpression1 = createCaptureExpression(dsl, exprSupplier); + LogProbe.CaptureExpression captureExpression2 = createCaptureExpression(dsl, exprSupplier); + assertEquals(captureExpression1, captureExpression2); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("valueExpressions") + public void hashCodeCaptureExpression(String dsl, Supplier> exprSupplier) { + LogProbe.CaptureExpression captureExpression1 = createCaptureExpression(dsl, exprSupplier); + LogProbe.CaptureExpression captureExpression2 = createCaptureExpression(dsl, exprSupplier); + assertEquals(captureExpression1.hashCode(), captureExpression2.hashCode()); + } + + private static LogProbe.CaptureExpression createCaptureExpression( + String dsl, Supplier> exprSupplier) { + return new LogProbe.CaptureExpression( + "expr", new ValueScript(exprSupplier.get(), dsl), new LogProbe.Capture(1, 2, 3, 4)); + } + + @Test + public void equalsCoversAllNonTransientFields() throws Exception { + assertEquals(createFullProbe1(), createFullProbe1()); + LogProbe other = createFullProbe2(); + List uncoveredFields = new ArrayList<>(); + for (Class clazz = LogProbe.class; clazz != Object.class; clazz = clazz.getSuperclass()) { + for (Field field : clazz.getDeclaredFields()) { + int modifiers = field.getModifiers(); + if (Modifier.isStatic(modifiers) + || Modifier.isTransient(modifiers) + || field.isSynthetic()) { + continue; + } + field.setAccessible(true); + LogProbe probe = createFullProbe1(); + Object otherValue = field.get(other); + assertNotEquals( + field.get(probe), otherValue, "field " + field.getName() + " must differ in test data"); + field.set(probe, otherValue); + if (probe.equals(createFullProbe1())) { + uncoveredFields.add(clazz.getSimpleName() + "." + field.getName()); + } + } + } + assertTrue(uncoveredFields.isEmpty(), "equals does not cover fields: " + uncoveredFields); + } + + private static LogProbe createFullProbe1() { + return LogProbe.builder() + .language(LANGUAGE) + .probeId("probe-id-1", 1) + .tags("tag1:value1") + .where("Class1", "method1") + .evaluateAt(MethodLocation.ENTRY) + .template("{arg1}", parseTemplate("{arg1}")) + .captureSnapshot(true) + .when(new ProbeCondition(DSL.when(DSL.TRUE), "true")) + .capture(1, 2, 3, 4) + .sampling(1.0) + .captureExpressions( + singletonList( + new LogProbe.CaptureExpression( + "expr1", new ValueScript(DSL.ref("arg1"), "arg1"), null))) + .build(); + } + + private static LogProbe createFullProbe2() { + return LogProbe.builder() + .language("other") + .probeId("probe-id-2", 2) + .tags("tag2:value2") + .where("Class2", "method2") + .evaluateAt(MethodLocation.EXIT) + .template("{arg2}", parseTemplate("{arg2}")) + .captureSnapshot(false) + .when(new ProbeCondition(DSL.when(DSL.FALSE), "false")) + .capture(5, 6, 7, 8) + .sampling(2.0) + .captureExpressions( + singletonList( + new LogProbe.CaptureExpression( + "expr2", new ValueScript(DSL.ref("arg2"), "arg2"), null))) + .build(); + } + @Test public void coordinatedSamplingEmitsProbeOnceConcurrently() { LogProbe.CoordinatedSamplingState state =