Skip to content

Commit 2b1af46

Browse files
committed
rollup : Initial integration
1 parent 614def5 commit 2b1af46

8 files changed

Lines changed: 352 additions & 0 deletions

File tree

.claude/scheduled_tasks.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"sessionId":"3db4c6a4-83c9-4fa5-9c27-472daefee6e6","pid":16680,"procStart":"Thu May 14 16:45:36 2026","acquiredAt":1778777160785}

projects/rollup/Dockerfile

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
################################################################################
16+
17+
FROM gcr.io/oss-fuzz-base/base-builder-javascript
18+
19+
# Clone the upstream repository so OSS-Fuzz tracks the source.
20+
RUN git clone --depth 1 https://github.com/rollup/rollup.git
21+
22+
# Set up a separate workspace where Rollup is installed from npm
23+
# (the published package ships prebuilt native bindings, avoiding a Rust/WASM
24+
# toolchain inside the fuzz image).
25+
RUN mkdir -p $SRC/rollup-fuzz
26+
27+
COPY build.sh $SRC/
28+
COPY package.json $SRC/rollup-fuzz/
29+
COPY fuzz_parse_ast.js $SRC/rollup-fuzz/
30+
COPY fuzz_log_filter.js $SRC/rollup-fuzz/
31+
COPY fuzz_bundle.js $SRC/rollup-fuzz/
32+
33+
WORKDIR $SRC/rollup-fuzz

projects/rollup/build.sh

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/bin/bash -eu
2+
# Copyright 2026 Google LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
################################################################################
17+
18+
# Install rollup (from npm, ships prebuilt native bindings for linux-x64-gnu)
19+
# together with Jazzer.js, in the fuzz workspace prepared by the Dockerfile.
20+
npm install
21+
npm install --save-dev @jazzer.js/core
22+
23+
# Build fuzzers. The "-i rollup" flag tells Jazzer.js to instrument the
24+
# rollup package so coverage is tracked across rollup's JavaScript runtime.
25+
compile_javascript_fuzzer rollup-fuzz fuzz_parse_ast -i rollup --sync
26+
compile_javascript_fuzzer rollup-fuzz fuzz_log_filter -i rollup --sync
27+
compile_javascript_fuzzer rollup-fuzz fuzz_bundle -i rollup

projects/rollup/fuzz_bundle.js

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
////////////////////////////////////////////////////////////////////////////////
16+
17+
const { FuzzedDataProvider } = require("@jazzer.js/core");
18+
const { rollup } = require("rollup");
19+
20+
const FORMATS = ["amd", "cjs", "es", "iife", "system", "umd"];
21+
const GENERATED_CODE = ["es5", "es2015"];
22+
23+
function virtualPlugin(entryCode) {
24+
return {
25+
name: "virtual-entry",
26+
resolveId(source) {
27+
if (source === "entry.js") {
28+
return source;
29+
}
30+
// Mark every other import as external so we don't touch the real
31+
// filesystem while still exercising import-resolution code paths.
32+
return { id: source, external: true };
33+
},
34+
load(id) {
35+
if (id === "entry.js") {
36+
return entryCode;
37+
}
38+
return null;
39+
},
40+
};
41+
}
42+
43+
module.exports.fuzz = async function (data) {
44+
const provider = new FuzzedDataProvider(data);
45+
46+
const inputOptions = {
47+
input: "entry.js",
48+
plugins: [virtualPlugin(provider.consumeString(provider.consumeIntegralInRange(0, 4096)))],
49+
onLog: () => {},
50+
treeshake: provider.consumeBoolean(),
51+
};
52+
53+
const outputOptions = {
54+
format: FORMATS[provider.consumeIntegralInRange(0, FORMATS.length - 1)],
55+
name: provider.consumeString(provider.consumeIntegralInRange(0, 32)),
56+
generatedCode:
57+
GENERATED_CODE[provider.consumeIntegralInRange(0, GENERATED_CODE.length - 1)],
58+
compact: provider.consumeBoolean(),
59+
sourcemap: provider.consumeBoolean(),
60+
strict: provider.consumeBoolean(),
61+
esModule: provider.consumeBoolean(),
62+
};
63+
64+
let bundle;
65+
try {
66+
bundle = await rollup(inputOptions);
67+
await bundle.generate(outputOptions);
68+
} catch (error) {
69+
if (!isExpectedBundleError(error)) {
70+
throw error;
71+
}
72+
} finally {
73+
if (bundle) {
74+
try {
75+
await bundle.close();
76+
} catch (_) {
77+
// ignore close errors
78+
}
79+
}
80+
}
81+
};
82+
83+
function isExpectedBundleError(error) {
84+
if (!error) {
85+
return false;
86+
}
87+
if (typeof error.code === "string" && EXPECTED_CODES.has(error.code)) {
88+
return true;
89+
}
90+
const message =
91+
typeof error.message === "string" ? error.message.toLowerCase() : "";
92+
return EXPECTED_MESSAGES.some((m) => message.includes(m));
93+
}
94+
95+
// Rollup raises these structured error codes for malformed or unsupported
96+
// input; they are expected when fuzzing arbitrary source code and option
97+
// combinations.
98+
const EXPECTED_CODES = new Set([
99+
"PARSE_ERROR",
100+
"UNRESOLVED_IMPORT",
101+
"MISSING_EXPORT",
102+
"INVALID_OPTION",
103+
"INVALID_EXPORT_OPTION",
104+
"VALIDATION_ERROR",
105+
"MISSING_NAME_OPTION_FOR_IIFE_EXPORT",
106+
"MISSING_NAME_OPTION_FOR_UMD_EXPORT",
107+
"MIXED_EXPORTS",
108+
"AMBIGUOUS_EXTERNAL_NAMESPACES",
109+
"INVALID_EXTERNAL_ID",
110+
"MODULE_LEVEL_DIRECTIVE",
111+
"INVALID_PLUGIN_HOOK",
112+
"ILLEGAL_REASSIGNMENT",
113+
"ILLEGAL_IDENTIFIER_AS_NAME",
114+
"BAD_LOADER",
115+
"ASSET_NOT_FINALISED",
116+
"CHUNK_NOT_GENERATED",
117+
"FILE_NAME_CONFLICT",
118+
"INVALID_CONFIG_MODULE_FORMAT",
119+
"INPUT_HOOK_IN_OUTPUT_PLUGIN",
120+
"PLUGIN_ERROR",
121+
]);
122+
123+
const EXPECTED_MESSAGES = [
124+
"parse error",
125+
"unexpected",
126+
"unterminated",
127+
"must be supplied",
128+
"invalid value",
129+
"is not exported",
130+
"could not resolve",
131+
"is not a valid",
132+
"exports is not specified",
133+
"you must supply",
134+
];

projects/rollup/fuzz_log_filter.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
////////////////////////////////////////////////////////////////////////////////
16+
17+
const { FuzzedDataProvider } = require("@jazzer.js/core");
18+
const { getLogFilter } = require("rollup/getLogFilter");
19+
20+
const LOG_FIELDS = [
21+
"code",
22+
"message",
23+
"id",
24+
"plugin",
25+
"hook",
26+
"pluginCode",
27+
"binding",
28+
"url",
29+
"loc",
30+
];
31+
32+
module.exports.fuzz = function (data) {
33+
const provider = new FuzzedDataProvider(data);
34+
35+
const filterCount = provider.consumeIntegralInRange(0, 6);
36+
const filters = [];
37+
for (let i = 0; i < filterCount; i++) {
38+
filters.push(provider.consumeString(provider.consumeIntegralInRange(0, 80)));
39+
}
40+
41+
let logFilter;
42+
try {
43+
logFilter = getLogFilter(filters);
44+
} catch (error) {
45+
if (!isExpectedFilterError(error)) {
46+
throw error;
47+
}
48+
return;
49+
}
50+
51+
if (typeof logFilter !== "function") {
52+
return;
53+
}
54+
55+
const log = {};
56+
const fieldCount = provider.consumeIntegralInRange(0, LOG_FIELDS.length);
57+
for (let i = 0; i < fieldCount; i++) {
58+
const field = LOG_FIELDS[i];
59+
log[field] = provider.consumeString(provider.consumeIntegralInRange(0, 60));
60+
}
61+
62+
try {
63+
logFilter(log);
64+
} catch (error) {
65+
if (!isExpectedFilterError(error)) {
66+
throw error;
67+
}
68+
}
69+
};
70+
71+
function isExpectedFilterError(error) {
72+
if (!error || typeof error.message !== "string") {
73+
return false;
74+
}
75+
const message = error.message.toLowerCase();
76+
return (
77+
error.code === "INVALID_LOG_FILTER" ||
78+
message.includes("invalid filter") ||
79+
message.includes("log filter")
80+
);
81+
}

projects/rollup/fuzz_parse_ast.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
////////////////////////////////////////////////////////////////////////////////
16+
17+
const { FuzzedDataProvider } = require("@jazzer.js/core");
18+
const { parseAst } = require("rollup/parseAst");
19+
20+
module.exports.fuzz = function (data) {
21+
const provider = new FuzzedDataProvider(data);
22+
23+
const options = {
24+
allowReturnOutsideFunction: provider.consumeBoolean(),
25+
jsx: provider.consumeBoolean(),
26+
};
27+
28+
const code = provider.consumeRemainingAsString();
29+
30+
try {
31+
parseAst(code, options);
32+
} catch (error) {
33+
if (!isExpectedError(error)) {
34+
throw error;
35+
}
36+
}
37+
};
38+
39+
function isExpectedError(error) {
40+
if (!error || typeof error.message !== "string") {
41+
return false;
42+
}
43+
const message = error.message.toLowerCase();
44+
// Rollup's parser throws structured PARSE_ERROR codes for invalid input.
45+
// These are expected for fuzzed (typically malformed) source code.
46+
if (error.code === "PARSE_ERROR") {
47+
return true;
48+
}
49+
return EXPECTED_MESSAGES.some((m) => message.includes(m));
50+
}
51+
52+
const EXPECTED_MESSAGES = [
53+
"parse error",
54+
"unexpected",
55+
"unterminated",
56+
"invalid",
57+
"expected",
58+
];

projects/rollup/package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "rollup-fuzz",
3+
"version": "1.0.0",
4+
"description": "OSS-Fuzz fuzz targets for Rollup",
5+
"private": true,
6+
"dependencies": {
7+
"rollup": "latest"
8+
}
9+
}

projects/rollup/project.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
homepage: https://rollupjs.org
2+
language: javascript
3+
primary_contact: "lukas.taegert-atkinson@tngtech.com"
4+
main_repo: https://github.com/rollup/rollup
5+
fuzzing_engines:
6+
- libfuzzer
7+
sanitizers:
8+
- none
9+
base_os_version: ubuntu-24-04

0 commit comments

Comments
 (0)