New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

eslump

Package Overview
Dependencies
Maintainers
1
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

eslump - npm Package Compare versions

Comparing version 1.5.1 to 1.6.0

cli-program.js

7

CHANGELOG.md

@@ -0,1 +1,8 @@

### Version 1.6.0 (2017-06-26)
- Added: Node.js 4 support. Thanks to Teddy Katz (@not-an-aardvark)!
- Added: eslump can now explicitly be used as an npm module, exposing the
`generateRandomJS` function.
### Version 1.5.1 (2017-03-26)

@@ -2,0 +9,0 @@

101

codegen.js

@@ -1,3 +0,5 @@

const { FormattedCodeGen, Sep, Paren } = require("shift-codegen");
const { TokenStream } = require("shift-codegen/dist/token_stream");
"use strict";
const ShiftCodegen = require("shift-codegen");
const TokenStream = require("shift-codegen/dist/token_stream").TokenStream;
const shiftFuzzer = require("shift-fuzzer");

@@ -22,5 +24,8 @@ const shiftReducer = require("shift-reducer");

class CustomTokenStream extends TokenStream {
constructor({ comments = false, whitespace = false } = {}) {
constructor(options) {
super();
this._options = { comments, whitespace };
this._options = {
comments: options && options.comments,
whitespace: options && options.whitespace
};
this._probabilities = {

@@ -35,3 +40,3 @@ comments: Math.random(),

put(tokenString, isRegExp) {
const { optionalSemi } = this;
const optionalSemi = this.optionalSemi;

@@ -78,3 +83,4 @@ // Sometimes print semicolons, sometimes newlines.

newTokenString = random.string(randomTimes(), () =>
random.item(choices)());
random.item(choices)()
);
didRandomizeWhitespace = true;

@@ -85,3 +91,4 @@ }

if (
this._options.comments && Math.random() < this._probabilities.comments
this._options.comments &&
Math.random() < this._probabilities.comments
) {

@@ -114,5 +121,5 @@ newTokenString = random.insertComments(newTokenString, {

class CustomFormattedCodeGen extends FormattedCodeGen {
constructor(...args) {
super(...args);
class CustomFormattedCodeGen extends ShiftCodegen.FormattedCodeGen {
constructor() {
super();
this._probabilities = {

@@ -140,3 +147,3 @@ parentheses: Math.random()

reduceObjectExpression(node, data) {
const { properties } = data;
const properties = data.properties;
const newProperties = properties.map(removeParentheses);

@@ -149,3 +156,3 @@ const newData = Object.assign({}, data, { properties: newProperties });

reduceExportLocalSpecifier(node, data) {
const { name } = data;
const name = data.name;
const newName = removeParentheses(name);

@@ -158,3 +165,3 @@ const newData = Object.assign({}, data, { name: newName });

function removeParentheses(node) {
return node instanceof Paren
return node instanceof ShiftCodegen.Paren
? removeParentheses(node.expr.children[1])

@@ -164,32 +171,36 @@ : node;

const parenthesesAddingHandler = {
get(target, property) {
// Sometimes add extra parentheses.
if (
PARENTHESES_WRAPPABLE_METHOD.test(property) &&
Math.random() < target._probabilities.parentheses
) {
return (...args) => {
const times = randomTimes();
let node = target[property](...args);
for (let i = 0; i < times; i++) {
node = target.paren(
node,
Sep.EXPRESSION_PAREN_BEFORE,
Sep.EXPRESSION_PAREN_AFTER
);
}
return node;
};
const overridablePrototypeMethodNames = new Set();
for (
let prototype = CustomFormattedCodeGen.prototype;
prototype;
prototype = Object.getPrototypeOf(prototype)
) {
Object.getOwnPropertyNames(prototype)
.filter(methodName => PARENTHESES_WRAPPABLE_METHOD.test(methodName))
.forEach(methodName => overridablePrototypeMethodNames.add(methodName));
}
overridablePrototypeMethodNames.forEach(methodName => {
const originalMethod = CustomFormattedCodeGen.prototype[methodName];
CustomFormattedCodeGen.prototype[methodName] = function() {
let node = originalMethod.apply(this, arguments);
if (Math.random() < this._probabilities.parentheses) {
const times = randomTimes();
for (let i = 0; i < times; i++) {
node = this.paren(
node,
ShiftCodegen.Sep.EXPRESSION_PAREN_BEFORE,
ShiftCodegen.Sep.EXPRESSION_PAREN_AFTER
);
}
}
return node;
};
});
return target[property];
}
};
function codeGen(ast, options = {}) {
function codeGen(ast, options) {
const generator = new CustomFormattedCodeGen();
const proxiedGenerator = new Proxy(generator, parenthesesAddingHandler);
const tokenStream = new CustomTokenStream(options);
const rep = shiftReducer.default(proxiedGenerator, ast);
const tokenStream = new CustomTokenStream(options || {});
const rep = shiftReducer.default(generator, ast);
rep.emit(tokenStream);

@@ -199,4 +210,4 @@ return tokenStream.result;

function generateRandomJS(options = {}) {
const fuzzer = options.sourceType === "script"
function generateRandomJS(options) {
const fuzzer = options && options.sourceType === "script"
? shiftFuzzer.fuzzScript

@@ -207,3 +218,3 @@ : shiftFuzzer.fuzzModule;

new shiftFuzzer.FuzzerState({
maxDepth: options.maxDepth
maxDepth: options && options.maxDepth
})

@@ -213,4 +224,4 @@ );

return codeGen(randomAST, {
comments: options.comments,
whitespace: options.whitespace
comments: options && options.comments,
whitespace: options && options.whitespace
});

@@ -217,0 +228,0 @@ }

"use strict";
const createCodeFrame = require("babel-code-frame");
const fs = require("fs");
const mkdirp = require("mkdirp");
const optionator = require("optionator");
const path = require("path");
const generateRandomJS = require("./codegen");
const program = optionator({
prepend: [
"Usage: eslump [options]",
" or: eslump TEST_MODULE OUTPUT_DIR [options]",
"",
"Options:"
].join("\n"),
append: [
"When no arguments are provided, random JavaScript is printed to stdout.",
"Otherwise, TEST_MODULE is executed until an error occurs, or you kill the",
"program. When an error occurs, the error is printed to stdout and files",
"are written to OUTPUT_DIR:",
"",
" - random.js contains the random JavaScript that caused the error.",
" - random.backup.js is a backup of random.js.",
" - reproductionData.json contains additional data defined by TEST_MODULE",
" needed to reproduce the error caused by random.js, if any.",
" - Other files, if any, are defined by TEST_MODULE.",
"",
"OUTPUT_DIR is created as with `mkdir -p` if non-existent.",
"",
"The value of TEST_MODULE is passed directly to the `require` function.",
"",
"For information on how to write a TEST_MODULE, see:",
"https://github.com/lydell/eslump#test-files",
"",
"Examples:",
"",
' # See how "prettier" pretty-prints random JavaScript.',
" $ eslump | prettier",
"",
" # Run ./test.js and save the results in output/.",
" $ eslump ./test.js output/",
"",
" # Narrow down the needed JavaScript to produce the error.",
" # output/random.backup.js is handy if you go too far.",
" $ vim output/random.js",
"",
" # Reproduce the narrowed down case.",
" $ eslump ./test.js output/ --reproduce"
].join("\n"),
options: [
{
option: "max-depth",
type: "Number",
default: "7",
description: "The maximum depth of the random JavaScript."
},
{
option: "source-type",
type: "String",
enum: ["module", "script"],
default: "module",
description: "Parsing mode."
},
{
option: "whitespace",
type: "Boolean",
description: "Randomize the whitespace in the random JavaScript."
},
{
option: "comments",
type: "Boolean",
description: "Insert random comments into the random JavaScript."
},
{
option: "reproduce",
alias: "r",
type: "Boolean",
description: "Reproduce a previous error using files in OUTPUT_DIR."
},
{
option: "help",
alias: "h",
type: "Boolean",
description: "Show help",
overrideRequired: true
},
{
option: "version",
alias: "v",
type: "Boolean",
description: "Show version",
overrideRequired: true
}
]
});
const FILES = {
random: "random.js",
randomBackup: "random.backup.js",
reproductionData: "reproductionData.json"
};
function run(input) {
let options;
try {
options = program.parse(input, { slice: 0 });
} catch (error) {
return { stderr: error.message, code: 1 };
}
if (options.help) {
return { stdout: program.generateHelp(), code: 0 };
}
if (options.version) {
return { stdout: require("./package.json").version, code: 0 };
}
const numPositional = options._.length;
if (!(numPositional === 0 || numPositional === 2)) {
return {
stderr: `Expected 0 or 2 arguments, but ${numPositional} given.`,
code: 1
};
}
if (numPositional === 0 && options.reproduce !== undefined) {
return {
stderr: `The --reproduce flag cannot be used without arguments.`,
code: 1
};
}
if (numPositional === 0) {
return { stdout: generateRandomJS(options), code: 0 };
}
const [testModule, outputDir] = options._;
let testFunction;
try {
testFunction = require(testModule);
} catch (error) {
const message = error &&
error.code === "MODULE_NOT_FOUND" &&
error.message.includes(testModule)
? error.message
: `Error when loading module '${testModule}':\n${printError(error)}`;
return { stderr: message, code: 1 };
}
if (typeof testFunction !== "function") {
return {
stderr: `Expected \`require(${JSON.stringify(testModule)})\` to return a function, but got: ${testFunction}`,
code: 1
};
}
let reproductionCode;
let reproductionData;
if (options.reproduce) {
const codePath = path.join(outputDir, FILES.random);
const dataPath = path.join(outputDir, FILES.reproductionData);
try {
reproductionCode = fs.readFileSync(codePath, "utf8");
} catch (error) {
return {
stderr: `Failed to read '${codePath}' for reproduction:\n${error.message}`,
code: 1
};
}
let reproductionDataString;
try {
reproductionDataString = fs.readFileSync(dataPath, "utf8");
} catch (error) {
if (error.code !== "ENOENT") {
return {
stderr: `Failed to read '${dataPath}' for reproduction:\n${error.message}`,
code: 1
};
}
}
if (typeof reproductionDataString === "string") {
try {
reproductionData = JSON.parse(reproductionDataString);
} catch (error) {
return {
stderr: `Failed to parse JSON in '${dataPath}':\n${error.message}`,
code: 1
};
}
}
}
function* loop() {
let attemptNum = 1;
// eslint-disable-next-line no-constant-condition
while (true) {
const testData = {
code: options.reproduce ? reproductionCode : generateRandomJS(options),
sourceType: options.sourceType,
reproductionData
};
let result;
try {
result = testFunction(testData);
} catch (error) {
const mainMessage = [
`Attempt ${attemptNum}: The test function threw an unexpected error:`,
printError(error, testData.code)
].join("\n");
const extraMessage = writeFiles(outputDir, {
code: testData.code,
reproduce: options.reproduce
});
yield {
message: extraMessage
? `${mainMessage}\n\n${extraMessage}`
: mainMessage,
code: 1
};
break;
}
if (result) {
const mainMessage = [
`Attempt ${attemptNum}: The test function returned an error:`,
printError(result.error, testData.code)
].join("\n");
const extraMessage = writeFiles(outputDir, {
code: testData.code,
result,
reproduce: options.reproduce
});
yield {
message: extraMessage
? `${mainMessage}\n\n${extraMessage}`
: mainMessage,
code: 1
};
break;
} else if (options.reproduce) {
yield {
message: "Failed to reproduce the error; the test function succeeded.",
code: 1
};
break;
} else {
yield { message: `Attempt ${attemptNum}: Success` };
}
attemptNum += 1;
}
}
return { loop };
}
function writeFiles(
outputDir,
{ code = null, result = {}, reproduce = false } = {}
) {
try {
mkdirp.sync(outputDir);
} catch (error) {
return `Failed to \`mkdir -p\` '${outputDir}':\n${error.message}`;
}
const message = [];
function tryWrite(name, content) {
const fullPath = path.join(outputDir, name);
try {
fs.writeFileSync(fullPath, content);
} catch (error) {
message.push(`Failed to write write '${fullPath}':\n${error.message}`);
}
}
let reproductionDataString;
if ("reproductionData" in result && !reproduce) {
try {
reproductionDataString = JSON.stringify(result.reproductionData, null, 2);
} catch (error) {
message.push(
`Failed to run \`JSON.stringify\` on the returned reproductionData:`,
"reproductionData:",
indent(String(result.reproductionData)),
"Error:",
indent(error.message)
);
}
}
if (code !== null && !reproduce) {
tryWrite(FILES.random, code);
tryWrite(FILES.randomBackup, code);
}
if (reproductionDataString && !reproduce) {
tryWrite(FILES.reproductionData, reproductionDataString);
}
if (result.artifacts) {
for (const [name, content] of Object.entries(result.artifacts)) {
tryWrite(name, String(content));
}
}
return message.length === 0 ? null : message.join("\n\n");
}
function printError(error, code = "") {
if (code && error) {
const { line, column } = getLocation(error);
if (typeof line === "number") {
const codeFrame = createCodeFrame(code, line, column, {
highlightCode: true
});
return `${error.stack}\n${codeFrame}`;
}
}
return error && error.stack ? error.stack : String(error);
}
function getLocation(error) {
// Acorn and Babylon has `.loc.line` (1-indexed) and `.loc.column`
// (0-indexed). The Flow example is adjusted to this format.
// Espree and Esprima has `.lineNumber` (1-indexed) and `.column` (1-indexed).
// Shift-parser has `.line` (1-indexed) and `.column` (1-indexed).
const line = error.loc && typeof error.loc.line === "number"
? error.loc.line
: typeof error.lineNumber === "number"
? error.lineNumber
: typeof error.line === "number" ? error.line : undefined;
const column = error.loc && typeof error.loc.column === "number"
? error.loc.column + 1
: typeof error.column === "number" ? error.column : undefined;
return { line, column };
}
function indent(string) {
return string.replace(/^/mg, " ");
}
module.exports = { run, generateRandomJS };
module.exports = { generateRandomJS };
{
"name": "eslump",
"version": "1.5.1",
"version": "1.6.0",
"license": "MIT",

@@ -18,8 +18,8 @@ "author": "Simon Lydell",

],
"preferGlobal": true,
"bin": {
"eslump": "cli.js"
"eslump": "cli-runner.js"
},
"files": [
"cli.js",
"cli-program.js",
"cli-runner.js",
"codegen.js",

@@ -40,20 +40,21 @@ "index.js",

"random-item": "^1.0.0",
"shift-codegen": "^5.0.2",
"shift-fuzzer": "^1.0.1",
"shift-codegen": "^5.0.4",
"shift-fuzzer": "^1.0.2",
"shift-reducer": "^4.0.0"
},
"devDependencies": {
"acorn": "^4.0.11",
"babel-generator": "^6.24.0",
"babylon": "^6.16.1",
"acorn": "^5.0.3",
"babel-generator": "^6.25.0",
"babylon": "^6.17.4",
"escodegen": "^1.8.1",
"eslint": "^3.18.0",
"eslint-plugin-prettier": "^2.0.1",
"espree": "^3.4.0",
"esprima": "^3.1.3",
"flow-parser": "^0.42.0",
"prettier": "^0.22.0",
"shift-parser": "^5.0.4",
"unexpected": "^10.26.3"
"eslint": "^4.1.0",
"eslint-plugin-node": "^5.0.0",
"eslint-plugin-prettier": "^2.1.2",
"espree": "^3.4.3",
"esprima": "^4.0.0",
"flow-parser": "^0.47.0",
"prettier": "^1.4.4",
"shift-parser": "^5.0.7",
"unexpected": "^10.29.0"
}
}

@@ -0,1 +1,3 @@

"use strict";
const randomInt = require("random-int");

@@ -44,3 +46,3 @@ const randomItem = require("random-item");

function randomArray(length, randomItem) {
return [...Array(length)].map(() => randomItem());
return Array.from(Array(length)).map(() => randomItem());
}

@@ -67,5 +69,5 @@

function randomMultiLineComment({ allowNewlines = false } = {}) {
function randomMultiLineComment(options) {
const chars = whitespace.concat(
allowNewlines ? lineTerminators : [],
options && options.allowNewlines ? lineTerminators : [],
letters

@@ -86,7 +88,5 @@ );

function insertComments(
whitespaceString,
{ times = 1, allowNewlines = false } = {}
) {
const choices = allowNewlines
function insertComments(whitespaceString, options) {
const times = options && options.times === undefined ? 1 : options.times;
const choices = options && options.allowNewlines
? insertCommentsChoicesWithNewlines

@@ -93,0 +93,0 @@ : insertCommentsChoices;

# eslump
CLI tool for fuzz testing JavaScript parsers and suchlike programs.
Fuzz testing JavaScript parsers and suchlike programs.

@@ -13,6 +13,14 @@ > **es :** short for ECMAScript (the JavaScript standard)

eslump is primarily intended to be used as a CLI tool.
`npm install --global eslump`
You can also use parts of it as a Node.js module.
`npm install eslump`
## Usage
### CLI
```

@@ -66,2 +74,60 @@ Usage: eslump [options]

### Module
### Overview
```js
const {generateRandomJS} = require("eslump");
const randomJSString = generateRandomJS({
sourceType: "module",
maxDepth: 7,
comments: false,
whitespace: false,
});
```
#### generateRandomJS(options)
Returns a string of random JavaScript code.
If you want, you can pass some options:
Option | Type | Default | Description
-------|------|---------|------------
sourceType | `"module"` or `"script"` | `"module"` | The type of code to generate.
maxDepth | integer | 7 | How deeply nested AST:s to generate.
comments | boolean | false | Whether or not to generate random comments.
whitespace | boolean | false | Whether or not to generate random whitespace.
## Disclaimer
eslump was created from the need of finding edge cases in [Prettier]. It started
out as a bare-bones little script in a branch on my fork of that repo. As I
wanted more and more features, I extracted it and fleshed it out in its own
repo. Then I realized that it might be useful to others, so I put it on GitHub
and made the CLI installable from npm.
Initially, eslump basically just strung together [shift-fuzzer] and
[shift-codegen]. Then, I realized that no random comments were generated, so I
hacked that in (along with random whitespace) since comments are very difficult
to get right in Prettier. Then, random parentheses and semicolons where
requested, so I hacked that in as well.
eslump has successfully found lots of little edge cases in Prettier, so it
evidently works. But there are no tests. (I’ve just gone meta and fuzz-tested it
using itself basically.)
From the beginning eslump was only ever intended to be a CLI tool, but other
people have started to want to use eslump's code generation as an npm module, so
these days it can also be used as a module. If you know what you're doing.
Here are some features I'd like to see from a proper random JS library:
- No hacks.
- Seeded randomness, so things can be reproduced.
- JSX and Flow support.
- Ability to generate code without any early errors.
- Possibly ways to prevent certain syntax constructs from being generated.
## Examples

@@ -82,3 +148,3 @@

- [escodegen]
- [prettier]
- [Prettier]
- [shift-codegen]

@@ -181,9 +247,2 @@

## Ideas for the future
- Fuzzing JSX.
- Fuzzing Flowtype annotations.
- Automatically narrow down JavaScript that causes an error, instead of having
the user do it manually. Looking for a challenge? You’ve just found it.
## License

@@ -201,3 +260,3 @@

[flow]: https://github.com/facebook/flow
[prettier]: https://github.com/prettier/prettier/
[Prettier]: https://github.com/prettier/prettier/
[shift-codegen]: https://github.com/shapesecurity/shift-codegen-js

@@ -204,0 +263,0 @@ [shift-fuzzer]: https://github.com/shapesecurity/shift-fuzzer-js

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc