diff --git a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java index f31cec13f09..a77b4ac93a7 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java @@ -25,8 +25,8 @@ import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.internal.CodeCache; import org.hyperledger.besu.evm.internal.EvmConfiguration; -import org.hyperledger.besu.evm.internal.FixedStack.OverflowException; -import org.hyperledger.besu.evm.internal.FixedStack.UnderflowException; +import org.hyperledger.besu.evm.internal.OverflowException; +import org.hyperledger.besu.evm.internal.UnderflowException; import org.hyperledger.besu.evm.operation.AddModOperation; import org.hyperledger.besu.evm.operation.AddOperation; import org.hyperledger.besu.evm.operation.AndOperation; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java index 64c865dfd41..9eca8d88429 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java @@ -26,11 +26,11 @@ import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.code.CodeSection; -import org.hyperledger.besu.evm.internal.FixedStack.UnderflowException; import org.hyperledger.besu.evm.internal.MemoryEntry; import org.hyperledger.besu.evm.internal.OperandStack; import org.hyperledger.besu.evm.internal.ReturnStack; import org.hyperledger.besu.evm.internal.StorageEntry; +import org.hyperledger.besu.evm.internal.UnderflowException; import org.hyperledger.besu.evm.log.Log; import org.hyperledger.besu.evm.operation.Operation; import org.hyperledger.besu.evm.worldstate.WorldUpdater; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/internal/FixedStack.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/FixedStack.java index c18ee0aeda1..21b913f363c 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/internal/FixedStack.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/internal/FixedStack.java @@ -20,7 +20,8 @@ import java.util.Arrays; /** - * An operand stack for the Ethereum Virtual machine (EVM). + * An operand stack for the Ethereum Virtual machine (EVM). This stack allocates the entire stack at + * initialization time. * *

The operand stack is responsible for storing the current operands that the EVM can execute. It * is assumed to have a fixed size. @@ -35,30 +36,6 @@ public class FixedStack { private int top; - /** The Underflow exception. */ - public static class UnderflowException extends RuntimeException { - // Don't create a stack trace since these are not "errors" per say but are using exceptions to - // throw rare control flow conditions (EVM stack overflow) that are expected to be seen in - // normal - // operations. - @Override - public synchronized Throwable fillInStackTrace() { - return this; - } - } - - /** The Overflow exception. */ - public static class OverflowException extends RuntimeException { - // Don't create a stack trace since these are not "errors" per say but are using exceptions to - // throw rare control flow conditions (EVM stack overflow) that are expected to be seen in - // normal - // operations. - @Override - public synchronized Throwable fillInStackTrace() { - return this; - } - } - /** * Instantiates a new Fixed stack. * diff --git a/evm/src/main/java/org/hyperledger/besu/evm/internal/FlexStack.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/FlexStack.java new file mode 100644 index 00000000000..861cfbce8f9 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/internal/FlexStack.java @@ -0,0 +1,253 @@ +/* + * Copyright contributors to Hyperledger Besu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.internal; + +import static com.google.common.base.Preconditions.checkArgument; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Objects; + +/** + * An operand stack for the Ethereum Virtual machine (EVM). The stack grows 32 entries at a time if + * it expands past the top of the allocated stack, up to maxSize. + * + *

The operand stack is responsible for storing the current operands that the EVM can execute. It + * is assumed to have a fixed maximum size but may have a smaller memory footprint. + * + * @param the type parameter + */ +public class FlexStack { + + private static final int INCREMENT = 32; + + private T[] entries; + + private final int maxSize; + private int currentCapacity; + + private int top; + + /** + * Instantiates a new Flex stack. + * + * @param maxSize the max size + * @param klass the klass + */ + @SuppressWarnings("unchecked") + public FlexStack(final int maxSize, final Class klass) { + checkArgument(maxSize > 0, "max size must be positive"); + + this.currentCapacity = Math.min(INCREMENT, maxSize); + this.entries = (T[]) Array.newInstance(klass, currentCapacity); + this.maxSize = maxSize; + this.top = -1; + } + + /** + * Get operand. + * + * @param offset the offset + * @return the operand + */ + public T get(final int offset) { + if (offset < 0 || offset >= size()) { + throw new UnderflowException(); + } + + return entries[top - offset]; + } + + /** + * Pop operand. + * + * @return the operand + */ + public T pop() { + if (top < 0) { + throw new UnderflowException(); + } + + final T removed = entries[top]; + entries[top--] = null; + return removed; + } + + /** + * Peek and return type T. + * + * @return the T entry + */ + public T peek() { + if (top < 0) { + return null; + } else { + return entries[top]; + } + } + + /** + * Pops the specified number of operands from the stack. + * + * @param items the number of operands to pop off the stack + * @throws IllegalArgumentException if the items to pop is negative. + * @throws UnderflowException when the items to pop is greater than {@link #size()} + */ + public void bulkPop(final int items) { + checkArgument(items > 0, "number of items to pop must be greater than 0"); + if (items > size()) { + throw new UnderflowException(); + } + + Arrays.fill(entries, top - items + 1, top + 1, null); + top -= items; + } + + /** + * Trims the "middle" section of items out of the stack. Items below the cutpoint remains, and of + * the items above only the itemsToKeep items remain. All items in the middle are removed. + * + * @param cutPoint Point at which to start removing items + * @param itemsToKeep itemsToKeep Number of items on top to place at the cutPoint + * @throws IllegalArgumentException if the cutPoint or items to keep is negative. + * @throws UnderflowException If there are less than itemsToKeep above the cutPoint + */ + public void preserveTop(final int cutPoint, final int itemsToKeep) { + checkArgument(cutPoint >= 0, "cutPoint must be positive"); + checkArgument(itemsToKeep >= 0, "itemsToKeep must be positive"); + if (itemsToKeep == 0) { + if (cutPoint < size()) { + bulkPop(top - cutPoint); + } + } else { + int targetSize = cutPoint + itemsToKeep; + int currentSize = size(); + if (targetSize > currentSize) { + throw new UnderflowException(); + } else if (targetSize < currentSize) { + System.arraycopy(entries, currentSize - itemsToKeep, entries, cutPoint, itemsToKeep); + Arrays.fill(entries, targetSize, currentSize, null); + top = targetSize - 1; + } + } + } + + @SuppressWarnings("unchecked") + private void expandEntries(final int nextSize) { + var nextEntries = (T[]) Array.newInstance(entries.getClass().getComponentType(), nextSize); + System.arraycopy(entries, 0, nextEntries, 0, currentCapacity); + entries = nextEntries; + currentCapacity = nextSize; + } + + /** + * Push operand. + * + * @param operand the operand + */ + public void push(final T operand) { + final int nextTop = top + 1; + if (nextTop >= maxSize) { + throw new OverflowException(); + } + if (nextTop >= currentCapacity) { + expandEntries(Math.min(currentCapacity + INCREMENT, maxSize)); + } + entries[nextTop] = operand; + top = nextTop; + } + + /** + * Set operand. + * + * @param offset the offset + * @param operand the operand + */ + public void set(final int offset, final T operand) { + if (offset < 0) { + throw new UnderflowException(); + } else if (offset > top) { + throw new OverflowException(); + } + + entries[top - offset] = operand; + } + + /** + * Size of entries. + * + * @return the size + */ + public int size() { + return top + 1; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + for (int i = 0; i < top; ++i) { + builder.append(String.format("%n0x%04X ", i)).append(entries[i]); + } + return builder.toString(); + } + + @Override + public int hashCode() { + int result = 1; + + for (int i = 0; i < currentCapacity; i++) { + result = 31 * result + (entries[i] == null ? 0 : entries[i].hashCode()); + } + + return result; + } + + @SuppressWarnings("unchecked") + @Override + public boolean equals(final Object other) { + if (!(other instanceof FlexStack)) { + return false; + } + + final FlexStack that = (FlexStack) other; + if (this.currentCapacity != that.currentCapacity) { + return false; + } + for (int i = 0; i < currentCapacity; i++) { + if (!Objects.deepEquals(this.entries[i], that.entries[i])) { + return false; + } + } + return true; + } + + /** + * Is stack full. + * + * @return the boolean + */ + public boolean isFull() { + return top + 1 >= maxSize; + } + + /** + * Is stack empty. + * + * @return the boolean + */ + public boolean isEmpty() { + return top < 0; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/internal/OperandStack.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/OperandStack.java index 60da0a98343..df27579a297 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/internal/OperandStack.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/internal/OperandStack.java @@ -19,7 +19,7 @@ import org.apache.tuweni.bytes.Bytes; /** The Operand stack. */ -public class OperandStack extends FixedStack { +public class OperandStack extends FlexStack { /** * Instantiates a new Operand stack. diff --git a/evm/src/main/java/org/hyperledger/besu/evm/internal/OverflowException.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/OverflowException.java new file mode 100644 index 00000000000..f2e4efba353 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/internal/OverflowException.java @@ -0,0 +1,34 @@ +/* + * Copyright contributors to Hyperledger Besu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.internal; + +/** + * Overflow exception for {@link FixedStack} and {@link FlexStack}. The main need for a separate + * class is to remove the stack trace generation as the exception is not used to signal a debuggable + * failure but instead an expected edge case the EVM should handle. + */ +public class OverflowException extends RuntimeException { + + /** + * Overload the stack trace fill in so no stack is filled in. This is done for performance reasons + * as this exception signals an expected corner case not a debuggable failure. + * + * @return the exception, with no stack trace filled in. + */ + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/internal/ReturnStack.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/ReturnStack.java index 1482aa704ab..565f9c8cdcf 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/internal/ReturnStack.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/internal/ReturnStack.java @@ -17,7 +17,7 @@ import java.util.Objects; /** The type Return stack. */ -public class ReturnStack extends FixedStack { +public class ReturnStack extends FlexStack { /** The type Return stack item. */ // Java17 convert to record diff --git a/evm/src/main/java/org/hyperledger/besu/evm/internal/UnderflowException.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/UnderflowException.java new file mode 100644 index 00000000000..5b794faa6c9 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/internal/UnderflowException.java @@ -0,0 +1,34 @@ +/* + * Copyright contributors to Hyperledger Besu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.internal; + +/** + * Underflow exception for {@link FixedStack} and {@link FlexStack}. The main need for a separate + * class is to remove the stack trace generation as the exception is not used to signal a debuggable + * failure but instead an expected edge case the EVM should handle. + */ +public class UnderflowException extends RuntimeException { + + /** + * Overload the stack trace fill in so no stack is filled in. This is done for performance reasons + * as this exception signals an expected corner case not a debuggable failure. + * + * @return the exception, with no stack trace filled in. + */ + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractFixedCostOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractFixedCostOperation.java index afccdb20eb2..30a7b79305e 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractFixedCostOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractFixedCostOperation.java @@ -20,8 +20,8 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.FixedStack.OverflowException; -import org.hyperledger.besu.evm.internal.FixedStack.UnderflowException; +import org.hyperledger.besu.evm.internal.OverflowException; +import org.hyperledger.besu.evm.internal.UnderflowException; /** The Abstract fixed cost operation. */ abstract class AbstractFixedCostOperation extends AbstractOperation { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/BalanceOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/BalanceOperation.java index 3b822d4f887..18538413355 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/BalanceOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/BalanceOperation.java @@ -20,8 +20,8 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.FixedStack.OverflowException; -import org.hyperledger.besu.evm.internal.FixedStack.UnderflowException; +import org.hyperledger.besu.evm.internal.OverflowException; +import org.hyperledger.besu.evm.internal.UnderflowException; import org.hyperledger.besu.evm.internal.Words; import org.apache.tuweni.bytes.Bytes; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java index 5ab9216903f..ba6cd22d9df 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java @@ -20,8 +20,8 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.FixedStack.OverflowException; -import org.hyperledger.besu.evm.internal.FixedStack.UnderflowException; +import org.hyperledger.besu.evm.internal.OverflowException; +import org.hyperledger.besu.evm.internal.UnderflowException; import org.hyperledger.besu.evm.internal.Words; import org.apache.tuweni.bytes.Bytes; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java index 6ab0f799dc7..9d891f7d37e 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java @@ -20,8 +20,8 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.FixedStack.OverflowException; -import org.hyperledger.besu.evm.internal.FixedStack.UnderflowException; +import org.hyperledger.besu.evm.internal.OverflowException; +import org.hyperledger.besu.evm.internal.UnderflowException; import org.hyperledger.besu.evm.internal.Words; import org.apache.tuweni.bytes.Bytes; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/SLoadOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/SLoadOperation.java index 087d18ab0ee..f97917a9935 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SLoadOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SLoadOperation.java @@ -20,8 +20,8 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.FixedStack.OverflowException; -import org.hyperledger.besu.evm.internal.FixedStack.UnderflowException; +import org.hyperledger.besu.evm.internal.OverflowException; +import org.hyperledger.besu.evm.internal.UnderflowException; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/TLoadOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/TLoadOperation.java index e813e72e1a9..7187e5d12b1 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/TLoadOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/TLoadOperation.java @@ -18,8 +18,8 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.FixedStack.OverflowException; -import org.hyperledger.besu.evm.internal.FixedStack.UnderflowException; +import org.hyperledger.besu.evm.internal.OverflowException; +import org.hyperledger.besu.evm.internal.UnderflowException; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; diff --git a/evm/src/test/java/org/hyperledger/besu/evm/internal/OperandStackTest.java b/evm/src/test/java/org/hyperledger/besu/evm/internal/OperandStackTest.java index 63afa470584..9a3e21f134e 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/internal/OperandStackTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/internal/OperandStackTest.java @@ -17,12 +17,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import org.hyperledger.besu.evm.internal.FixedStack.OverflowException; -import org.hyperledger.besu.evm.internal.FixedStack.UnderflowException; - import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; class OperandStackTest { @@ -99,6 +98,14 @@ void set_IndexGreaterThanSize() { assertThatThrownBy(() -> stack.set(2, operand)).isInstanceOf(OverflowException.class); } + @Test + void set_IndexGreaterThanCurrentSize() { + final OperandStack stack = new OperandStack(1024); + stack.push(UInt256.fromHexString("0x01")); + final Bytes32 operand = Bytes32.fromHexString("0x01"); + assertThatThrownBy(() -> stack.set(2, operand)).isInstanceOf(OverflowException.class); + } + @Test void set() { final OperandStack stack = new OperandStack(3); @@ -165,4 +172,14 @@ void preserveTop() { assertThatThrownBy(() -> stack.preserveTop(5, 1)).isInstanceOf(UnderflowException.class); assertThatThrownBy(() -> stack.preserveTop(1, 5)).isInstanceOf(UnderflowException.class); } + + @ParameterizedTest + @ValueSource(ints = {5, 31, 32, 33, 1023, 1024, 1025}) + void largeOverflows(final int n) { + final OperandStack stack = new OperandStack(n); + for (int i = 0; i < n; i++) { + stack.push(UInt256.ONE); + } + assertThatThrownBy(() -> stack.push(UInt256.ONE)).isInstanceOf(OverflowException.class); + } }