Skip to content

feat: add updated fuzzer from tact-check repository #2340

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 43 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
e4c01a3
feat: added fuzzer code with some issues
Mell0r Mar 7, 2025
86568f9
fix: receiver.ts rewritten, many small bugfixes
Mell0r Mar 9, 2025
f49d0c9
fix: many linter fixes, all files codestyle switched to 4 spaces
Mell0r Mar 9, 2025
debcb8d
style: prettier fixes
Mell0r Mar 9, 2025
20daeef
fix: added non empty src info, but it is not working still
Mell0r Mar 11, 2025
8e21cff
fix: replaced simplified-string with string, added non-empty locInfo,…
Mell0r Mar 16, 2025
de9ebca
fix: added stdlib to compilation
Mell0r Mar 29, 2025
159d6bd
fix: added evalComptimeExpressions in the precompile replica
Mell0r Mar 29, 2025
608af66
fix: added constant definitions to trait generation
Mell0r Mar 29, 2025
0418907
fix: there is not possible to generate two equal entities of differen…
Mell0r Mar 30, 2025
8a09691
refactor: separated named and unnamed entities in scope to provide nu…
Mell0r Mar 30, 2025
7af6135
refactor: various small linter and cspell fixes
Mell0r Mar 30, 2025
63c3803
Merge branch 'main' into fuzzer-insertion
Mell0r Mar 30, 2025
c41a314
style: import type
Mell0r Mar 30, 2025
1a11374
feat: added correct wildcard formatting
Mell0r Mar 31, 2025
a37ca2c
refactor: adressed couple of todos and formatted files
Mell0r Mar 31, 2025
8e36352
style: done suggested change to prettifying imports from ast
Mell0r Mar 31, 2025
e6a55a9
Merge branch 'main' into fuzzer-insertion
jeshecdom Apr 3, 2025
6eb5e6f
tests: Added expression stats script.
jeshecdom Apr 4, 2025
ac21810
tests: added column titles in counts file.
jeshecdom Apr 4, 2025
fa12962
tests: build functions for contracts.
jeshecdom Apr 8, 2025
fbc54f6
feat: wip: added expression equality testing with stubs for now
Mell0r Apr 8, 2025
1c517ce
test: expression generator.
jeshecdom Apr 9, 2025
c90ad42
Merge branch 'fuzzer-insertion' into expression-fuzzing
Mell0r Apr 10, 2025
972f182
feat: simple test using the expression generator implemented
Mell0r Apr 10, 2025
6611366
fix: types in util functions specified more clearly, removed any cast
Mell0r Apr 10, 2025
34fd4e8
tests: Added logarithmic counts.
jeshecdom Apr 11, 2025
7959b7e
fix: error formatted correctly now, fixed interpreter context specifi…
Mell0r Apr 11, 2025
b4394e9
fix: shift operator checks in range [0..256]
jeshecdom Apr 11, 2025
abd7d3e
tests: Added indexed getters to ProxyContract.
jeshecdom Apr 11, 2025
bf285c8
fix: checkProperty error printing
Mell0r Apr 11, 2025
e145dc1
feat: merge with parent branch
Mell0r Apr 11, 2025
53d4eca
fix: added export from expression generation, swapped expects in test
Mell0r Apr 11, 2025
81431a0
fix: initializeGenerator function must receive the AstFactory object
jeshecdom Apr 11, 2025
c891808
fix: nullable bindings is now generated differently, no longer can ge…
Mell0r Apr 11, 2025
a15c9c9
refactor: moved test logic to separate file
Mell0r Apr 11, 2025
5e513db
refactor: fully port generator to fast-check.
jeshecdom Apr 12, 2025
54f210f
fix: add cell_hash, slice_hash, string_hash missing functions in mini…
jeshecdom Apr 12, 2025
7abb57b
feat: added interpratation and compilation failing tests saving to th…
Mell0r Apr 12, 2025
32b4420
Merge branch 'fuzzer-insertion' into expression-fuzzing
Mell0r Apr 12, 2025
ff735d2
wip: feat: merge with parent branch
Mell0r Apr 12, 2025
d9353f4
fix: removed partial sums from counting.
jeshecdom Apr 12, 2025
2a12aff
refactor: updated some wrong comments
jeshecdom Apr 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"src/test/exit-codes/contracts/compute-phase-errors.tact",
"src/test/e2e-emulated/map-property-tests/map-properties-key-value-types.ts",
"src/test/e2e-emulated/map-property-tests/build",
"src/test/fuzzer/src/minimal-fc-stdlib/stdlib.fc",
"/docs",
"src/benchmarks/contracts/func/notcoin/stdlib-custom.fc",
"src/benchmarks/contracts/func/notcoin/gas.fc",
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"gen:contracts:test:map": "ts-node ./src/test/e2e-emulated/map-property-tests/generate.ts",
"gen:contracts:all": "yarn gen:contracts:examples && yarn gen:contracts:test && yarn gen:contracts:benchmarks && yarn gen:contracts:test:map",
"gen": "yarn gen:grammar && yarn gen:stdlib && yarn gen:func-js && yarn gen:contracts:all",
"fuzz:expressions:stats": "ts-node src/test/fuzzer/test/expression-stats.ts",
"clean": "rm -fr dist",
"cleanall": "rm -fr dist node_modules",
"copy:stdlib": "ts-node src/stdlib/copy.build.ts",
Expand Down
2 changes: 2 additions & 0 deletions spell/cspell-list.txt
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ mintable
misparse
misparsed
mktemp
mult
multiformats
nanoton
nanotons
Expand All @@ -138,6 +139,7 @@ pinst
POSIX
postpack
prando
preorder
quadtree
quadtrees
RANDU
Expand Down
84 changes: 84 additions & 0 deletions src/test/fuzzer/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import * as os from "os";
import { existsSync, mkdirSync } from "fs";

/**
* The default number of executions per test. Corresponds to fast-check defaults.
*/
const DEFAULT_NUM_RUNS: number = 100;

/**
* Configuration handler for fuzz testing settings.
*/
export class FuzzConfig {
/**
* The number of samples to dump during fuzz testing.
* If `samplesNum` is not set, the fuzzer won't dump samples.
*/
public samplesNum: number | undefined;

/**
* A format used to dump samples.
*/
public samplesFormat: "ast" | "json" = "ast";

/**
* Explicitly specified fast-check seed.
*/
public seed: number | undefined;

/**
* Number of AST generation cycles. POSITIVE_INFINITY means running in the continuous fuzzing mode.
*/
public numRuns: number;

/**
* Directory to save contracts compiled during the compilation test.
*/
public compileDir: string;

/**
* Maximum AST generation depth.
*/
public maxDepth: number = 5;

constructor() {
this.samplesNum = process.env.SAMPLES_NUM
? parseInt(process.env.SAMPLES_NUM)
: undefined;
if (process.env.SAMPLES_FORMAT) {
this.validateAndSetFormat(process.env.SAMPLES_FORMAT);
}
this.compileDir = process.env.COMPILE_DIR ?? os.tmpdir();
if (process.env.COMPILE_DIR && !existsSync(process.env.COMPILE_DIR)) {
mkdirSync(process.env.COMPILE_DIR, { recursive: true });
}
this.seed = process.env.SEED ? parseInt(process.env.SEED) : undefined;
this.numRuns =
process.env.FUZZ === "1"
? Number.POSITIVE_INFINITY
: process.env.NUM_RUNS
? parseInt(process.env.NUM_RUNS)
: DEFAULT_NUM_RUNS;
if (this.samplesNum && this.numRuns < this.samplesNum) {
console.warn(
`the requested number of SAMPLES_NUM=${this.samplesNum} is less than NUM_RUNS=${this.numRuns}`,
);
}
}

/**
* Validates and sets the sample format based on the provided format string.
* Throws an error if the format is not supported.
* @param fmt The format string to validate and set.
*/
private validateAndSetFormat(fmt: string): void {
const supportedFormats: ("ast" | "json")[] = ["ast", "json"];
if (supportedFormats.includes(fmt as "ast" | "json")) {
this.samplesFormat = fmt as "ast" | "json";
} else {
throw new Error(
`unsupported SAMPLES_FMT: ${fmt} (supported options: "ast" and "json")`,
);
}
}
}
88 changes: 88 additions & 0 deletions src/test/fuzzer/src/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { prettyPrint } from "@/ast/ast-printer";
import { FuzzConfig } from "@/test/fuzzer/src/config";
import type { AstNode } from "@/ast/ast";
import { stringify } from "@/test/fuzzer/src/util";

/**
* FuzzContext represents a stateful context that handles AST generation.
* It keeps the global options used to control and configure the AST generation.
*/
export class FuzzContext {
/**
* Tracks the number of samples that have been printed.
*/
private printedSamplesCount: number = 0;

/**
* Configuration of the fuzzer.
*/
public config: FuzzConfig;

/**
* Current depth of AST expression generation, which limits recursive generation.
*/
private currentDepth: number = 0;

constructor() {
this.config = new FuzzConfig();
}

public getDepth(): number {
return this.currentDepth;
}

public incDepth(): void {
this.currentDepth++;
}

public decDepth(): void {
this.currentDepth--;
if (this.currentDepth < 0) {
throw new Error("Reached negative recursion depth");
}
}

public resetDepth(): void {
this.currentDepth = 0;
}

/**
* Formats the given AST construction according to the current formatter configuration.
*/
public format(value: AstNode, fmt = this.config.samplesFormat): string {
switch (fmt) {
case "json":
return stringify(value, 2);
case "ast":
return prettyPrint(value);
default:
throw new Error(
`Unsupported sample format: ${this.config.samplesFormat}`,
);
}
}

/**
* Prints the given sample if the number of already printed samples is less than the configured limit.
* @param sample The sample to print.
*/
public printSample(sample: AstNode): void {
if (
this.config.samplesNum === undefined ||
this.printedSamplesCount >= this.config.samplesNum
) {
return;
}
console.log(
`Sample #${this.printedSamplesCount}:\n${this.format(sample)}`,
);
this.printedSamplesCount++;
}
}

/**
* A global context accessible and mutable throughout the generation process to
* reflect changes across the AST.
*/
// eslint-disable-next-line prefer-const
export let GlobalContext: FuzzContext = new FuzzContext();
123 changes: 123 additions & 0 deletions src/test/fuzzer/src/generators/constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import type * as Ast from "@/ast/ast";
import {
generateAstIdFromName,
createSample,
generateAstId,
generateName,
dummySrcInfoPrintable,
} from "@/test/fuzzer/src/util";
import { tyToAstType } from "@/test/fuzzer/src/types";
import type { Type } from "@/test/fuzzer/src/types";
import type { Scope } from "@/test/fuzzer/src/scope";
import { NamedGenerativeEntity } from "@/test/fuzzer/src/generators/generator";

import fc from "fast-check";

/**
* An object that encapsulates a generated Ast.ConstantDecl.
*/
export class ConstantDecl extends NamedGenerativeEntity<Ast.ConstantDecl> {
constructor(scope: Scope, type: Type) {
super(type, createSample(generateAstId(scope)));
}

private getAttributes(
extraAttrs: Ast.ConstantAttribute[],
): Ast.ConstantAttribute[] {
const attrs: Ast.ConstantAttribute[] = extraAttrs;
attrs.push({ type: "abstract", loc: dummySrcInfoPrintable });
return attrs;
}

private generateImpl(
extraAttrs: Ast.ConstantAttribute[],
): fc.Arbitrary<Ast.ConstantDecl> {
return fc.record<Ast.ConstantDecl>({
kind: fc.constant("constant_decl"),
id: fc.constant(this.idx),
name: fc.constant(this.name),
type: fc.constant(tyToAstType(this.type)),
attributes: fc.constantFrom(this.getAttributes(extraAttrs)),
loc: fc.constant(dummySrcInfoPrintable),
});
}

/**
* Generates a constant declaration without extra attributes.
*/
public generate(): fc.Arbitrary<Ast.ConstantDecl> {
return this.generateImpl([]);
}

/**
* Create definition for this constant destination.
* @param init An initializer evaluable in compile-time. // cspell:disable-line
*/
public createDefinition(init: fc.Arbitrary<Ast.Expression>): ConstantDef {
return new ConstantDef(this.name.text, this.type, init);
}
}

/**
* An object that encapsulates a generated Ast.ConstantDef.
* @parentScope Scope this constant belongs to.
*/
export class ConstantDef extends NamedGenerativeEntity<Ast.ConstantDef> {
/**
* Create new constant definition from its name and type. Used to create definition from an existing declaration.
* @param init An initializer evaluable in compile-time. // cspell:disable-line
*/
constructor(
name: string,
type: Type,
private init: fc.Arbitrary<Ast.Expression>,
) {
super(type, generateAstIdFromName(name));
}
/**
* Create a new constant definition generation name from scope.
* @param scope Scope to generate constant name from.
* @param type Constant type.
* @param init An initializer evaluable in compile-time. // cspell:disable-line
*/
public static fromScope(
scope: Scope,
type: Type,
init: fc.Arbitrary<Ast.Expression>,
): ConstantDef {
return new ConstantDef(createSample(generateName(scope)), type, init);
}

private generateImpl(
extraAttrs: Ast.ConstantAttribute[],
init?: fc.Arbitrary<Ast.Expression>,
): fc.Arbitrary<Ast.ConstantDef> {
const chosenInit = init ?? this.init;
return fc.record<Ast.ConstantDef>({
kind: fc.constant("constant_def"),
id: fc.constant(this.idx),
name: fc.constant(this.name),
type: fc.constant(tyToAstType(this.type)),
initializer: chosenInit,
attributes: fc.constantFrom(extraAttrs),
loc: fc.constant(dummySrcInfoPrintable),
});
}

/**
* Generates a constant definition without extra attributes.
*/
public generate(): fc.Arbitrary<Ast.ConstantDef> {
return this.generateImpl([]);
}

/**
* Generates a constant definition with extra attributes and overridden init.
*/
public generateWithAttrs(
extraAttrs: Ast.ConstantAttribute[] = [],
init?: fc.Arbitrary<Ast.Expression>,
): fc.Arbitrary<Ast.ConstantDef> {
return this.generateImpl(extraAttrs, init);
}
}
Loading