Bug 547462 - Fix inconsistency in infix expression evaluation
When evaluating a infix expression which contains extended operands,
then the number of no-op that are pushed are not stored in instructions
when leaving the infix expression. This cause problem with instruction
order when in situations where their is a prefix expression, where the
prefix expression will be popped too late into instructions.
The fix solves this issue by storing/popping the no-op from stack equal
to the number of extended operands in the infix expression before it
leaves.
Change-Id: I5451b68e34a00007736ec24ff421cd112b6f85fd
Signed-off-by: Gayan Perera <gayanper@gmail.com>
Reviewed-on: https://git.eclipse.org/r/c/jdt/eclipse.jdt.debug/+/180551
Reviewed-by: Jesper Moller <jesper@selskabet.org>
Tested-by: JDT Bot <jdt-bot@eclipse.org>
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java
index 9d072e6..7f93c69 100644
--- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java
@@ -92,6 +92,7 @@
import org.eclipse.jdt.debug.tests.core.WorkingDirectoryTests;
import org.eclipse.jdt.debug.tests.core.WorkspaceSourceContainerTests;
import org.eclipse.jdt.debug.tests.eval.BlockStatementEvaluationTests;
+import org.eclipse.jdt.debug.tests.eval.ExpressionEvalTest;
import org.eclipse.jdt.debug.tests.eval.GeneralEvalTests;
import org.eclipse.jdt.debug.tests.eval.GenericsEval17Test;
import org.eclipse.jdt.debug.tests.eval.GenericsEvalTests;
@@ -333,6 +334,7 @@
addTest(new TestSuite(GeneralEvalTests.class));
addTest(new TestSuite(GenericsEval17Test.class));
addTest(new TestSuite(BlockStatementEvaluationTests.class));
+ addTest(new TestSuite(ExpressionEvalTest.class));
if (JavaProjectHelper.isJava8Compatible()) {
addTest(new TestSuite(LambdaVariableTest.class));
}
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/eval/ExpressionEvalTest.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/eval/ExpressionEvalTest.java
new file mode 100644
index 0000000..8a59db3
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/eval/ExpressionEvalTest.java
@@ -0,0 +1,129 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Gayan Perera and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Gayan Perera - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.debug.tests.eval;
+
+import org.eclipse.debug.core.model.IValue;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.debug.core.IJavaThread;
+import org.eclipse.jdt.debug.tests.AbstractDebugTest;
+
+public class ExpressionEvalTest extends AbstractDebugTest {
+ private IJavaThread javaThread;
+
+ public ExpressionEvalTest(String name) {
+ super(name);
+ }
+
+ @Override
+ protected IJavaProject getProjectContext() {
+ return get14Project();
+ }
+
+ public void test547462_BooleanExpression_WithPrefixAndInfix_ExtendedOperands() throws Exception {
+ debugWithBreakpoint("EvalSimpleTests", 18);
+ IValue value = doEval(javaThread, "false && !(false || false || false);");
+
+ assertNotNull("value is null", value);
+ assertEquals("value is not false", "false", value.getValueString());
+ }
+
+ public void test547462_BooleanExpression_WithPrefixAndMixedInfix_ExtendedOperands() throws Exception {
+ debugWithBreakpoint("EvalSimpleTests", 18);
+ IValue value = doEval(javaThread, "false && !(false || false && false);");
+
+ assertNotNull("value is null", value);
+ assertEquals("value is not false", "false", value.getValueString());
+ }
+
+ public void test547462_BooleanExpression_WithPrefixAndInfix() throws Exception {
+ debugWithBreakpoint("EvalSimpleTests", 18);
+ IValue value = doEval(javaThread, "false && !(false || false);");
+
+ assertNotNull("value is null", value);
+ assertEquals("value is not false", "false", value.getValueString());
+ }
+
+ public void test547462_BooleanExpression_WithInfix() throws Exception {
+ debugWithBreakpoint("EvalSimpleTests", 18);
+ IValue value = doEval(javaThread, "true && (false || false);");
+
+ assertNotNull("value is null", value);
+ assertEquals("value is not false", "false", value.getValueString());
+ }
+
+ public void test547462_BooleanExpression() throws Exception {
+ debugWithBreakpoint("EvalSimpleTests", 18);
+ IValue value = doEval(javaThread, "false && false || true;");
+
+ assertNotNull("value is null", value);
+ assertEquals("value is not true", "true", value.getValueString());
+ }
+
+ public void test547462_BooleanExpression_NonShortCircuit() throws Exception {
+ debugWithBreakpoint("EvalSimpleTests", 18);
+ IValue value = doEval(javaThread, "false & false | true;");
+ // IValue value = doEval(javaThread, "!(false && false);");
+
+ assertNotNull("value is null", value);
+ assertEquals("value is not true", "true", value.getValueString());
+ }
+
+ public void test547462_BooleanExpression_WithPrefixAndInfix_NonShortCircuit() throws Exception {
+ debugWithBreakpoint("EvalSimpleTests", 18);
+ IValue value = doEval(javaThread, "false & !(false | false);");
+
+ assertNotNull("value is null", value);
+ assertEquals("value is not false", "false", value.getValueString());
+ }
+
+ public void test547462_BooleanExpression_WithPrefixAndInfix_ExtendedOperands_NonShortCircuit() throws Exception {
+ debugWithBreakpoint("EvalSimpleTests", 18);
+ IValue value = doEval(javaThread, "false & !(false | false | false);");
+
+ assertNotNull("value is null", value);
+ assertEquals("value is not false", "false", value.getValueString());
+ }
+
+ public void test547462_BooleanExpression_WithPrefixAndMixedInfix_ExtendedOperands_NonShortCircuit() throws Exception {
+ debugWithBreakpoint("EvalSimpleTests", 18);
+ IValue value = doEval(javaThread, "false & !(false | false & false);");
+
+ assertNotNull("value is null", value);
+ assertEquals("value is not false", "false", value.getValueString());
+ }
+
+ public void test547462_BooleanExpression_WithInfix_NonShortCircuit() throws Exception {
+ debugWithBreakpoint("EvalSimpleTests", 18);
+ IValue value = doEval(javaThread, "true & (false | false);");
+
+ assertNotNull("value is null", value);
+ assertEquals("value is not false", "false", value.getValueString());
+ }
+
+ private void debugWithBreakpoint(String testClass, int lineNumber) throws Exception {
+ createLineBreakpoint(lineNumber, testClass);
+ javaThread = launchToBreakpoint(testClass);
+ assertNotNull("The program did not suspend", javaThread);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ try {
+ terminateAndRemove(javaThread);
+ } finally {
+ super.tearDown();
+ removeAllBreakpoints();
+ }
+ }
+}
diff --git a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/ASTInstructionCompiler.java b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/ASTInstructionCompiler.java
index 38aaf4b..231127e 100644
--- a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/ASTInstructionCompiler.java
+++ b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/ASTInstructionCompiler.java
@@ -2861,7 +2861,12 @@
storeInstruction();
// store the no-op
- storeInstruction();
+ // since we add no-op for number of operators (there will be more than one when we have extendedOperands),
+ // we need to store them as well to make sure the next expression such as PrefixExpressions can properly
+ // store the instruction in correct place.
+ for (int i = 0; i < operatorNumber; i++) {
+ storeInstruction();
+ }
} else { // other operators