Skip to content

Commit c9c2098

Browse files
committed
refactor: fuzzer mode is unified
1 parent 799edf3 commit c9c2098

11 files changed

Lines changed: 86 additions & 64 deletions

File tree

packages/core/cli.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,13 @@
1717

1818
import yargs, { Argv } from "yargs";
1919

20+
import {
21+
defaultCLIOptions,
22+
OptionsManager,
23+
OptionSource,
24+
} from "@jazzer.js/options";
25+
2026
import { FuzzingExitCode, startFuzzing } from "./core";
21-
import { defaultCLIOptions, OptionsManager, OptionSource } from "./options";
2227
import { prepareArgs } from "./utils";
2328

2429
// Use yargs to parse command line arguments and provide a nice CLI experience.

packages/core/core.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
registerEsmLoaderHooks,
3333
registerInstrumentor,
3434
} from "@jazzer.js/instrumentor";
35+
import { OptionsManager, resolveEngine } from "@jazzer.js/options";
3536

3637
import { getCallbacks } from "./callback";
3738
import {
@@ -508,14 +509,6 @@ export function asFindingAwareFuzzFn(
508509
// Export public API from within core module for easy access.
509510
export * from "./api";
510511
export { FuzzedDataProvider } from "./FuzzedDataProvider";
511-
export {
512-
AllowedFuzzTestOptions,
513-
Options,
514-
OptionsManager,
515-
OptionSource,
516-
OptionsWithSource,
517-
printOptions,
518-
} from "./options";
519512

520513
export type {
521514
FuzzTarget,

packages/core/options.ts

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,19 @@
1515
*/
1616

1717
import fs from "fs";
18-
import * as util from "util";
1918

2019
import * as tmp from "tmp";
2120

2221
import {
2322
type LibAflOptions,
23+
Mode,
2424
type OptionsManager,
25+
printOptions,
2526
resolveEngine,
26-
toOptionsWithPrintableSources,
2727
} from "@jazzer.js/options";
2828

2929
import { useDictionaryByParams } from "./dictionary";
3030

31-
export * from "@jazzer.js/options";
32-
3331
export function buildLibFuzzerOptions(options: OptionsManager) {
3432
let params: string[] = [];
3533
params = optionDependentParams(options, params);
@@ -105,7 +103,7 @@ export function buildLibAflOptions(options: OptionsManager): LibAflOptions {
105103
);
106104
}
107105

108-
if (options.get("mode") === "regression") {
106+
if (options.get("mode") === Mode.Regression) {
109107
// Regression mode should replay every available corpus input unless the
110108
// user asked to stop for some other reason, mirroring libFuzzer's behavior.
111109
runs = 0;
@@ -180,18 +178,6 @@ function parseDecimalInteger(name: string, value: string): number {
180178
return parsed;
181179
}
182180

183-
export function printOptions(options: OptionsManager, infix = "") {
184-
if (process.env.JAZZER_DEBUG) {
185-
console.error(
186-
util.formatWithOptions(
187-
{ maxArrayLength: null, depth: null, colors: false },
188-
`DEBUG: [core] Jazzer.js options ${infix}: \n%O`,
189-
toOptionsWithPrintableSources(options),
190-
),
191-
);
192-
}
193-
}
194-
195181
function logInfoAboutFuzzerOptions(fuzzerOptions: string[]) {
196182
fuzzerOptions.slice(1).forEach((element) => {
197183
if (element.length > 0 && element[0] != "-") {
@@ -209,7 +195,7 @@ function optionDependentParams(
209195
}
210196

211197
let opts = options.get("fuzzerOptions");
212-
if (options.get("mode") === "regression") {
198+
if (options.get("mode") === Mode.Regression) {
213199
// The last provided option takes precedence
214200
opts = opts.concat("-runs=0");
215201
}

packages/jest-runner/config.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { OptionsManager, OptionSource } from "@jazzer.js/options";
17+
import { Mode, OptionsManager, OptionSource } from "@jazzer.js/options";
1818

1919
import { loadConfig } from "./config";
2020

@@ -46,12 +46,12 @@ describe("Config", () => {
4646
);
4747
});
4848
it("default to regression mode", () => {
49-
expect(loadConfig().get("mode")).toEqual("regression");
49+
expect(loadConfig().get("mode")).toEqual(Mode.Regression);
5050
});
5151
it("set fuzzing mode based on environment variable", () => {
5252
try {
5353
process.env.JAZZER_FUZZ = "1";
54-
expect(loadConfig().get("mode")).toEqual("fuzzing");
54+
expect(loadConfig().get("mode")).toEqual(Mode.Fuzzing);
5555
} finally {
5656
delete process.env.JAZZER_FUZZ;
5757
}

packages/jest-runner/config.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@
1616

1717
import { cosmiconfigSync } from "cosmiconfig";
1818

19-
import { Options, OptionsManager, OptionSource } from "@jazzer.js/options";
19+
import {
20+
Mode,
21+
Options,
22+
OptionsManager,
23+
OptionSource,
24+
} from "@jazzer.js/options";
2025

2126
export const TIMEOUT_PLACEHOLDER = Number.MIN_SAFE_INTEGER;
2227

@@ -29,7 +34,7 @@ export function loadConfig(
2934

3035
// Switch to fuzzing mode if environment variable `JAZZER_FUZZ` is set.
3136
if (process.env.JAZZER_FUZZ) {
32-
config.mode = "fuzzing";
37+
config.mode = Mode.Fuzzing;
3338
}
3439
// Merge explicitly passed in options, e.g. coverage settings from Jest.
3540
Object.assign(config, options);

packages/jest-runner/fuzz.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import * as tmp from "tmp";
2121

2222
import { FindingAwareFuzzTarget, startFuzzingNoInit } from "@jazzer.js/core";
2323
import { FuzzTarget } from "@jazzer.js/fuzzer";
24-
import { OptionsManager, OptionSource } from "@jazzer.js/options";
24+
import { Mode, OptionsManager, OptionSource } from "@jazzer.js/options";
2525

2626
import { Corpus } from "./corpus";
2727
import {
@@ -72,7 +72,7 @@ describe("fuzz", () => {
7272
const inputPaths = mockInputPaths();
7373
const fuzzingConfig = new OptionsManager(
7474
OptionSource.DefaultJestOptions,
75-
).merge({ mode: "fuzzing" }, OptionSource.ConfigurationFile);
75+
).merge({ mode: Mode.Fuzzing }, OptionSource.ConfigurationFile);
7676
await withMockTest(() => {
7777
const originalTestNamePattern = jest
7878
.fn()

packages/jest-runner/fuzz.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,15 @@ import {
2424
FuzzTarget,
2525
FuzzTargetAsyncOrValue,
2626
FuzzTargetCallback,
27-
printOptions,
2827
startFuzzingNoInit,
2928
} from "@jazzer.js/core";
3029
import {
3130
AllowedFuzzTestOptions,
31+
Mode,
3232
Options,
3333
OptionsManager,
3434
OptionSource,
35+
printOptions,
3536
} from "@jazzer.js/options";
3637

3738
import { Corpus } from "./corpus";
@@ -141,13 +142,13 @@ export function fuzz(
141142

142143
const wrappedFn = asFindingAwareFuzzFn(
143144
fn,
144-
localConfig.get("mode") === "fuzzing",
145+
localConfig.get("mode") === Mode.Fuzzing,
145146
localConfig.get("engine"),
146147
);
147148

148-
if (localConfig.get("mode") === "regression") {
149+
if (localConfig.get("mode") === Mode.Regression) {
149150
runInRegressionMode(name, wrappedFn, corpus, localConfig, globals, mode);
150-
} else if (localConfig.get("mode") === "fuzzing") {
151+
} else if (localConfig.get("mode") === Mode.Fuzzing) {
151152
runInFuzzingMode(name, wrappedFn, corpus, localConfig, globals, mode);
152153
} else {
153154
throw new Error(`Unknown mode ${localConfig.get("mode")}`);

packages/jest-runner/testStateInterceptor.test.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { Options, OptionsManager, OptionSource } from "@jazzer.js/options";
17+
import {
18+
Mode,
19+
Options,
20+
OptionsManager,
21+
OptionSource,
22+
} from "@jazzer.js/options";
1823

1924
import { interceptTestState } from "./testStateInterceptor";
2025

@@ -32,7 +37,7 @@ describe("Test state interceptor", () => {
3237
});
3338

3439
it("adjust test name pattern in regression mode", () => {
35-
const { env, config } = mockEnvironment({ mode: "regression" });
40+
const { env, config } = mockEnvironment({ mode: Mode.Regression });
3641

3742
const { originalTestNamePattern } = interceptTestState(env, config);
3843

@@ -43,7 +48,7 @@ describe("Test state interceptor", () => {
4348
});
4449

4550
it("do not adjust test name pattern in fuzzing mode", () => {
46-
const { env, config } = mockEnvironment({ mode: "fuzzing" });
51+
const { env, config } = mockEnvironment({ mode: Mode.Fuzzing });
4752

4853
const interceptedTestState = interceptTestState(env, config);
4954

@@ -68,7 +73,7 @@ describe("Test state interceptor", () => {
6873
}
6974

7075
const { env, config, originalHandleTestEvent } = mockEnvironment({
71-
mode: "fuzzing",
76+
mode: Mode.Fuzzing,
7277
});
7378

7479
interceptTestState(env, config);
@@ -88,7 +93,7 @@ describe("Test state interceptor", () => {
8893
});
8994

9095
it("deactivate Jest timeout in fuzzing mode", () => {
91-
const { env, config } = mockEnvironment({ mode: "fuzzing" });
96+
const { env, config } = mockEnvironment({ mode: Mode.Fuzzing });
9297

9398
const { currentTestTimeout } = interceptTestState(env, config);
9499

packages/jest-runner/testStateInterceptor.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import { JestEnvironment } from "@jest/environment";
1818
import { Circus } from "@jest/types";
1919

20-
import { OptionsManager } from "@jazzer.js/options";
20+
import { Mode, OptionsManager } from "@jazzer.js/options";
2121

2222
// Arbitrary high value to disable Jest timeout.
2323
const JEST_TIMEOUT_DISABLED = 1000 * 60 * 24 * 365;
@@ -47,7 +47,7 @@ export function interceptTestState(
4747
// test inside. This breaks test name pattern matching, so remove "$" from the end of the pattern,
4848
// and skip tests not matching the original pattern in the fuzz function.
4949
if (
50-
jazzerConfig.get("mode") == "regression" &&
50+
jazzerConfig.get("mode") == Mode.Regression &&
5151
state.testNamePattern?.source?.endsWith("$")
5252
) {
5353
originalTestNamePattern = state.testNamePattern;
@@ -60,7 +60,7 @@ export function interceptTestState(
6060
// In fuzzing mode, only execute the first encountered (not skipped) fuzz test
6161
// and mark all others as skipped.
6262
if (
63-
jazzerConfig.get("mode") === "fuzzing" &&
63+
jazzerConfig.get("mode") === Mode.Fuzzing &&
6464
event.test.mode !== "skip"
6565
) {
6666
if (
@@ -78,7 +78,7 @@ export function interceptTestState(
7878
} else if (event.name === "test_fn_start") {
7979
// Disable Jest timeout in fuzzing mode by setting it to a high value,
8080
// otherwise Jest will kill the fuzz test after it's timeout (default 5 seconds).
81-
if (jazzerConfig.get("mode") === "fuzzing") {
81+
if (jazzerConfig.get("mode") === Mode.Fuzzing) {
8282
state.testTimeout = JEST_TIMEOUT_DISABLED;
8383
}
8484
// Use configured timeout as fuzzing timeout as well. Every invocation

packages/options/index.ts

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16+
import * as util from "util";
17+
18+
export const Mode = {
19+
Fuzzing: "fuzzing",
20+
Regression: "regression",
21+
} as const;
22+
23+
export type Mode = (typeof Mode)[keyof typeof Mode];
1624

1725
export type LibAflOptions = {
1826
mode: "fuzzing" | "regression";
@@ -65,7 +73,7 @@ export interface Options {
6573
// Part of filepath names to include in the instrumentation.
6674
includes: string[];
6775
// Fuzzing mode.
68-
mode: "fuzzing" | "regression";
76+
mode: Mode;
6977
// Whether to run the fuzzer in sync mode or not.
7078
sync: boolean;
7179
// Timeout for one fuzzing iteration in milliseconds.
@@ -115,7 +123,7 @@ export const defaultCLIOptions: Options = Object.freeze({
115123
fuzzTarget: "",
116124
idSyncFile: "",
117125
includes: ["*"],
118-
mode: "fuzzing",
126+
mode: Mode.Fuzzing,
119127
sync: false,
120128
timeout: 5000,
121129
verbose: false,
@@ -124,7 +132,7 @@ export const defaultCLIOptions: Options = Object.freeze({
124132
export const defaultJestOptions: Options = Object.freeze({
125133
...defaultCLIOptions,
126134
engine: "libfuzzer",
127-
mode: "regression",
135+
mode: Mode.Regression,
128136
});
129137

130138
export type KeyFormatSource = (key: string) => string;
@@ -174,7 +182,6 @@ export enum OptionSource {
174182
}
175183

176184
export type FuzzingEngine = Options["engine"];
177-
export type FuzzingMode = Options["mode"];
178185

179186
export function resolveEngine(engine: string): FuzzingEngine {
180187
switch (engine) {
@@ -190,16 +197,23 @@ export function resolveEngine(engine: string): FuzzingEngine {
190197
}
191198
}
192199

193-
export function resolveMode(mode: string): FuzzingMode {
194-
switch (mode) {
195-
case "fuzzing":
196-
case "regression":
197-
return mode;
198-
default:
199-
throw new Error(
200-
`Unknown fuzzing mode '${mode}'. Supported modes are 'fuzzing' and 'regression'.`,
201-
);
200+
function isOneOf<T extends Record<string, string>>(
201+
obj: T,
202+
value: string,
203+
): value is T[keyof T] {
204+
return Object.values(obj).includes(value as T[keyof T]);
205+
}
206+
207+
export function resolveMode(mode: string): Mode {
208+
if (isOneOf(Mode, mode)) {
209+
return mode;
202210
}
211+
212+
throw new Error(
213+
`Unknown fuzzer mode '${mode}'. Supported modes are ${Object.values(Mode)
214+
.map((v) => `'${v}'`)
215+
.join(", ")}.`,
216+
);
203217
}
204218

205219
type DefaultSourceInfo = {
@@ -455,6 +469,18 @@ export function toOptionsWithPrintableSources(
455469
return result;
456470
}
457471

472+
export function printOptions(options: OptionsManager, infix = "") {
473+
if (process.env.JAZZER_DEBUG) {
474+
console.error(
475+
util.formatWithOptions(
476+
{ maxArrayLength: null, depth: null, colors: false },
477+
`DEBUG: [core] Jazzer.js options ${infix}: \n%O`,
478+
toOptionsWithPrintableSources(options),
479+
),
480+
);
481+
}
482+
}
483+
458484
export function validateKeySource(key: keyof Options, source: OptionSource) {
459485
const sourceName = defaultOptions[source].name;
460486

0 commit comments

Comments
 (0)