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>
pull/4898/head
Danno Ferrin 2 years ago committed by GitHub
parent 80ea06e4f8
commit 25f197eeb9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 59
      ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/CodeValidateSubCommand.java
  2. 37
      ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/CodeValidationSubCommandTest.java
  3. 7
      evm/src/main/java/org/hyperledger/besu/evm/Code.java
  4. 1
      evm/src/main/java/org/hyperledger/besu/evm/EVM.java
  5. 2
      evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java
  6. 5
      evm/src/main/java/org/hyperledger/besu/evm/code/CodeInvalid.java
  7. 5
      evm/src/main/java/org/hyperledger/besu/evm/code/CodeV0.java
  8. 83
      evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1.java
  9. 13
      evm/src/main/java/org/hyperledger/besu/evm/code/EOFLayout.java
  10. 2
      evm/src/main/java/org/hyperledger/besu/evm/operation/RetFOperation.java
  11. 207
      evm/src/test/java/org/hyperledger/besu/evm/code/CodeV1Test.java
  12. 41
      evm/src/test/java/org/hyperledger/besu/evm/code/EOFLayoutTest.java

@ -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 {
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.println(considerCode(code));
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,11 +677,12 @@ 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) {
try {
byte[] code = codeSections[codeSectionToValidate].code.toArrayUnsafe();
int codeLength = code.length;
int[] stackHeights = new int[codeLength];
@ -695,6 +696,7 @@ public class CodeV1 implements Code {
int maxStackHeight = initialStackHeight;
stackHeights[0] = initialStackHeight;
workList[0][1] = initialStackHeight;
int unusedBytes = codeLength;
while (thisWork < maxWork) {
int currentPC = workList[thisWork][0];
@ -709,24 +711,18 @@ public class CodeV1 implements Code {
thisWork++;
continue;
}
} else {
stackHeights[currentPC] = currentStackHeight;
}
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);
}
byte[] stackInfo = opcodeStackValidation[thisOp];
byte[] stackInfo = OPCODE_STACK_VALIDATION[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) {
if (thisOp == CallFOperation.OPCODE) {
int section = readBigEndianU16(currentPC + 1, code);
stackInputs = codeSections[section].getInputs();
stackOutputs = codeSections[section].getOutputs();
@ -754,30 +750,50 @@ public class CodeV1 implements Code {
workList[maxWork] = new int[] {currentPC + rvalue + 3, currentStackHeight};
maxWork++;
} else if (thisOp == RelativeJumpVectorOperation.OPCODE) {
int tableEnd = code[currentPC + 1] * 2 + currentPC + 2;
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 + 1, code);
workList[maxWork] = new int[] {currentPC + rvalue + 3, currentStackHeight};
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);
}
}
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;
}
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);
}
return null;
} catch (RuntimeException re) {
return "Internal Exception " + re.getMessage();
}
}
@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"})
void testCallFWrongSection(final String code) {
final String validationError = validateCode(Bytes.fromHexString(code), 3);
assertThat(validationError).startsWith("CALLF to non-existent section -");
}
@ParameterizedTest
@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 = {"b00004", "b003ff", "b0ffff", "b20004", "b203ff", "b2ffff"})
void testJumpCallFWrongSection(final String code) {
@ValueSource(strings = {"b0000100", "b0000200", "b0000000"})
void testCallFValid(final String code) {
final String validationError = validateCode(Bytes.fromHexString(code), 3);
assertThat(validationError).startsWith("CALLF/JUMPF to non-existent section -");
assertThat(validationError).isNull();
}
@ParameterizedTest
@ValueSource(strings = {"b0000100", "b0000200", "b0000000", "b20001", "b20002", "b20000"})
void testJumpCallFValid(final String code) {
@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))),
Arguments.of(
"RJUMP -4 matched stack", null, 0, List.of(List.of("43 50 5B 5Cfffc 00", 0, 0, 1))),
"RJUMP -4 matched stack", null, 0, List.of(List.of("43 50 5B 5Cfffc", 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

Loading…
Cancel
Save