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