mirror of
https://github.com/vacp2p/linea-besu.git
synced 2026-01-08 04:33:56 -05:00
EOF Fuzzing Fixes (#4872)
Fix a variety of issues found during the fuzzing sprint. * Validate type length matches code length * Remove JUMPF from shanghai * check retf output size * handle zero length types and code better * Fix table error on RETURNDATACOPY * RJUMPV byte should be unsigned * RJUMPV stack validation fix and more tests * dead code detection via counting considered bytes * EVMTool EOF Fuzzing support- remove all alphanumerics (punctuation) and comment lines from code in CLI Signed-off-by: Danno Ferrin <danno.ferrin@swirldslabs.com>
This commit is contained in:
@@ -20,15 +20,20 @@ import static org.hyperledger.besu.evmtool.CodeValidateSubCommand.COMMAND_NAME;
|
||||
import org.hyperledger.besu.datatypes.Hash;
|
||||
import org.hyperledger.besu.evm.code.CodeFactory;
|
||||
import org.hyperledger.besu.evm.code.CodeInvalid;
|
||||
import org.hyperledger.besu.evm.code.CodeSection;
|
||||
import org.hyperledger.besu.evm.code.EOFLayout;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import org.apache.tuweni.bytes.Bytes;
|
||||
import picocli.CommandLine;
|
||||
@@ -43,6 +48,11 @@ public class CodeValidateSubCommand implements Runnable {
|
||||
private final InputStream input;
|
||||
private final PrintStream output;
|
||||
|
||||
@CommandLine.Option(
|
||||
names = {"--file"},
|
||||
description = "A file containing a set of inputs")
|
||||
private final File codeFile = null;
|
||||
|
||||
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection") // picocli does it magically
|
||||
@CommandLine.Parameters
|
||||
private final List<String> cliCode = new ArrayList<>();
|
||||
@@ -60,40 +70,65 @@ public class CodeValidateSubCommand implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (cliCode.isEmpty()) {
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(input, UTF_8));
|
||||
try {
|
||||
for (String code = in.readLine(); code != null; code = in.readLine()) {
|
||||
output.println(considerCode(code));
|
||||
}
|
||||
if (cliCode.isEmpty() && codeFile == null) {
|
||||
try (BufferedReader in = new BufferedReader(new InputStreamReader(input, UTF_8))) {
|
||||
checkCodeFromBufferedReader(in);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
for (String code : cliCode) {
|
||||
output.println(considerCode(code));
|
||||
if (codeFile != null) {
|
||||
try (BufferedReader in = new BufferedReader(new FileReader(codeFile, UTF_8))) {
|
||||
checkCodeFromBufferedReader(in);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
for (String code : cliCode) {
|
||||
output.print(considerCode(code));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkCodeFromBufferedReader(final BufferedReader in) {
|
||||
try {
|
||||
for (String code = in.readLine(); code != null; code = in.readLine()) {
|
||||
output.print(considerCode(code));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public String considerCode(final String hexCode) {
|
||||
Bytes codeBytes;
|
||||
try {
|
||||
codeBytes = Bytes.fromHexString(hexCode.replaceAll("\\s+", ""));
|
||||
codeBytes =
|
||||
Bytes.fromHexString(
|
||||
hexCode.replaceAll("(^|\n)#[^\n]*($|\n)", "").replaceAll("[^0-9A-Za-z]", ""));
|
||||
} catch (RuntimeException re) {
|
||||
return "err: hex string -" + re;
|
||||
return "err: hex string -" + re + "\n";
|
||||
}
|
||||
if (codeBytes.size() == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
var layout = EOFLayout.parseEOF(codeBytes);
|
||||
if (!layout.isValid()) {
|
||||
return "err: layout - " + layout.getInvalidReason();
|
||||
return "err: layout - " + layout.getInvalidReason() + "\n";
|
||||
}
|
||||
|
||||
var code = CodeFactory.createCode(codeBytes, Hash.hash(codeBytes), 1, true);
|
||||
if (!code.isValid()) {
|
||||
return "err: " + ((CodeInvalid) code).getInvalidReason();
|
||||
return "err: " + ((CodeInvalid) code).getInvalidReason() + "\n";
|
||||
}
|
||||
|
||||
return "OK " + code.getCodeBytes(0).toUnprefixedHexString();
|
||||
return "OK "
|
||||
+ IntStream.range(0, code.getCodeSectionCount())
|
||||
.mapToObj(code::getCodeSection)
|
||||
.map(CodeSection::getCode)
|
||||
.map(Bytes::toUnprefixedHexString)
|
||||
.collect(Collectors.joining(","))
|
||||
+ "\n";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,9 +26,16 @@ import picocli.CommandLine;
|
||||
|
||||
public class CodeValidationSubCommandTest {
|
||||
|
||||
static final String CODE_STOP_ONLY = "0xef0001 010001 020001 0001 030000 00 00000000 00";
|
||||
static final String CODE_RETF_ONLY = "0xef0001 010001 020001 0001 030000 00 00000000 b1";
|
||||
static final String CODE_BAD_MAGIC = "0xefffff 010001 020001 0001 030000 00 00000000 b1";
|
||||
static final String CODE_STOP_ONLY = "0xef0001 010004 020001-0001 030000 00 00000000 00";
|
||||
static final String CODE_RETF_ONLY = "0xef0001 010004 020001-0001 030000 00 00000000 b1";
|
||||
static final String CODE_BAD_MAGIC = "0xefffff 010004 020001-0001 030000 00 00000000 b1";
|
||||
static final String CODE_INTERIOR_COMMENTS =
|
||||
"0xef0001 010008 020002-000c-0002 030000 00 \n"
|
||||
+ "# 7 inputs 1 output,\n"
|
||||
+ "00000007-07010007 \n"
|
||||
+ "59-59-59-59-59-59-59-b00001-50-b1\n"
|
||||
+ "# No immediate data\n"
|
||||
+ "f1-b1";
|
||||
static final String CODE_MULTIPLE =
|
||||
CODE_STOP_ONLY + "\n" + CODE_BAD_MAGIC + "\n" + CODE_RETF_ONLY + "\n";
|
||||
|
||||
@@ -111,4 +118,28 @@ public class CodeValidationSubCommandTest {
|
||||
codeValidateSubCommand.run();
|
||||
assertThat(baos.toString(UTF_8)).contains("OK b1\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInteriorCommentsSkipped() {
|
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
final ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]);
|
||||
final CodeValidateSubCommand codeValidateSubCommand =
|
||||
new CodeValidateSubCommand(bais, new PrintStream(baos));
|
||||
final CommandLine cmd = new CommandLine(codeValidateSubCommand);
|
||||
cmd.parseArgs(CODE_INTERIOR_COMMENTS);
|
||||
codeValidateSubCommand.run();
|
||||
assertThat(baos.toString(UTF_8)).contains("OK 59595959595959b0000150b1,f1b1\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlankLinesAndCommentsSkipped() {
|
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
final ByteArrayInputStream bais =
|
||||
new ByteArrayInputStream(("# comment\n\n#blank line\n\n" + CODE_MULTIPLE).getBytes(UTF_8));
|
||||
final CodeValidateSubCommand codeValidateSubCommand =
|
||||
new CodeValidateSubCommand(bais, new PrintStream(baos));
|
||||
codeValidateSubCommand.run();
|
||||
assertThat(baos.toString(UTF_8))
|
||||
.isEqualTo("OK 00\n" + "err: layout - EOF header byte 1 incorrect\n" + "OK b1\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,4 +77,11 @@ public interface Code {
|
||||
* @return The code section, or null of there is no associated section
|
||||
*/
|
||||
CodeSection getCodeSection(final int section);
|
||||
|
||||
/**
|
||||
* The number of code sections in this container.
|
||||
*
|
||||
* @return 1 for legacy, count for valid, zero for invalid.
|
||||
*/
|
||||
int getCodeSectionCount();
|
||||
}
|
||||
|
||||
@@ -279,7 +279,6 @@ public class EVM {
|
||||
break;
|
||||
case 0xb0: // CALLF
|
||||
case 0xb1: // RETF
|
||||
case 0xb2: // JUMPF
|
||||
// Function operations reset code
|
||||
if (enableCancun) {
|
||||
frame.setCurrentOperation(currentOperation);
|
||||
|
||||
@@ -64,7 +64,6 @@ import org.hyperledger.besu.evm.operation.GtOperation;
|
||||
import org.hyperledger.besu.evm.operation.InvalidOperation;
|
||||
import org.hyperledger.besu.evm.operation.IsZeroOperation;
|
||||
import org.hyperledger.besu.evm.operation.JumpDestOperation;
|
||||
import org.hyperledger.besu.evm.operation.JumpFOperation;
|
||||
import org.hyperledger.besu.evm.operation.JumpOperation;
|
||||
import org.hyperledger.besu.evm.operation.JumpiOperation;
|
||||
import org.hyperledger.besu.evm.operation.Keccak256Operation;
|
||||
@@ -492,7 +491,6 @@ public class MainnetEVMs {
|
||||
registry.put(new RelativeJumpVectorOperation(gasCalculator));
|
||||
registry.put(new CallFOperation(gasCalculator));
|
||||
registry.put(new RetFOperation(gasCalculator));
|
||||
registry.put(new JumpFOperation(gasCalculator));
|
||||
}
|
||||
|
||||
public static EVM futureEips(final BigInteger chainId, final EvmConfiguration evmConfiguration) {
|
||||
|
||||
@@ -76,4 +76,9 @@ public class CodeInvalid implements Code {
|
||||
public CodeSection getCodeSection(final int section) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCodeSectionCount() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,6 +128,11 @@ public class CodeV0 implements Code {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCodeSectionCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
long[] calculateJumpDests() {
|
||||
final int size = getSize();
|
||||
final long[] bitmap = new long[(size >> 6) + 1];
|
||||
|
||||
@@ -23,11 +23,11 @@ import static org.hyperledger.besu.evm.internal.Words.readBigEndianU16;
|
||||
import org.hyperledger.besu.datatypes.Hash;
|
||||
import org.hyperledger.besu.evm.Code;
|
||||
import org.hyperledger.besu.evm.operation.CallFOperation;
|
||||
import org.hyperledger.besu.evm.operation.JumpFOperation;
|
||||
import org.hyperledger.besu.evm.operation.PushOperation;
|
||||
import org.hyperledger.besu.evm.operation.RelativeJumpIfOperation;
|
||||
import org.hyperledger.besu.evm.operation.RelativeJumpOperation;
|
||||
import org.hyperledger.besu.evm.operation.RelativeJumpVectorOperation;
|
||||
import org.hyperledger.besu.evm.operation.RetFOperation;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.BitSet;
|
||||
@@ -42,7 +42,7 @@ public class CodeV1 implements Code {
|
||||
static final byte VALID = 0x02;
|
||||
static final byte TERMINAL = 0x04;
|
||||
static final byte VALID_AND_TERMINAL = VALID | TERMINAL;
|
||||
private static final byte[] opcodeAttributes = {
|
||||
static final byte[] OPCODE_ATTRIBUTES = {
|
||||
VALID_AND_TERMINAL, // 0x00 STOP
|
||||
VALID, // 0x01 - ADD
|
||||
VALID, // 0x02 - MUL
|
||||
@@ -135,7 +135,7 @@ public class CodeV1 implements Code {
|
||||
VALID, // 0x59 - MSIZE
|
||||
VALID, // 0x5a - GAS
|
||||
VALID, // 0x5b - NOOOP (née JUMPDEST)
|
||||
VALID, // 0X5c - RJUMP
|
||||
VALID_AND_TERMINAL, // 0X5c - RJUMP
|
||||
VALID, // 0X5d - RJUMPI
|
||||
VALID, // 0X5e - RJUMPV
|
||||
VALID, // 0X5f - PUSH0
|
||||
@@ -221,7 +221,7 @@ public class CodeV1 implements Code {
|
||||
INVALID, // 0xaf
|
||||
VALID, // 0xb0 - CALLF
|
||||
VALID_AND_TERMINAL, // 0xb1 - RETF
|
||||
VALID_AND_TERMINAL, // 0xb2 - JUMPF
|
||||
INVALID, // 0xb2 - JUMPF
|
||||
INVALID, // 0xb3
|
||||
INVALID, // 0xb4
|
||||
INVALID, // 0xb5
|
||||
@@ -305,7 +305,7 @@ public class CodeV1 implements Code {
|
||||
// [0] - stack input consumed
|
||||
// [1] - stack outputs added
|
||||
// [2] - PC advance
|
||||
private static final byte[][] opcodeStackValidation = {
|
||||
static final byte[][] OPCODE_STACK_VALIDATION = {
|
||||
{0, 0, -1}, // 0x00 - STOP
|
||||
{2, 1, 1}, // 0x01 - ADD
|
||||
{2, 1, 1}, // 0x02 - MUL
|
||||
@@ -327,7 +327,7 @@ public class CodeV1 implements Code {
|
||||
{2, 1, 1}, // 0x12 - SLT
|
||||
{2, 1, 1}, // 0x13 - SGT
|
||||
{2, 1, 1}, // 0x14 - EQ
|
||||
{2, 1, 1}, // 0x15 - ISZERO
|
||||
{1, 1, 1}, // 0x15 - ISZERO
|
||||
{2, 1, 1}, // 0x16 - AND
|
||||
{2, 1, 1}, // 0x17 - OR
|
||||
{2, 1, 1}, // 0x18 - XOR
|
||||
@@ -366,9 +366,9 @@ public class CodeV1 implements Code {
|
||||
{3, 0, 1}, // 0x39 - CODECOPY
|
||||
{0, 1, 1}, // 0x3a - GASPRICE
|
||||
{1, 1, 1}, // 0x3b - EXTCODESIZE
|
||||
{4, 1, 1}, // 0x3c - EXTCODECOPY
|
||||
{4, 0, 1}, // 0x3c - EXTCODECOPY
|
||||
{0, 1, 1}, // 0x3d - RETURNDATASIZE
|
||||
{3, 1, 1}, // 0x3e - RETURNDATACOPY
|
||||
{3, 0, 1}, // 0x3e - RETURNDATACOPY
|
||||
{1, 1, 1}, // 0x3f - EXTCODEHASH
|
||||
{1, 1, 1}, // 0x40 - BLOCKHASH
|
||||
{0, 1, 1}, // 0x41 - COINBASE
|
||||
@@ -398,7 +398,7 @@ public class CodeV1 implements Code {
|
||||
{0, 1, 1}, // 0x59 - MSIZE
|
||||
{0, 1, 1}, // 0x5a - GAS
|
||||
{0, 0, 1}, // 0x5b - NOOP (née JUMPDEST)
|
||||
{0, 0, -1}, // 0x5c - RJUMP
|
||||
{0, 0, -3}, // 0x5c - RJUMP
|
||||
{1, 0, 3}, // 0x5d - RJUMPI
|
||||
{1, 0, 2}, // 0x5e - RJUMPV
|
||||
{0, 1, 1}, // 0x5f - PUSH0
|
||||
@@ -484,7 +484,7 @@ public class CodeV1 implements Code {
|
||||
{0, 0, 0}, // 0xaf
|
||||
{0, 0, 3}, // 0xb0 - CALLF
|
||||
{0, 0, -1}, // 0xb1 - RETF
|
||||
{0, 0, -1}, // 0xb2 - JUMPF
|
||||
{0, 0, 0}, // 0xb2 - JUMPF
|
||||
{0, 0, 0}, // 0xb3
|
||||
{0, 0, 0}, // 0xb4
|
||||
{0, 0, 0}, // 0xb5
|
||||
@@ -548,7 +548,7 @@ public class CodeV1 implements Code {
|
||||
{0, 0, 0}, // 0xef
|
||||
{3, 1, 1}, // 0xf0 - CREATE
|
||||
{7, 1, 1}, // 0xf1 - CALL
|
||||
{0, 1, 1}, // 0xf2 - CALLCODE
|
||||
{0, 0, 0}, // 0xf2 - CALLCODE
|
||||
{2, 0, -1}, // 0xf3 - RETURN
|
||||
{6, 1, 1}, // 0xf4 - DELEGATECALL
|
||||
{4, 1, 1}, // 0xf5 - CREATE2
|
||||
@@ -598,10 +598,10 @@ public class CodeV1 implements Code {
|
||||
int pos = 0;
|
||||
while (pos < size) {
|
||||
final int operationNum = rawCode[pos] & 0xff;
|
||||
attribute = opcodeAttributes[operationNum];
|
||||
attribute = OPCODE_ATTRIBUTES[operationNum];
|
||||
if ((attribute & INVALID) == INVALID) {
|
||||
// undefined instruction
|
||||
return "Invalid Instruction 0x" + Integer.toHexString(operationNum);
|
||||
return String.format("Invalid Instruction 0x%02x", operationNum);
|
||||
}
|
||||
pos += 1;
|
||||
int pcPostInstruction = pos;
|
||||
@@ -640,13 +640,13 @@ public class CodeV1 implements Code {
|
||||
}
|
||||
rjumpdests.set(rjumpdest);
|
||||
}
|
||||
} else if (operationNum == CallFOperation.OPCODE || operationNum == JumpFOperation.OPCODE) {
|
||||
} else if (operationNum == CallFOperation.OPCODE) {
|
||||
if (pos + 2 > size) {
|
||||
return "Truncated CALLF/JUMPF";
|
||||
return "Truncated CALLF";
|
||||
}
|
||||
int section = readBigEndianU16(pos, rawCode);
|
||||
if (section >= sectionCount) {
|
||||
return "CALLF/JUMPF to non-existent section - " + Integer.toHexString(section);
|
||||
return "CALLF to non-existent section - " + Integer.toHexString(section);
|
||||
}
|
||||
pcPostInstruction += 2;
|
||||
}
|
||||
@@ -677,107 +677,123 @@ public class CodeV1 implements Code {
|
||||
* immediates as well as no immediates falling off of the end of code sections.
|
||||
*
|
||||
* @param codeSectionToValidate The index of code to validate in the code sections
|
||||
* @param codeSections The code sections to use for CALLF and JUMPF reference validation.
|
||||
* @param codeSections The code sections to use for CALLF reference validation.
|
||||
* @return null if valid, otherwise an error string providing the validation error.
|
||||
*/
|
||||
public static String validateStack(
|
||||
final int codeSectionToValidate, final CodeSection[] codeSections) {
|
||||
byte[] code = codeSections[codeSectionToValidate].code.toArrayUnsafe();
|
||||
int codeLength = code.length;
|
||||
int[] stackHeights = new int[codeLength];
|
||||
Arrays.fill(stackHeights, -1);
|
||||
try {
|
||||
byte[] code = codeSections[codeSectionToValidate].code.toArrayUnsafe();
|
||||
int codeLength = code.length;
|
||||
int[] stackHeights = new int[codeLength];
|
||||
Arrays.fill(stackHeights, -1);
|
||||
|
||||
int thisWork = 0;
|
||||
int maxWork = 1;
|
||||
int[][] workList = new int[codeLength][2];
|
||||
int thisWork = 0;
|
||||
int maxWork = 1;
|
||||
int[][] workList = new int[codeLength][2];
|
||||
|
||||
int initialStackHeight = codeSections[codeSectionToValidate].getInputs();
|
||||
int maxStackHeight = initialStackHeight;
|
||||
stackHeights[0] = initialStackHeight;
|
||||
workList[0][1] = initialStackHeight;
|
||||
int initialStackHeight = codeSections[codeSectionToValidate].getInputs();
|
||||
int maxStackHeight = initialStackHeight;
|
||||
stackHeights[0] = initialStackHeight;
|
||||
workList[0][1] = initialStackHeight;
|
||||
int unusedBytes = codeLength;
|
||||
|
||||
while (thisWork < maxWork) {
|
||||
int currentPC = workList[thisWork][0];
|
||||
int currentStackHeight = workList[thisWork][1];
|
||||
if (thisWork > 0 && stackHeights[currentPC] >= 0) {
|
||||
// we've been here, validate the jump is what is expected
|
||||
if (stackHeights[currentPC] != currentStackHeight) {
|
||||
return String.format(
|
||||
"Jump into code stack height (%d) does not match previous value (%d)",
|
||||
stackHeights[currentPC], currentStackHeight);
|
||||
while (thisWork < maxWork) {
|
||||
int currentPC = workList[thisWork][0];
|
||||
int currentStackHeight = workList[thisWork][1];
|
||||
if (thisWork > 0 && stackHeights[currentPC] >= 0) {
|
||||
// we've been here, validate the jump is what is expected
|
||||
if (stackHeights[currentPC] != currentStackHeight) {
|
||||
return String.format(
|
||||
"Jump into code stack height (%d) does not match previous value (%d)",
|
||||
stackHeights[currentPC], currentStackHeight);
|
||||
} else {
|
||||
thisWork++;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
thisWork++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
while (currentPC < codeLength) {
|
||||
int thisOp = code[currentPC] & 0xff;
|
||||
int recordedStack = stackHeights[currentPC];
|
||||
if (recordedStack >= 0 && currentStackHeight != recordedStack) {
|
||||
return String.format(
|
||||
"Stack height mismatch %d/%d at %d", recordedStack, currentStackHeight, currentPC);
|
||||
stackHeights[currentPC] = currentStackHeight;
|
||||
}
|
||||
|
||||
byte[] stackInfo = opcodeStackValidation[thisOp];
|
||||
int stackInputs;
|
||||
int stackOutputs;
|
||||
int pcAdvance = stackInfo[2];
|
||||
if (pcAdvance == 0) {
|
||||
return String.format("Invalid opcode 0x%02X", thisOp);
|
||||
}
|
||||
if (thisOp == CallFOperation.OPCODE || thisOp == JumpFOperation.OPCODE) {
|
||||
int section = readBigEndianU16(currentPC + 1, code);
|
||||
stackInputs = codeSections[section].getInputs();
|
||||
stackOutputs = codeSections[section].getOutputs();
|
||||
} else {
|
||||
stackInputs = stackInfo[0];
|
||||
stackOutputs = stackInfo[1];
|
||||
}
|
||||
while (currentPC < codeLength) {
|
||||
int thisOp = code[currentPC] & 0xff;
|
||||
|
||||
if (stackInputs > currentStackHeight) {
|
||||
return String.format(
|
||||
"Operation 0x%02X requires stack of %d but only has %d items",
|
||||
thisOp, stackInputs, currentStackHeight);
|
||||
}
|
||||
byte[] stackInfo = OPCODE_STACK_VALIDATION[thisOp];
|
||||
int stackInputs;
|
||||
int stackOutputs;
|
||||
int pcAdvance = stackInfo[2];
|
||||
if (thisOp == CallFOperation.OPCODE) {
|
||||
int section = readBigEndianU16(currentPC + 1, code);
|
||||
stackInputs = codeSections[section].getInputs();
|
||||
stackOutputs = codeSections[section].getOutputs();
|
||||
} else {
|
||||
stackInputs = stackInfo[0];
|
||||
stackOutputs = stackInfo[1];
|
||||
}
|
||||
|
||||
currentStackHeight = currentStackHeight - stackInputs + stackOutputs;
|
||||
if (currentStackHeight > MAX_STACK_HEIGHT) {
|
||||
return "Stack height exceeds 1024";
|
||||
}
|
||||
if (stackInputs > currentStackHeight) {
|
||||
return String.format(
|
||||
"Operation 0x%02X requires stack of %d but only has %d items",
|
||||
thisOp, stackInputs, currentStackHeight);
|
||||
}
|
||||
|
||||
maxStackHeight = Math.max(maxStackHeight, currentStackHeight);
|
||||
currentStackHeight = currentStackHeight - stackInputs + stackOutputs;
|
||||
if (currentStackHeight > MAX_STACK_HEIGHT) {
|
||||
return "Stack height exceeds 1024";
|
||||
}
|
||||
|
||||
if (thisOp == RelativeJumpOperation.OPCODE || thisOp == RelativeJumpIfOperation.OPCODE) {
|
||||
// no `& 0xff` on high byte because this is one case we want sign extension
|
||||
int rvalue = readBigEndianI16(currentPC + 1, code);
|
||||
workList[maxWork] = new int[] {currentPC + rvalue + 3, currentStackHeight};
|
||||
maxWork++;
|
||||
} else if (thisOp == RelativeJumpVectorOperation.OPCODE) {
|
||||
int tableEnd = code[currentPC + 1] * 2 + currentPC + 2;
|
||||
for (int i = currentPC + 2; i < tableEnd; i += 2) {
|
||||
int rvalue = readBigEndianI16(i + 1, code);
|
||||
maxStackHeight = Math.max(maxStackHeight, currentStackHeight);
|
||||
|
||||
if (thisOp == RelativeJumpOperation.OPCODE || thisOp == RelativeJumpIfOperation.OPCODE) {
|
||||
// no `& 0xff` on high byte because this is one case we want sign extension
|
||||
int rvalue = readBigEndianI16(currentPC + 1, code);
|
||||
workList[maxWork] = new int[] {currentPC + rvalue + 3, currentStackHeight};
|
||||
maxWork++;
|
||||
} else if (thisOp == RelativeJumpVectorOperation.OPCODE) {
|
||||
int immediateDataSize = (code[currentPC + 1] & 0xff) * 2;
|
||||
unusedBytes -= immediateDataSize;
|
||||
int tableEnd = immediateDataSize + currentPC + 2;
|
||||
for (int i = currentPC + 2; i < tableEnd; i += 2) {
|
||||
int rvalue = readBigEndianI16(i, code);
|
||||
workList[maxWork] = new int[] {tableEnd + rvalue, currentStackHeight};
|
||||
maxWork++;
|
||||
}
|
||||
currentPC = tableEnd - 2;
|
||||
} else if (thisOp == RetFOperation.OPCODE) {
|
||||
int returnStackItems = codeSections[codeSectionToValidate].getOutputs();
|
||||
if (currentStackHeight != returnStackItems) {
|
||||
return String.format(
|
||||
"Section return (RETF) calculated height 0x%x does not match configured height 0x%x",
|
||||
currentStackHeight, returnStackItems);
|
||||
}
|
||||
}
|
||||
currentPC = tableEnd - 2;
|
||||
if (pcAdvance < 0) {
|
||||
unusedBytes += pcAdvance;
|
||||
break;
|
||||
} else if (pcAdvance == 0) {
|
||||
return String.format("Invalid Instruction 0x%02x", thisOp);
|
||||
}
|
||||
|
||||
currentPC += pcAdvance;
|
||||
stackHeights[currentPC] = currentStackHeight;
|
||||
unusedBytes -= pcAdvance;
|
||||
}
|
||||
if (pcAdvance < 0) {
|
||||
break;
|
||||
}
|
||||
currentPC += pcAdvance;
|
||||
stackHeights[currentPC] = currentStackHeight;
|
||||
|
||||
thisWork++;
|
||||
}
|
||||
if (maxStackHeight != codeSections[codeSectionToValidate].maxStackHeight) {
|
||||
return String.format(
|
||||
"Calculated max stack height (%d) does not match reported stack height (%d)",
|
||||
maxStackHeight, codeSections[codeSectionToValidate].maxStackHeight);
|
||||
}
|
||||
if (unusedBytes != 0) {
|
||||
return String.format("Dead code detected in section %d", codeSectionToValidate);
|
||||
}
|
||||
|
||||
thisWork++;
|
||||
return null;
|
||||
} catch (RuntimeException re) {
|
||||
return "Internal Exception " + re.getMessage();
|
||||
}
|
||||
|
||||
if (maxStackHeight != codeSections[codeSectionToValidate].maxStackHeight) {
|
||||
return String.format(
|
||||
"Calculated max stack height (%d) does not match reported stack height (%d)",
|
||||
maxStackHeight, codeSections[codeSectionToValidate].maxStackHeight);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -816,6 +832,11 @@ public class CodeV1 implements Code {
|
||||
return codeSectionInfos[section];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCodeSectionCount() {
|
||||
return codeSectionInfos.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bytes getContainerBytes() {
|
||||
return container;
|
||||
|
||||
@@ -87,7 +87,7 @@ public class EOFLayout {
|
||||
return invalidLayout(container, version, error);
|
||||
}
|
||||
int typesLength = readUnsignedShort(inputStream);
|
||||
if (typesLength < 0) {
|
||||
if (typesLength <= 0) {
|
||||
return invalidLayout(container, version, "Invalid Types section size");
|
||||
}
|
||||
|
||||
@@ -96,9 +96,18 @@ public class EOFLayout {
|
||||
return invalidLayout(container, version, error);
|
||||
}
|
||||
int codeSectionCount = readUnsignedShort(inputStream);
|
||||
if (codeSectionCount < 0) {
|
||||
if (codeSectionCount <= 0) {
|
||||
return invalidLayout(container, version, "Invalid Code section count");
|
||||
}
|
||||
if (codeSectionCount * 4 != typesLength) {
|
||||
return invalidLayout(
|
||||
container,
|
||||
version,
|
||||
"Type section length incompatible with code section count - 0x"
|
||||
+ Integer.toHexString(codeSectionCount)
|
||||
+ " * 4 != 0x"
|
||||
+ Integer.toHexString(typesLength));
|
||||
}
|
||||
if (codeSectionCount > 1024) {
|
||||
return invalidLayout(
|
||||
container,
|
||||
|
||||
@@ -20,7 +20,7 @@ import org.hyperledger.besu.evm.gascalculator.GasCalculator;
|
||||
|
||||
public class RetFOperation extends AbstractOperation {
|
||||
|
||||
static final int OPCODE = 0xb1;
|
||||
public static final int OPCODE = 0xb1;
|
||||
static final OperationResult retfSuccess = new OperationResult(4, null);
|
||||
|
||||
public RetFOperation(final GasCalculator gasCalculator) {
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.tuweni.bytes.Bytes;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
@@ -37,12 +38,13 @@ import org.junit.jupiter.params.provider.ValueSource;
|
||||
*/
|
||||
class CodeV1Test {
|
||||
|
||||
public static final String ZERO_HEX = String.format("%02x", 0);
|
||||
public static final String ZERO_HEX = "00";
|
||||
public static final String NOOP_HEX = "5b";
|
||||
|
||||
@Test
|
||||
void validCode() {
|
||||
String codeHex =
|
||||
"0xEF0001 010010 020003 000A 0002 0008 030000 00 00000000 02010001 01000002 60016002b00001b20002 01b1 60005360106000f3";
|
||||
"0xEF0001 01000C 020003 000b 0002 0008 030000 00 00000000 02010001 01000002 60016002b00001b00002b1 01b1 60005360106000f3";
|
||||
final EOFLayout layout = EOFLayout.parseEOF(Bytes.fromHexString(codeHex.replace(" ", "")));
|
||||
|
||||
String validationError = validateCode(layout);
|
||||
@@ -59,7 +61,7 @@ class CodeV1Test {
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"00", "f3", "fd", "fe"})
|
||||
@ValueSource(strings = {"00", "3030f3", "3030fd", "fe"})
|
||||
void testValidCodeTerminator(final String code) {
|
||||
final String validationError = validateCode(Bytes.fromHexString(code), 1);
|
||||
assertThat(validationError).isNull();
|
||||
@@ -91,12 +93,12 @@ class CodeV1Test {
|
||||
"5c000000",
|
||||
"5c00010000",
|
||||
"5c00010000000000",
|
||||
"5c0100" + ZERO_HEX.repeat(256) + ZERO_HEX,
|
||||
"5c7fff" + ZERO_HEX.repeat(32767) + ZERO_HEX,
|
||||
"5c0100" + NOOP_HEX.repeat(256) + ZERO_HEX,
|
||||
"5c7fff" + NOOP_HEX.repeat(32767) + ZERO_HEX,
|
||||
"5cfffd0000",
|
||||
"005cfffc00",
|
||||
ZERO_HEX.repeat(253) + "5cff0000",
|
||||
ZERO_HEX.repeat(32765) + "5c800000")
|
||||
NOOP_HEX.repeat(253) + "5cff0000",
|
||||
NOOP_HEX.repeat(32765) + "5c800000")
|
||||
.map(Arguments::arguments);
|
||||
}
|
||||
|
||||
@@ -116,8 +118,8 @@ class CodeV1Test {
|
||||
"60015d7fff" + "5b".repeat(32767) + ZERO_HEX,
|
||||
"60015dfffd0000",
|
||||
"60015dfffb00",
|
||||
ZERO_HEX.repeat(252) + "60015dff0000",
|
||||
ZERO_HEX.repeat(32763) + "60015d800000",
|
||||
NOOP_HEX.repeat(252) + "60015dff0000",
|
||||
NOOP_HEX.repeat(32763) + "60015d800000",
|
||||
"5d000000")
|
||||
.map(Arguments::arguments);
|
||||
}
|
||||
@@ -178,7 +180,7 @@ class CodeV1Test {
|
||||
return Stream.concat(
|
||||
Stream.of("60"),
|
||||
IntStream.range(0, 31)
|
||||
.mapToObj(i -> String.format("%02x", 0x61 + i) + ZERO_HEX.repeat(i + 1)))
|
||||
.mapToObj(i -> String.format("%02x", 0x61 + i) + NOOP_HEX.repeat(i + 1)))
|
||||
.map(Arguments::arguments);
|
||||
}
|
||||
|
||||
@@ -323,22 +325,46 @@ class CodeV1Test {
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"b0", "b000", "b2", "b200"})
|
||||
@ValueSource(strings = {"b0", "b000"})
|
||||
void testCallFTruncated(final String code) {
|
||||
final String validationError = validateCode(Bytes.fromHexString(code), 1);
|
||||
assertThat(validationError).isEqualTo("Truncated CALLF");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"b2", "b200"})
|
||||
@Disabled("Out of Shahghai, will likely return in Cancun or Prague")
|
||||
void testJumpCallFTruncated(final String code) {
|
||||
final String validationError = validateCode(Bytes.fromHexString(code), 1);
|
||||
assertThat(validationError).isEqualTo("Truncated CALLF/JUMPF");
|
||||
assertThat(validationError).isEqualTo("Truncated CALLF");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"b00004", "b003ff", "b0ffff", "b20004", "b203ff", "b2ffff"})
|
||||
void testJumpCallFWrongSection(final String code) {
|
||||
@ValueSource(strings = {"b00004", "b003ff", "b0ffff"})
|
||||
void testCallFWrongSection(final String code) {
|
||||
final String validationError = validateCode(Bytes.fromHexString(code), 3);
|
||||
assertThat(validationError).startsWith("CALLF/JUMPF to non-existent section -");
|
||||
assertThat(validationError).startsWith("CALLF to non-existent section -");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"b0000100", "b0000200", "b0000000", "b20001", "b20002", "b20000"})
|
||||
void testJumpCallFValid(final String code) {
|
||||
@ValueSource(strings = {"b20004", "b203ff", "b2ffff"})
|
||||
@Disabled("Out of Shahghai, will likely return in Cancun or Prague")
|
||||
void testJumpFWrongSection(final String code) {
|
||||
final String validationError = validateCode(Bytes.fromHexString(code), 3);
|
||||
assertThat(validationError).startsWith("CALLF to non-existent section -");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"b0000100", "b0000200", "b0000000"})
|
||||
void testCallFValid(final String code) {
|
||||
final String validationError = validateCode(Bytes.fromHexString(code), 3);
|
||||
assertThat(validationError).isNull();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"b20001", "b20002", "b20000"})
|
||||
@Disabled("Out of Shahghai, will likely return in Cancun or Prague")
|
||||
void testJumpFValid(final String code) {
|
||||
final String validationError = validateCode(Bytes.fromHexString(code), 3);
|
||||
assertThat(validationError).isNull();
|
||||
}
|
||||
@@ -386,7 +412,9 @@ class CodeV1Test {
|
||||
"stackRJumpI",
|
||||
"stackCallF",
|
||||
"stackRetF",
|
||||
"stackUnreachable"
|
||||
"stackUnreachable",
|
||||
"stackHeight",
|
||||
"invalidInstructions",
|
||||
})
|
||||
void validateStackAnalysis(
|
||||
final String ignoredName,
|
||||
@@ -427,7 +455,7 @@ class CodeV1Test {
|
||||
"Stack empty with input",
|
||||
null,
|
||||
1,
|
||||
List.of(List.of("5000", 0, 0, 0), List.of("50 00", 1, 0, 1))),
|
||||
List.of(List.of("00", 0, 0, 0), List.of("50 00", 1, 0, 1))),
|
||||
// this depends on requiring stacks to be "clean" returns
|
||||
Arguments.of(
|
||||
"Stack not empty at output",
|
||||
@@ -454,35 +482,42 @@ class CodeV1Test {
|
||||
static Stream<Arguments> stackRJumpForward() {
|
||||
return Stream.of(
|
||||
Arguments.of("RJUMP 0", null, 0, List.of(List.of("5C0000 00", 0, 0, 0))),
|
||||
Arguments.of("RJUMP 1 w/ dead code", null, 0, List.of(List.of("5C0001 43 00", 0, 0, 0))),
|
||||
Arguments.of("RJUMP 2 w/ dead code", null, 0, List.of(List.of("5C0002 43 50 00", 0, 0, 0))),
|
||||
Arguments.of(
|
||||
"RJUMP 1 w/ dead code",
|
||||
"Dead code detected in section 0",
|
||||
0,
|
||||
List.of(List.of("5C0001 43 00", 0, 0, 0))),
|
||||
Arguments.of(
|
||||
"RJUMP 2 w/ dead code",
|
||||
"Dead code detected in section 0",
|
||||
0,
|
||||
List.of(List.of("5C0002 43 50 00", 0, 0, 0))),
|
||||
Arguments.of(
|
||||
"RJUMP 3 and -10",
|
||||
null,
|
||||
0,
|
||||
List.of(List.of("5C0003 01 50 00 6001 6001 5Cfff6 00", 0, 0, 2))));
|
||||
List.of(List.of("5C0003 01 50 00 6001 6001 5Cfff6", 0, 0, 2))));
|
||||
}
|
||||
|
||||
static Stream<Arguments> stackRJumpBackward() {
|
||||
return Stream.of(
|
||||
Arguments.of("RJUMP -3", null, 0, List.of(List.of("5Cfffd 00", 0, 0, 0))),
|
||||
Arguments.of("RJUMP -4", null, 0, List.of(List.of("5B 5Cfffc 00", 0, 0, 0))),
|
||||
Arguments.of("RJUMP -3", null, 0, List.of(List.of("5Cfffd", 0, 0, 0))),
|
||||
Arguments.of("RJUMP -4", null, 0, List.of(List.of("5B 5Cfffc", 0, 0, 0))),
|
||||
Arguments.of(
|
||||
"RJUMP -4 unmatched stack",
|
||||
"Jump into code stack height (0) does not match previous value (1)",
|
||||
0,
|
||||
List.of(List.of("43 5Cfffc 50 00", 0, 0, 0))),
|
||||
List.of(List.of("43 5Cfffc", 0, 0, 0))),
|
||||
Arguments.of(
|
||||
"RJUMP -4 unmatched stack",
|
||||
"Jump into code stack height (1) does not match previous value (0)",
|
||||
0,
|
||||
List.of(List.of("43 50 5Cfffc 00", 0, 0, 0))),
|
||||
Arguments.of("RJUMP -3 matched stack", null, 0, List.of(List.of("43 50 5Cfffd", 0, 0, 1))),
|
||||
Arguments.of(
|
||||
"RJUMP -3 matched stack", null, 0, List.of(List.of("43 50 5Cfffd 00", 0, 0, 1))),
|
||||
"RJUMP -4 matched stack", null, 0, List.of(List.of("43 50 5B 5Cfffc", 0, 0, 1))),
|
||||
Arguments.of(
|
||||
"RJUMP -4 matched stack", null, 0, List.of(List.of("43 50 5B 5Cfffc 00", 0, 0, 1))),
|
||||
Arguments.of(
|
||||
"RJUMP -5 matched stack", null, 0, List.of(List.of("43 50 43 5Cfffb 50 00", 0, 0, 1))),
|
||||
"RJUMP -5 matched stack", null, 0, List.of(List.of("43 50 43 5Cfffb", 0, 0, 1))),
|
||||
Arguments.of(
|
||||
"RJUMP -4 unmatched stack",
|
||||
"Jump into code stack height (0) does not match previous value (1)",
|
||||
@@ -559,98 +594,101 @@ class CodeV1Test {
|
||||
"0 input 0 output",
|
||||
null,
|
||||
0,
|
||||
List.of(List.of("B00001 00", 0, 0, 0), List.of("", 0, 0, 0))),
|
||||
List.of(List.of("B00001 00", 0, 0, 0), List.of("b1", 0, 0, 0))),
|
||||
Arguments.of(
|
||||
"0 inputs, 0 output 3 sections",
|
||||
null,
|
||||
0,
|
||||
List.of(List.of("B00002 00", 0, 0, 0), List.of("", 1, 1, 1), List.of("", 0, 0, 0))),
|
||||
List.of(List.of("B00002 00", 0, 0, 0), List.of("b1", 1, 1, 1), List.of("b1", 0, 0, 0))),
|
||||
Arguments.of(
|
||||
"more than 0 inputs",
|
||||
null,
|
||||
0,
|
||||
List.of(List.of("30 B00001 00", 0, 0, 1), List.of("", 1, 0, 1))),
|
||||
List.of(List.of("30 B00001 00", 0, 0, 1), List.of("00", 1, 0, 1))),
|
||||
Arguments.of(
|
||||
"forwarding an argument",
|
||||
null,
|
||||
1,
|
||||
List.of(List.of("", 0, 0, 0), List.of("B00002 00", 1, 0, 1), List.of("", 1, 0, 1))),
|
||||
List.of(List.of("00", 0, 0, 0), List.of("B00002 00", 1, 0, 1), List.of("00", 1, 0, 1))),
|
||||
Arguments.of(
|
||||
"more than 1 inputs",
|
||||
null,
|
||||
0,
|
||||
List.of(List.of("30 80 B00001 00", 0, 0, 2), List.of("", 2, 0, 2))),
|
||||
List.of(List.of("30 80 B00001 00", 0, 0, 2), List.of("00", 2, 0, 2))),
|
||||
Arguments.of(
|
||||
"more than 0 outputs",
|
||||
null,
|
||||
0,
|
||||
List.of(List.of("B00001 50 00", 0, 0, 1), List.of("", 0, 1, 1))),
|
||||
List.of(List.of("B00001 50 00", 0, 0, 1), List.of("3000", 0, 1, 1))),
|
||||
Arguments.of(
|
||||
"more than 0 outputs 3 sections",
|
||||
null,
|
||||
0,
|
||||
List.of(List.of("B00002 50 00", 0, 0, 1), List.of("", 0, 0, 0), List.of("", 0, 1, 2))),
|
||||
List.of(
|
||||
List.of("B00002 50 00", 0, 0, 1),
|
||||
List.of("00", 0, 0, 0),
|
||||
List.of("30305000", 0, 1, 2))),
|
||||
Arguments.of(
|
||||
"more than 1 outputs",
|
||||
null,
|
||||
0,
|
||||
List.of(List.of("B00001 50 50 00", 0, 0, 2), List.of("", 0, 2, 2))),
|
||||
List.of(List.of("B00001 50 50 00", 0, 0, 2), List.of("303000", 0, 2, 2))),
|
||||
Arguments.of(
|
||||
"more than 0 inputs, more than 0 outputs",
|
||||
null,
|
||||
0,
|
||||
List.of(
|
||||
List.of("30 30 B00001 50 50 50 00", 0, 0, 3),
|
||||
List.of("30 30 B00001 50 50 50 00", 2, 3, 3))),
|
||||
List.of("30 30 B00001 50 50 00", 2, 3, 5))),
|
||||
Arguments.of("recursion", null, 0, List.of(List.of("B00000 00", 0, 0, 0))),
|
||||
Arguments.of(
|
||||
"recursion 2 inputs",
|
||||
null,
|
||||
1,
|
||||
List.of(List.of("", 0, 0, 0), List.of("B00000 00", 2, 0, 2))),
|
||||
List.of(List.of("00", 0, 0, 0), List.of("B00000 00", 2, 0, 2))),
|
||||
Arguments.of(
|
||||
"recursion 2 inputs 2 outputs",
|
||||
null,
|
||||
1,
|
||||
List.of(List.of("", 0, 0, 0), List.of("B00000 50 50 00", 2, 2, 2))),
|
||||
List.of(List.of("00", 0, 0, 0), List.of("B00000 50 50 00", 2, 2, 2))),
|
||||
Arguments.of(
|
||||
"recursion 2 inputs 1 output",
|
||||
null,
|
||||
1,
|
||||
List.of(List.of("", 0, 0, 0), List.of("30 30 B00001 50 50 50 00", 2, 1, 4))),
|
||||
List.of(List.of("00", 0, 0, 0), List.of("30 30 B00001 50 50 50 00", 2, 1, 4))),
|
||||
Arguments.of(
|
||||
"multiple CALLFs with different types",
|
||||
null,
|
||||
1,
|
||||
List.of(
|
||||
List.of("", 0, 0, 0),
|
||||
List.of("00", 0, 0, 0),
|
||||
List.of("44 B00002 80 80 B00003 44 80 B00004 50 50 00", 0, 0, 3),
|
||||
List.of("", 1, 1, 3),
|
||||
List.of("", 3, 0, 3),
|
||||
List.of("", 2, 2, 2))),
|
||||
List.of("3030505000", 1, 1, 3),
|
||||
List.of("50505000", 3, 0, 3),
|
||||
List.of("00", 2, 2, 2))),
|
||||
Arguments.of(
|
||||
"underflow",
|
||||
"Operation 0xB0 requires stack of 1 but only has 0 items",
|
||||
0,
|
||||
List.of(List.of("B00001 00", 0, 0, 0), List.of("", 1, 0, 0))),
|
||||
List.of(List.of("B00001 00", 0, 0, 0), List.of("00", 1, 0, 0))),
|
||||
Arguments.of(
|
||||
"underflow 2",
|
||||
"Operation 0xB0 requires stack of 2 but only has 1 items",
|
||||
0,
|
||||
List.of(List.of("30 B00001 00", 0, 0, 0), List.of("", 2, 0, 2))),
|
||||
List.of(List.of("30 B00001 00", 0, 0, 0), List.of("00", 2, 0, 2))),
|
||||
Arguments.of(
|
||||
"underflow 3",
|
||||
"Operation 0xB0 requires stack of 1 but only has 0 items",
|
||||
1,
|
||||
List.of(List.of("", 0, 0, 0), List.of("50 B00001 00", 1, 0, 1))),
|
||||
List.of(List.of("00", 0, 0, 0), List.of("50 B00001 00", 1, 0, 1))),
|
||||
Arguments.of(
|
||||
"underflow 4",
|
||||
"Operation 0xB0 requires stack of 3 but only has 2 items",
|
||||
0,
|
||||
List.of(
|
||||
List.of("44 B00001 80 B00002 00", 0, 0, 0),
|
||||
List.of("", 1, 1, 1),
|
||||
List.of("", 3, 0, 3))));
|
||||
List.of("00", 1, 1, 1),
|
||||
List.of("00", 3, 0, 3))));
|
||||
}
|
||||
|
||||
static Stream<Arguments> stackRetF() {
|
||||
@@ -659,95 +697,122 @@ class CodeV1Test {
|
||||
"0 outputs at section 0",
|
||||
null,
|
||||
0,
|
||||
List.of(List.of("B1", 0, 0, 0), List.of("", 0, 0, 0))),
|
||||
List.of(List.of("B1", 0, 0, 0), List.of("00", 0, 0, 0))),
|
||||
Arguments.of(
|
||||
"0 outputs at section 1",
|
||||
null,
|
||||
1,
|
||||
List.of(List.of("", 0, 0, 0), List.of("B1", 0, 0, 0))),
|
||||
List.of(List.of("00", 0, 0, 0), List.of("B1", 0, 0, 0))),
|
||||
Arguments.of(
|
||||
"0 outputs at section 2",
|
||||
null,
|
||||
2,
|
||||
List.of(List.of("", 0, 0, 0), List.of("", 1, 1, 1), List.of("B1", 0, 0, 0))),
|
||||
List.of(List.of("00", 0, 0, 0), List.of("00", 1, 1, 1), List.of("B1", 0, 0, 0))),
|
||||
Arguments.of(
|
||||
"more than 0 outputs section 0",
|
||||
null,
|
||||
0,
|
||||
List.of(List.of("44 B1", 0, 0, 1), List.of("", 0, 1, 1))),
|
||||
List.of(List.of("44 50 B1", 0, 0, 1), List.of("4400", 0, 1, 1))),
|
||||
Arguments.of(
|
||||
"more than 0 outputs section 0",
|
||||
null,
|
||||
1,
|
||||
List.of(List.of("", 0, 0, 0), List.of("44 B1", 0, 1, 1))),
|
||||
List.of(List.of("00", 0, 0, 0), List.of("44 B1", 0, 1, 1))),
|
||||
Arguments.of(
|
||||
"more than 1 outputs section 1",
|
||||
null,
|
||||
1,
|
||||
List.of(List.of("", 0, 0, 0), List.of("44 80 B1", 0, 2, 2))),
|
||||
List.of(List.of("00", 0, 0, 0), List.of("44 80 B1", 0, 2, 2))),
|
||||
Arguments.of(
|
||||
"Forwarding return values",
|
||||
null,
|
||||
1,
|
||||
List.of(List.of("", 0, 0, 0), List.of("B1", 1, 1, 1))),
|
||||
List.of(List.of("00", 0, 0, 0), List.of("B1", 1, 1, 1))),
|
||||
Arguments.of(
|
||||
"Forwarding of return values 2",
|
||||
null,
|
||||
1,
|
||||
List.of(List.of("", 0, 0, 0), List.of("B00002 B1", 0, 1, 1), List.of("", 0, 1, 1))),
|
||||
List.of(
|
||||
List.of("00", 0, 0, 0), List.of("B00002 B1", 0, 1, 1), List.of("3000", 0, 1, 1))),
|
||||
Arguments.of(
|
||||
"Multiple RETFs",
|
||||
null,
|
||||
1,
|
||||
List.of(List.of("", 0, 0, 0), List.of("5D0003 44 80 B1 30 80 B1", 1, 2, 2))),
|
||||
List.of(List.of("00", 0, 0, 0), List.of("5D0003 44 80 B1 30 80 B1", 1, 2, 2))),
|
||||
Arguments.of(
|
||||
"underflow 1",
|
||||
"Calculated max stack height (0) does not match reported stack height (1)",
|
||||
"Section return (RETF) calculated height 0x0 does not match configured height 0x1",
|
||||
1,
|
||||
List.of(List.of("", 0, 0, 0), List.of("B1", 0, 1, 1))),
|
||||
List.of(List.of("00", 0, 0, 0), List.of("B1", 0, 1, 0))),
|
||||
Arguments.of(
|
||||
"underflow 2",
|
||||
"Calculated max stack height (1) does not match reported stack height (2)",
|
||||
"Section return (RETF) calculated height 0x1 does not match configured height 0x2",
|
||||
1,
|
||||
List.of(List.of("", 0, 0, 0), List.of("44 B1", 0, 2, 2))),
|
||||
List.of(List.of("00", 0, 0, 0), List.of("44 B1", 0, 2, 1))),
|
||||
Arguments.of(
|
||||
"underflow 3",
|
||||
"Calculated max stack height (2) does not match reported stack height (0)",
|
||||
"Section return (RETF) calculated height 0x1 does not match configured height 0x2",
|
||||
1,
|
||||
List.of(List.of("", 0, 0, 0), List.of("5D0003 44 80 B1 30 B1", 1, 2, 0))));
|
||||
List.of(List.of("00", 0, 0, 0), List.of("5D0003 44 80 B1 30 B1", 1, 2, 2))));
|
||||
}
|
||||
|
||||
static Stream<Arguments> stackUnreachable() {
|
||||
return Stream.of(
|
||||
Arguments.of(
|
||||
"Max stack not changed by unreachable code",
|
||||
null,
|
||||
"Dead code detected in section 0",
|
||||
0,
|
||||
List.of(List.of("30 50 00 30 30 30 50 50 50 00", 0, 0, 1))),
|
||||
Arguments.of(
|
||||
"Max stack not changed by unreachable code RETf",
|
||||
null,
|
||||
"Dead code detected in section 0",
|
||||
0,
|
||||
List.of(List.of("30 50 B1 30 30 30 50 50 50 00", 0, 0, 1))),
|
||||
Arguments.of(
|
||||
"Max stack not changed by unreachable code RJUMP",
|
||||
null,
|
||||
"Dead code detected in section 0",
|
||||
0,
|
||||
List.of(List.of("30 50 5C0006 30 30 30 50 50 50 00", 0, 0, 1))),
|
||||
Arguments.of(
|
||||
"Stack underflow in unreachable code",
|
||||
null,
|
||||
"Dead code detected in section 0",
|
||||
0,
|
||||
List.of(List.of("30 50 00 50 00", 0, 0, 1))),
|
||||
Arguments.of(
|
||||
"Stack underflow in unreachable code RETF",
|
||||
null,
|
||||
"Dead code detected in section 0",
|
||||
0,
|
||||
List.of(List.of("30 50 B1 50 00", 0, 0, 1))),
|
||||
Arguments.of(
|
||||
"Stack underflow in unreachable code RJUMP",
|
||||
null,
|
||||
"Dead code detected in section 0",
|
||||
0,
|
||||
List.of(List.of("30 50 5C0001 50 00", 0, 0, 1))));
|
||||
}
|
||||
|
||||
static Stream<Arguments> stackHeight() {
|
||||
return Stream.of(
|
||||
Arguments.of(
|
||||
"Stack height mismatch backwards",
|
||||
"Jump into code stack height (0) does not match previous value (1)",
|
||||
0,
|
||||
List.of(List.of("30 5Cfffc00", 0, 0, 1))),
|
||||
Arguments.of(
|
||||
"Stack height mismatch forwards",
|
||||
"Jump into code stack height (3) does not match previous value (0)",
|
||||
0,
|
||||
List.of(List.of("305D0003303030303000", 0, 0, 2))));
|
||||
}
|
||||
|
||||
static Stream<Arguments> invalidInstructions() {
|
||||
return IntStream.range(0, 256)
|
||||
.filter(opcode -> CodeV1.OPCODE_ATTRIBUTES[opcode] == CodeV1.INVALID)
|
||||
.mapToObj(
|
||||
opcode ->
|
||||
Arguments.of(
|
||||
String.format("Invalid opcode %02x", opcode),
|
||||
String.format("Invalid Instruction 0x%02x", opcode),
|
||||
0,
|
||||
List.of(List.of(String.format("0x%02x", opcode), 0, 0, 0))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,15 +82,15 @@ public class EOFLayoutTest {
|
||||
1
|
||||
},
|
||||
{"EF0001 010004 0200010001 030000", "No Terminator", "Improper section headers", 1},
|
||||
{"EF0001 010008 0200010002 030000 00", "No type section", "Incomplete type section", 1},
|
||||
{"EF0001 010004 0200010002 030000 00", "No type section", "Incomplete type section", 1},
|
||||
{
|
||||
"EF0001 010008 0200010002 030001 030001 00 DA DA",
|
||||
"EF0001 010004 0200010002 030001 030001 00 DA DA",
|
||||
"Duplicate data sections",
|
||||
"Expected kind 0 but read kind 3",
|
||||
1
|
||||
},
|
||||
{
|
||||
"EF0001 010008 0200010002 030000 00 00",
|
||||
"EF0001 010004 0200010002 030000 00 00",
|
||||
"Incomplete type section",
|
||||
"Incomplete type section",
|
||||
1
|
||||
@@ -102,25 +102,31 @@ public class EOFLayoutTest {
|
||||
1
|
||||
},
|
||||
{
|
||||
"EF0001 010008 02000200020002 030000 00 0100000000000000",
|
||||
"EF0001 010008 0200010001 030000 00 00000000 FE ",
|
||||
"Incorrect type section size",
|
||||
"Type section length incompatible with code section count - 0x1 * 4 != 0x8",
|
||||
1
|
||||
},
|
||||
{
|
||||
"EF0001 010008 02000200010001 030000 00 0100000000000000 FE FE",
|
||||
"Incorrect section zero type input",
|
||||
"Code section does not have zero inputs and outputs",
|
||||
1
|
||||
},
|
||||
{
|
||||
"EF0001 010008 02000200020002 030000 00 0001000000000000",
|
||||
"EF0001 010008 02000200010001 030000 00 0001000000000000 FE FE",
|
||||
"Incorrect section zero type output",
|
||||
"Code section does not have zero inputs and outputs",
|
||||
1
|
||||
},
|
||||
{
|
||||
"EF0001 010008 0200010002 030000 00 00000000 ",
|
||||
"EF0001 010004 0200010002 030000 00 00000000 ",
|
||||
"Incomplete code section",
|
||||
"Incomplete code section 0",
|
||||
1
|
||||
},
|
||||
{
|
||||
"EF0001 010008 0200010002 030000 00 00000000 FE",
|
||||
"EF0001 010004 0200010002 030000 00 00000000 FE",
|
||||
"Incomplete code section",
|
||||
"Incomplete code section 0",
|
||||
1
|
||||
@@ -138,13 +144,13 @@ public class EOFLayoutTest {
|
||||
1
|
||||
},
|
||||
{
|
||||
"EF0001 010008 0200010001 030003 00 00000000 FE DEADBEEF",
|
||||
"EF0001 010004 0200010001 030003 00 00000000 FE DEADBEEF",
|
||||
"Incomplete data section",
|
||||
"Dangling data after end of all sections",
|
||||
1
|
||||
},
|
||||
{
|
||||
"EF0001 010008 0200010001 030003 00 00000000 FE BEEF",
|
||||
"EF0001 010004 0200010001 030003 00 00000000 FE BEEF",
|
||||
"Incomplete data section",
|
||||
"Incomplete data section",
|
||||
1
|
||||
@@ -187,7 +193,7 @@ public class EOFLayoutTest {
|
||||
},
|
||||
{"EF0001 00", "all sections missing", "Expected kind 1 but read kind 0", 1},
|
||||
{
|
||||
"EF0001 010004 020401"
|
||||
"EF0001 011004 020401"
|
||||
+ " 0001".repeat(1025)
|
||||
+ " 030000 00"
|
||||
+ " 00000000".repeat(1025)
|
||||
@@ -196,6 +202,15 @@ public class EOFLayoutTest {
|
||||
"Too many code sections - 0x401",
|
||||
1
|
||||
},
|
||||
{"ef000101000002000003000000", "All kinds zero size", "Invalid Types section size", 1},
|
||||
{"ef0001010000020001000103000000ef", "Zero type size ", "Invalid Types section size", 1},
|
||||
{
|
||||
"ef0001010004020001000003000000",
|
||||
"Zero code section length",
|
||||
"Invalid Code section size for section 0",
|
||||
1
|
||||
},
|
||||
{"ef000101000402000003000000", "Zero code sections", "Invalid Code section count", 1},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -227,13 +242,13 @@ public class EOFLayoutTest {
|
||||
1
|
||||
},
|
||||
{
|
||||
"EF0001 010010 0200040001000200020002 030000 00 00000000 01000000 00010000 02030000 FE 5000 3000 8000",
|
||||
"EF0001 010010 0200040001000200020002 030000 00 00000000 01000001 00010001 02030003 FE 5000 3000 8000",
|
||||
"non-void input and output types",
|
||||
null,
|
||||
1
|
||||
},
|
||||
{
|
||||
"EF0001 010004 020400"
|
||||
"EF0001 011000 020400"
|
||||
+ " 0001".repeat(1024)
|
||||
+ " 030000 00"
|
||||
+ " 00000000".repeat(1024)
|
||||
@@ -279,7 +294,7 @@ public class EOFLayoutTest {
|
||||
1
|
||||
},
|
||||
{
|
||||
"EF0001 010010 0200040001000200020002 030000 00 00000000 01000000 00010000 02030000 FE 5000 3000 8000",
|
||||
"EF0001 010010 0200040001000200020002 030000 00 00000000 01000001 00010001 02030003 FE 5000 3000 8000",
|
||||
"non-void input and output types",
|
||||
null,
|
||||
1
|
||||
|
||||
Reference in New Issue
Block a user