Skip to content

Commit

Permalink
Fuzzer & fixes (#14)
Browse files Browse the repository at this point in the history
* Fuzzer part 1

* Fuzz only sensible programs.

* Fix issues found from fuzzer.

* Additional fixes after fuzzing.

* Fix mul upper.

* Display arguments going into instructions.

* Generate JSON test cases from fuzzer.

* Fix memory allocation.

* Fuzzer fixes.

* Panic on OOM

* OOM is actually a page fault?

* Add gas cost for memory.

* Read JSON files from a directory.

* Cleanup

* Fix lint.

* Fix examples.
  • Loading branch information
tomusdrw authored Dec 29, 2024
1 parent e50cadf commit cbdcb4c
Show file tree
Hide file tree
Showing 23 changed files with 2,726 additions and 143 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules
tests/*
25 changes: 20 additions & 5 deletions assembly/api-generic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { INSTRUCTIONS, MISSING_INSTRUCTION } from "./instructions";
import { Interpreter, Status } from "./interpreter";
import { Memory, MemoryBuilder } from "./memory";
import { Access, PAGE_SIZE } from "./memory-page";
import { Program, decodeArguments, decodeProgram, liftBytes } from "./program";
import { Program, decodeArguments, decodeProgram, liftBytes, resolveArguments } from "./program";
import { NO_OF_REGISTERS, Registers } from "./registers";

export class InitialPage {
Expand Down Expand Up @@ -62,7 +62,7 @@ export function getAssembly(p: Program): string {
}

const args = decodeArguments(iData.kind, p.code.subarray(i + 1, end));
const argsArray = [args.a, args.b, args.c, args.d];
const argsArray = args === null ? [0, 0, 0, 0] : [args.a, args.b, args.c, args.d];
const relevantArgs = RELEVANT_ARGS[iData.kind];
for (let i = 0; i < relevantArgs; i++) {
v += ` ${argsArray[i]}, `;
Expand Down Expand Up @@ -96,9 +96,19 @@ export function runVm(input: VmInput, logs: boolean = false): VmOutput {
if (logs) console.log(`PC = ${int.pc}`);
if (logs) console.log(`STATUS = ${int.status}`);
if (logs) console.log(`REGISTERS = ${registers.join(", ")}`);
if (logs && int.pc < u32(int.program.code.length)) {
const name = changetype<string>(INSTRUCTIONS[int.program.code[int.pc]].namePtr);
console.log(`INSTRUCTION = ${name} (${int.program.code[int.pc]})`);
if (logs) {
const instruction = int.pc < u32(int.program.code.length) ? int.program.code[int.pc] : 0;
const iData = instruction >= <u8>INSTRUCTIONS.length ? MISSING_INSTRUCTION : INSTRUCTIONS[instruction];
const name = changetype<string>(iData.namePtr);
console.log(`INSTRUCTION = ${name} (${instruction})`);
const args = resolveArguments(iData.kind, int.program.code.subarray(int.pc + 1), int.registers);
if (args !== null) {
console.log(`ARGUMENTS:
${args.a} (${args.decoded.a}) = 0x${u64(args.a).toString(16)},
${args.b} (${args.decoded.b}) = 0x${u64(args.b).toString(16)},
${args.c} (${args.decoded.c}) = 0x${u64(args.c).toString(16)},
${args.d} (${args.decoded.d}) = 0x${u64(args.d).toString(16)}`);
}
}

isOk = int.nextStep();
Expand All @@ -120,6 +130,11 @@ export function getOutputChunks(memory: Memory): InitialChunk[] {
const pageIdx = pages[i];
const page = memory.pages.get(pageIdx);

// skip empty pages
if (page.raw.page === null) {
continue;
}

for (let n = 0; n < page.raw.data.length; n++) {
const v = page.raw.data[n];
if (v !== 0) {
Expand Down
29 changes: 18 additions & 11 deletions assembly/arguments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export enum Arguments {
/** How many numbers in `Args` is relevant for given `Arguments`. */
export const RELEVANT_ARGS = [<i32>0, 1, 2, 1, 2, 3, 3, 3, 2, 3, 3, 4, 3];
/** How many bytes is required by given `Arguments`. */
export const REQUIRED_BYTES = [<i32>0, 0, 0, 0, 1, 9, 1, 1, 1, 1, 1, 1, 2];
export const REQUIRED_BYTES = [<i32>0, 0, 1, 0, 1, 9, 1, 1, 1, 1, 1, 2, 2];

// @unmanaged
export class Args {
Expand All @@ -39,11 +39,8 @@ function asArgs(a: u32, b: u32, c: u32, d: u32): Args {
type ArgsDecoder = (data: Uint8Array) => Args;

function twoImm(data: Uint8Array): Args {
if (data.length === 0) {
return asArgs(0, 0, 0, 0);
}
const n = nibbles(data[0]);
const split = n.low + 1;
const split = <i32>Math.min(4, n.low) + 1;
const first = decodeI32(data.subarray(1, split));
const second = decodeI32(data.subarray(split));
return asArgs(first, second, 0, 0);
Expand Down Expand Up @@ -77,16 +74,16 @@ export const DECODERS: ArgsDecoder[] = [
},
//DECODERS[Arguments.OneRegTwoImm] =
(data: Uint8Array) => {
const first = nibbles(data[0]);
const split = first.hig + 1;
const n = nibbles(data[0]);
const split = <i32>Math.min(4, n.hig) + 1;
const immA = decodeI32(data.subarray(1, split));
const immB = decodeI32(data.subarray(split));
return asArgs(first.low, immA, immB, 0);
return asArgs(n.low, immA, immB, 0);
},
// DECODERS[Arguments.OneRegOneImmOneOff] =
(data: Uint8Array) => {
const n = nibbles(data[0]);
const split = n.hig + 1;
const split = <i32>Math.min(4, n.hig) + 1;
const immA = decodeI32(data.subarray(1, split));
const offs = decodeI32(data.subarray(split));
return asArgs(n.low, immA, offs, 0);
Expand Down Expand Up @@ -127,7 +124,7 @@ class Nibbles {
}

// @inline
function nibbles(byte: u8): Nibbles {
export function nibbles(byte: u8): Nibbles {
const low = byte & 0xf;
const hig = byte >> 4;
const n = new Nibbles();
Expand All @@ -138,7 +135,7 @@ function nibbles(byte: u8): Nibbles {

//@inline
function decodeI32(data: Uint8Array): u32 {
const len = <u32>data.length;
const len = <u32>Math.min(4, data.length);
let num = 0;
for (let i: u32 = 0; i < len; i++) {
num |= u32(data[i]) << (i * 8);
Expand All @@ -152,6 +149,16 @@ function decodeI32(data: Uint8Array): u32 {
return num;
}

export function encodeI32(input: i32): u8[] {
const data: u8[] = [];
let num = u32(input);
while (num > 0) {
data.push(u8(num));
num >>= 8;
}
return data;
}

function decodeU32(data: Uint8Array): u32 {
let num = u32(data[0]);
num |= u32(data[1]) << 8;
Expand Down
46 changes: 44 additions & 2 deletions assembly/codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class Decoder {

varU32(): u32 {
this.ensureBytes(1);
const v = readVarU32(this.source.subarray(this.offset));
const v = decodeVarU32(this.source.subarray(this.offset));
this.offset += v.offset;
return v.value;
}
Expand Down Expand Up @@ -90,7 +90,7 @@ export class ValOffset<T> {
}

/** Read variable-length u32 and return number of bytes read. */
export function readVarU32(data: Uint8Array): ValOffset<u32> {
export function decodeVarU32(data: Uint8Array): ValOffset<u32> {
const length = i32(variableLength(data[0]));
const first = u32(data[0]);
if (length === 0) {
Expand All @@ -110,3 +110,45 @@ export function readVarU32(data: Uint8Array): ValOffset<u32> {

return new ValOffset(number, 1 + length);
}

export function encodeVarU32(v: u64): Uint8Array {
if (v === 0) {
return new Uint8Array(1);
}

// handle the biggest case
let maxEncoded = u64(2 ** (7 * 8));
if (v >= maxEncoded) {
const dest = new Uint8Array(9);
dest[0] = 0xff;
const dataView = new DataView(dest.buffer);
dataView.setUint64(1, v, true);
return dest;
}

// let's look for the correct range
let minEncoded = maxEncoded >> 7;
for (let l = 7; l >= 0; l -= 1) {
if (v >= minEncoded) {
const dest = new Uint8Array(l + 1);

// encode the first byte
const maxVal = 2 ** (8 * l);
const byte = 2 ** 8 - 2 ** (8 - l) + v / maxVal;
dest[0] = u8(byte);

// now encode the rest of bytes of len `l`
let rest = v % maxVal;
for (let i = 1; i < 1 + l; i += 1) {
dest[i] = u8(rest);
rest >>= 8;
}
return dest;
}
// move one power down
maxEncoded = minEncoded;
minEncoded >>= 7;
}

throw new Error(`Unhandled number encoding: ${v}`);
}
6 changes: 3 additions & 3 deletions assembly/gas.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/** Gas type. */
export type Gas = u64;
export type Gas = i64;

/** Create a new gas counter instance depending on the gas value. */
export function gasCounter(gas: u64): GasCounter {
export function gasCounter(gas: i64): GasCounter {
return new GasCounterU64(gas);
}

Expand All @@ -25,7 +25,7 @@ class GasCounterU64 implements GasCounter {
constructor(private gas: Gas) {}

set(g: Gas): void {
this.gas = <u64>g;
this.gas = <i64>g;
}

get(): Gas {
Expand Down
9 changes: 5 additions & 4 deletions assembly/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { VmInput, getAssembly, runVm } from "./api-generic";
import { VmInput, VmOutput, getAssembly, runVm } from "./api-generic";
import { decodeProgram, decodeSpi, liftBytes } from "./program";

export * from "./api";
export { runVm, getAssembly } from "./api-generic";
export { wrapAsProgram } from "./program-build";

export enum InputKind {
Generic = 0,
Expand All @@ -24,16 +25,16 @@ export function disassemble(input: u8[], kind: InputKind): string {
return `Unknown kind: ${kind}`;
}

export function runProgram(input: u8[], kind: InputKind): void {
export function runProgram(input: u8[], registers: u64[], kind: InputKind): VmOutput {
if (kind === InputKind.Generic) {
const vmInput = new VmInput();
vmInput.registers[7] = 9;
vmInput.registers = registers;
vmInput.gas = 10_000;
vmInput.program = input;

const output = runVm(vmInput, true);
console.log(`Finished with status: ${output.status}`);
return;
return output;
}

if (kind === InputKind.SPI) {
Expand Down
Loading

0 comments on commit cbdcb4c

Please sign in to comment.