forked from LeenkxTeam/Kmake
486 lines
19 KiB
JavaScript
486 lines
19 KiB
JavaScript
// Copyright 2020 the V8 project authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
/**
|
|
* @fileoverview Regression tests.
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const assert = require('assert');
|
|
const { execSync } = require("child_process");
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const sinon = require('sinon');
|
|
const tempfile = require('tempfile');
|
|
const tempy = require('tempy');
|
|
|
|
const baseMutator = require('../mutators/mutator.js');
|
|
const crossOver = require('../mutators/crossover_mutator.js');
|
|
const db = require('../db.js');
|
|
const exceptions = require('../exceptions.js');
|
|
const functionCallMutator = require('../mutators/function_call_mutator.js');
|
|
const helpers = require('./helpers.js');
|
|
const random = require('../random.js');
|
|
const scriptMutator = require('../script_mutator.js');
|
|
const sourceHelpers = require('../source_helpers.js');
|
|
const tryCatch = require('../mutators/try_catch.js');
|
|
const variableMutator = require('../mutators/variable_mutator.js');
|
|
|
|
|
|
const sandbox = sinon.createSandbox();
|
|
|
|
const SYNTAX_ERROR_RE = /.*SyntaxError.*/
|
|
|
|
function createFuzzTest(fake_db, settings, inputFiles) {
|
|
const sources = inputFiles.map(input => helpers.loadV8TestData(input));
|
|
|
|
const mutator = new scriptMutator.ScriptMutator(settings, fake_db);
|
|
const result = mutator.mutateMultiple(sources);
|
|
|
|
const output_file = tempfile('.js');
|
|
fs.writeFileSync(output_file, result.code);
|
|
return { file:output_file, flags:result.flags };
|
|
}
|
|
|
|
function execFile(jsFile) {
|
|
execSync("node " + jsFile, {stdio: ['pipe']});
|
|
}
|
|
|
|
describe('Regression tests', () => {
|
|
beforeEach(() => {
|
|
helpers.deterministicRandom(sandbox);
|
|
|
|
this.settings = {
|
|
ADD_VAR_OR_OBJ_MUTATIONS: 0.0,
|
|
MUTATE_CROSSOVER_INSERT: 0.0,
|
|
MUTATE_EXPRESSIONS: 0.0,
|
|
MUTATE_FUNCTION_CALLS: 0.0,
|
|
MUTATE_NUMBERS: 0.0,
|
|
MUTATE_VARIABLES: 0.0,
|
|
engine: 'v8',
|
|
testing: true,
|
|
}
|
|
});
|
|
|
|
afterEach(() => {
|
|
sandbox.restore();
|
|
});
|
|
|
|
it('combine strict and with', () => {
|
|
// Test that when a file with "use strict" is used in the inputs,
|
|
// the result is only strict if no other file contains anything
|
|
// prohibited in strict mode (here a with statement).
|
|
// It is assumed that such input files are marked as sloppy in the
|
|
// auto generated exceptions.
|
|
sandbox.stub(exceptions, 'getGeneratedSloppy').callsFake(
|
|
() => { return new Set(['regress/strict/input_with.js']); });
|
|
const {file, flags} = createFuzzTest(
|
|
'test_data/regress/empty_db',
|
|
this.settings,
|
|
['regress/strict/input_strict.js', 'regress/strict/input_with.js']);
|
|
execFile(file);
|
|
});
|
|
|
|
it('combine strict and with, life analysis', () => {
|
|
// As above, but without the sloppy file being marked. "Strict"
|
|
// incompatibility will also be detected at parse time.
|
|
const {file, flags} = createFuzzTest(
|
|
'test_data/regress/empty_db',
|
|
this.settings,
|
|
['regress/strict/input_strict.js', 'regress/strict/input_with.js']);
|
|
execFile(file);
|
|
});
|
|
|
|
it('combine strict and delete', () => {
|
|
// As above with unqualified delete.
|
|
sandbox.stub(exceptions, 'getGeneratedSloppy').callsFake(
|
|
() => { return new Set(['regress/strict/input_delete.js']); });
|
|
const {file, flags} = createFuzzTest(
|
|
'test_data/regress/empty_db',
|
|
this.settings,
|
|
['regress/strict/input_strict.js', 'regress/strict/input_delete.js']);
|
|
execFile(file);
|
|
});
|
|
|
|
it('combine strict and delete, life analysis', () => {
|
|
// As above, but without the sloppy file being marked. "Strict"
|
|
// incompatibility will also be detected at parse time.
|
|
const {file, flags} = createFuzzTest(
|
|
'test_data/regress/empty_db',
|
|
this.settings,
|
|
['regress/strict/input_strict.js', 'regress/strict/input_delete.js']);
|
|
execFile(file);
|
|
});
|
|
|
|
it('mutates negative value', () => {
|
|
// This tests that the combination of number, function call and expression
|
|
// mutator does't produce an update expression.
|
|
// Previously the 1 in -1 was replaced with another negative number leading
|
|
// to e.g. -/*comment/*-2. Then cloning the expression removed the
|
|
// comment and produced --2 in the end.
|
|
this.settings['MUTATE_NUMBERS'] = 1.0;
|
|
this.settings['MUTATE_FUNCTION_CALLS'] = 1.0;
|
|
this.settings['MUTATE_EXPRESSIONS'] = 1.0;
|
|
const {file, flags} = createFuzzTest(
|
|
'test_data/regress/empty_db',
|
|
this.settings,
|
|
['regress/numbers/input_negative.js']);
|
|
execFile(file);
|
|
});
|
|
|
|
it('mutates indices', () => {
|
|
// Test that indices are not replaced with a negative number causing a
|
|
// syntax error (e.g. {-1: ""}).
|
|
this.settings['MUTATE_NUMBERS'] = 1.0;
|
|
const {file, flags} = createFuzzTest(
|
|
'test_data/regress/empty_db',
|
|
this.settings,
|
|
['regress/numbers/input_indices.js']);
|
|
execFile(file);
|
|
});
|
|
|
|
it('does not collide the module builder', () => {
|
|
const {file, flags} = createFuzzTest(
|
|
'test_data/regress/empty_db',
|
|
this.settings,
|
|
['regress/dependency_collision/v8/test/mjsunit/wasm/input.js',
|
|
'regress/dependency_collision/chakra/input.js']);
|
|
execFile(file);
|
|
});
|
|
|
|
it('resolves flag contradictions', () => {
|
|
sandbox.stub(exceptions, 'CONTRADICTORY_FLAGS').value(
|
|
[['--flag1', '--flag2']])
|
|
const {file, flags} = createFuzzTest(
|
|
'test_data/regress/empty_db',
|
|
this.settings,
|
|
['v8/regress/contradictions/input1.js',
|
|
'v8/regress/contradictions/input2.js']);
|
|
assert.deepEqual(['--flag1'], flags);
|
|
});
|
|
|
|
it('skips mjs flags', () => {
|
|
const {file, flags} = createFuzzTest(
|
|
'test_data/regress/empty_db',
|
|
this.settings,
|
|
['v8/regress/mjs_flags/input.js']);
|
|
assert.deepEqual(['--ok-flag1', '--ok-flag2'], flags);
|
|
});
|
|
|
|
function testSuper(settings, db_path, expected) {
|
|
// Enforce mutations at every possible location.
|
|
settings['MUTATE_CROSSOVER_INSERT'] = 1.0;
|
|
// Choose the only-super-statments path. This also fixed the insertion
|
|
// order to only insert before a statement.
|
|
sandbox.stub(random, 'choose').callsFake(() => { return true; });
|
|
|
|
const fakeDb = new db.MutateDb(db_path);
|
|
const mutator = new crossOver.CrossOverMutator(settings, fakeDb);
|
|
|
|
// An input with a couple of insertion spots in constructors and
|
|
// methods of two classes. One root and one a subclass.
|
|
const source = helpers.loadTestData('regress/super/input.js');
|
|
mutator.mutate(source);
|
|
|
|
const mutated = sourceHelpers.generateCode(source);
|
|
helpers.assertExpectedResult(expected, mutated);
|
|
}
|
|
|
|
it('mutates super call', () => {
|
|
// Ensure that a super() call expression isn't added to a
|
|
// non-constructor class member or to a root class.
|
|
testSuper(
|
|
this.settings,
|
|
'test_data/regress/super/super_call_db',
|
|
'regress/super/call_expected.js');
|
|
});
|
|
|
|
it('mutates super member expression', () => {
|
|
// Ensure that a super.x member expression isn't added to a
|
|
// root class.
|
|
testSuper(
|
|
this.settings,
|
|
'test_data/regress/super/super_member_db',
|
|
'regress/super/member_expected.js');
|
|
});
|
|
|
|
it('does not cross-insert duplicate variables', () => {
|
|
// Ensure we don't declare a duplicate variable when the
|
|
// declaration is part of a cross-over inserted snippet.
|
|
this.settings['MUTATE_CROSSOVER_INSERT'] = 1.0;
|
|
const fakeDb = new db.MutateDb(
|
|
'test_data/regress/duplicates/duplicates_db');
|
|
const mutator = new crossOver.CrossOverMutator(this.settings, fakeDb);
|
|
|
|
const source = helpers.loadTestData('regress/duplicates/input.js');
|
|
mutator.mutate(source);
|
|
|
|
const mutated = sourceHelpers.generateCode(source);
|
|
helpers.assertExpectedResult(
|
|
'regress/duplicates/duplicates_expected.js', mutated);
|
|
});
|
|
|
|
function testAsyncReplacements(settings, expected) {
|
|
settings['MUTATE_FUNCTION_CALLS'] = 1.0;
|
|
sandbox.stub(sourceHelpers, 'loadResource').callsFake(() => {
|
|
return helpers.loadTestData('differential_fuzz/fake_resource.js');
|
|
});
|
|
|
|
// Go only into function-call replacements.
|
|
sandbox.stub(random, 'random').callsFake(() => { return 0.2; });
|
|
|
|
// Input with several async and non-async replacement sites.
|
|
const source = helpers.loadTestData('regress/async/input.js');
|
|
const mutator = new scriptMutator.ScriptMutator(
|
|
settings, 'test_data/regress/empty_db');
|
|
const mutated = mutator.mutateMultiple([source]).code;
|
|
helpers.assertExpectedResult(expected, mutated);
|
|
}
|
|
|
|
it('makes no cross-async replacements', () => {
|
|
// Test to show that when REPLACE_CROSS_ASYNC_PROB isn't chosen, there
|
|
// are no replacements that change the async property.
|
|
sandbox.stub(functionCallMutator, 'REPLACE_CROSS_ASYNC_PROB').value(0);
|
|
testAsyncReplacements(
|
|
this.settings, 'regress/async/no_async_expected.js');
|
|
});
|
|
|
|
it('makes full cross-async replacements', () => {
|
|
// Test to show that when REPLACE_CROSS_ASYNC_PROB is chosen, the
|
|
// replacement functions are from the pool of all functions (both
|
|
// maintaining and not maintaining the async property).
|
|
sandbox.stub(functionCallMutator, 'REPLACE_CROSS_ASYNC_PROB').value(1);
|
|
testAsyncReplacements(
|
|
this.settings, 'regress/async/full_async_expected.js');
|
|
});
|
|
|
|
it('does not drop required parentheses', () => {
|
|
sandbox.stub(sourceHelpers, 'loadResource').callsFake(() => {
|
|
return helpers.loadTestData('differential_fuzz/fake_resource.js');
|
|
});
|
|
|
|
const source = helpers.loadTestData('regress/parentheses/input.js');
|
|
const mutator = new scriptMutator.ScriptMutator(
|
|
this.settings, 'test_data/regress/empty_db');
|
|
const mutated = mutator.mutateMultiple([source]).code;
|
|
helpers.assertExpectedResult('regress/parentheses/expected.js', mutated);
|
|
});
|
|
|
|
it('loads async iterator resource', () => {
|
|
// Test that inputs with the `AsyncIterator` identifier load an additional
|
|
// stub.
|
|
sandbox.stub(sourceHelpers, 'loadResource').callsFake((resource) => {
|
|
if (resource === 'async_iterator.js') {
|
|
// Stub out resource loading, except for the resource in question.
|
|
return sourceHelpers.loadResource.wrappedMethod(resource);
|
|
}
|
|
return helpers.loadTestData('differential_fuzz/fake_resource.js');
|
|
});
|
|
|
|
const source = helpers.loadTestData('regress/iterator/input.js');
|
|
const mutator = new scriptMutator.ScriptMutator(
|
|
this.settings, 'test_data/regress/empty_db');
|
|
const mutated = mutator.mutateMultiple([source]).code;
|
|
helpers.assertExpectedResult('regress/iterator/expected.js', mutated);
|
|
});
|
|
|
|
it('does not crash on spurious identifiers', () => {
|
|
// Test that we don't erroneously load identifiers like `toString` from
|
|
// the map of additional resources.
|
|
const source = helpers.loadTestData('regress/resources/input.js');
|
|
const mutator = new scriptMutator.ScriptMutator(
|
|
this.settings, 'test_data/regress/empty_db');
|
|
const mutated = mutator.mutateMultiple([source]).code;
|
|
});
|
|
|
|
it('does not mutate loop variables', () => {
|
|
sandbox.stub(sourceHelpers, 'loadResource').callsFake(() => {
|
|
return helpers.loadTestData('differential_fuzz/fake_resource.js');
|
|
});
|
|
|
|
// Choose replacement in var-or-object mutation.
|
|
sandbox.stub(random, 'random').callsFake(() => 0.75);
|
|
|
|
// We usually skip loop variables with 0.95 - set it to 1.0
|
|
// for a more predictable test.
|
|
sandbox.stub(variableMutator, 'SKIP_LOOP_VAR_PROB').value(1.0);
|
|
|
|
// Try-catch is not helpful for reading the test output.
|
|
sandbox.stub(
|
|
tryCatch.AddTryCatchMutator.prototype, "mutate").returns(undefined);
|
|
|
|
// Maximum variable mutations.
|
|
this.settings['MUTATE_VARIABLES'] = 1.0;
|
|
this.settings['ADD_VAR_OR_OBJ_MUTATIONS'] = 1.0;
|
|
|
|
// Also stress closure removal (though it has no effect in this test).
|
|
this.settings['TRANSFORM_CLOSURES'] = 1.0;
|
|
|
|
const source = helpers.loadTestData('loop_mutations.js');
|
|
const mutator = new scriptMutator.ScriptMutator(
|
|
this.settings, 'test_data/regress/empty_db');
|
|
const mutated = mutator.mutateMultiple([source]).code;
|
|
helpers.assertExpectedResult('loop_mutations_expected.js', mutated);
|
|
});
|
|
|
|
it('does not block-wrap nested functions', () => {
|
|
sandbox.stub(sourceHelpers, 'loadResource').callsFake(() => {
|
|
return helpers.loadTestData('differential_fuzz/fake_resource.js');
|
|
});
|
|
|
|
// Maximum top-level wrapping and no skipping try-catch to
|
|
// deterministically try to wrap an entire function block.
|
|
sandbox.stub(tryCatch, 'DEFAULT_SKIP_PROB').value(0.0);
|
|
sandbox.stub(tryCatch, 'DEFAULT_TOPLEVEL_PROB').value(1.0);
|
|
sandbox.stub(tryCatch, 'IGNORE_DEFAULT_PROB').value(0.0);
|
|
|
|
const source = helpers.loadTestData('regress/legacy_scope/input.js');
|
|
const mutator = new scriptMutator.ScriptMutator(
|
|
this.settings, 'test_data/regress/empty_db');
|
|
const mutated = mutator.mutateMultiple([source]).code;
|
|
helpers.assertExpectedResult('regress/legacy_scope/expected.js', mutated);
|
|
});
|
|
|
|
it('does not try-catch wrap in infinite while loops', () => {
|
|
sandbox.stub(sourceHelpers, 'loadResource').callsFake(() => {
|
|
return helpers.loadTestData('differential_fuzz/fake_resource.js');
|
|
});
|
|
|
|
// No top-level wrapping and no skipping try-catch to
|
|
// deterministically try to wrap within the loop.
|
|
sandbox.stub(tryCatch, 'DEFAULT_SKIP_PROB').value(0.0);
|
|
sandbox.stub(tryCatch, 'DEFAULT_TOPLEVEL_PROB').value(0.0);
|
|
sandbox.stub(tryCatch, 'IGNORE_DEFAULT_PROB').value(0.0);
|
|
|
|
const source = helpers.loadTestData('regress/try_catch_while_true/input.js');
|
|
const mutator = new scriptMutator.ScriptMutator(
|
|
this.settings, 'test_data/regress/empty_db');
|
|
const mutated = mutator.mutateMultiple([source]).code;
|
|
helpers.assertExpectedResult('regress/try_catch_while_true/expected.js', mutated);
|
|
});
|
|
|
|
it('does not replace with diverging functions', () => {
|
|
sandbox.stub(sourceHelpers, 'loadResource').callsFake(() => {
|
|
return helpers.loadTestData('differential_fuzz/fake_resource.js');
|
|
});
|
|
|
|
// Go only into function-call replacements.
|
|
sandbox.stub(random, 'random').callsFake(() => { return 0.2; });
|
|
this.settings['MUTATE_FUNCTION_CALLS'] = 1.0;
|
|
|
|
const source = helpers.loadTestData('regress/infinite_loop_fun/input.js');
|
|
const mutator = new scriptMutator.ScriptMutator(
|
|
this.settings, 'test_data/regress/empty_db');
|
|
const mutated = mutator.mutateMultiple([source]).code;
|
|
helpers.assertExpectedResult('regress/infinite_loop_fun/expected.js', mutated);
|
|
});
|
|
|
|
it('mutates less in large loops', () => {
|
|
sandbox.stub(sourceHelpers, 'loadResource').callsFake(() => {
|
|
return helpers.loadTestData('differential_fuzz/fake_resource.js');
|
|
});
|
|
|
|
// Try-catch is not helpful for reading the test output.
|
|
sandbox.stub(
|
|
tryCatch.AddTryCatchMutator.prototype, "mutate").returns(undefined);
|
|
|
|
// Maximum var/obj and cross-over mutations.
|
|
this.settings['ADD_VAR_OR_OBJ_MUTATIONS'] = 1.0;
|
|
this.settings['MUTATE_CROSSOVER_INSERT'] = 1.0;
|
|
const fakeDb = new db.MutateDb(
|
|
'test_data/regress/duplicates/duplicates_db');
|
|
sandbox.stub(crossOver.CrossOverMutator.prototype, "db").returns(fakeDb);
|
|
|
|
// Test case with large loops.
|
|
const source = helpers.loadTestData('regress/large_loops/input.js');
|
|
const mutator = new scriptMutator.ScriptMutator(
|
|
this.settings, 'test_data/regress/empty_db');
|
|
const mutated = mutator.mutateMultiple([source]).code;
|
|
helpers.assertExpectedResult('regress/large_loops/expected.js', mutated);
|
|
});
|
|
|
|
// TODO(https://crbug.com/389069288): This still needs fixing, though it's
|
|
// probably rare that usage of 'arguments' and assigning to 'arguments'
|
|
// happen in the same test case. Due to the assignment, all occurences
|
|
// are normalized. Such an assignment is not allowed in strict mode and
|
|
// causes a syntax error in this case. We'd need to fix this in a way that
|
|
// unnormalized argument assignment is not mixed with strict-mode cases.
|
|
//
|
|
// It's analogue with eval and undefined, though the strict-mode rules are
|
|
// asymmetric:
|
|
//
|
|
// Not allowed in strict mode:
|
|
// let eval = 0;
|
|
// let arguments = 0;
|
|
// arguments = 0;
|
|
// undefined = 0;
|
|
// eval = 0
|
|
//
|
|
// Allowed in strict mode:
|
|
// let undefined = 0;
|
|
it('does not normalize arguments', () => {
|
|
sandbox.stub(sourceHelpers, 'loadResource').callsFake(() => {
|
|
return helpers.loadTestData('differential_fuzz/fake_resource.js');
|
|
});
|
|
|
|
// Try-catch is not helpful for reading the test output.
|
|
sandbox.stub(
|
|
tryCatch.AddTryCatchMutator.prototype, "mutate").returns(undefined);
|
|
|
|
const source = helpers.loadTestData('regress/arguments/input.js');
|
|
const mutator = new scriptMutator.ScriptMutator(
|
|
this.settings, 'test_data/regress/empty_db');
|
|
const mutated = mutator.mutateMultiple([source]).code;
|
|
helpers.assertExpectedResult('regress/arguments/expected.js', mutated);
|
|
});
|
|
|
|
it('does not try-catch wrap yield expressions', () => {
|
|
sandbox.stub(sourceHelpers, 'loadResource').callsFake(() => {
|
|
return helpers.loadTestData('differential_fuzz/fake_resource.js');
|
|
});
|
|
|
|
const source = helpers.loadTestData('regress/yield/input.js');
|
|
const mutator = new scriptMutator.ScriptMutator(
|
|
this.settings, 'test_data/regress/empty_db');
|
|
const mutated = mutator.mutateMultiple([source]).code;
|
|
helpers.assertExpectedResult('regress/yield/expected.js', mutated);
|
|
});
|
|
|
|
it('iterates snippets', () => {
|
|
const mutator = new scriptMutator.CrossScriptMutator(
|
|
this.settings, 'test_data/regress/empty_db');
|
|
const testRunner = new mutator.runnerClass();
|
|
testRunner.dbPath = 'test_data/regress/super/super_call_db';
|
|
for (const [i, inputs] of testRunner.enumerateInputs()) {
|
|
const mutated = mutator.mutateMultiple(inputs);
|
|
helpers.assertExpectedResult(
|
|
`verify_db/expected_code_${i}.js`, mutated.code);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('DB tests', () => {
|
|
afterEach(() => {
|
|
sandbox.restore();
|
|
});
|
|
|
|
it('creates DB with Object.assign', () => {
|
|
// Test that an Object.assign expression is inserted into the snippet DB.
|
|
const tmpOut = tempy.directory();
|
|
const mutateDb = new db.MutateDbWriter(tmpOut);
|
|
const source = helpers.loadTestData('regress/db/input/input.js');
|
|
mutateDb.process(source);
|
|
mutateDb.writeIndex();
|
|
const expressionFile = path.join(
|
|
tmpOut,
|
|
'CallExpression/113f18444843b9cbf6778f4ac8b6f7cf585b280b.json');
|
|
const content = fs.readFileSync(expressionFile, 'utf-8');
|
|
assert.deepEqual(
|
|
'{"type":"CallExpression","source":"Object.assign(VAR_0, VAR_1)",' +
|
|
'"isStatement":true,"originalPath":"regress/db/input/input.js",' +
|
|
'"dependencies":["VAR_0","VAR_1"],"needsSuper":false}', content);
|
|
});
|
|
});
|