com.google.javascript.jscomp.fuzzing.Driver.java Source code

Java tutorial

Introduction

Here is the source code for com.google.javascript.jscomp.fuzzing.Driver.java

Source

/*
 * Copyright 2013 The Closure Compiler Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.javascript.jscomp.fuzzing;

import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import com.google.common.io.CharStreams;
import com.google.common.io.Files;
import com.google.javascript.jscomp.CommandLineRunner;
import com.google.javascript.jscomp.CompilationLevel;
import com.google.javascript.jscomp.Compiler;
import com.google.javascript.jscomp.CompilerInput;
import com.google.javascript.jscomp.CompilerOptions;
import com.google.javascript.jscomp.JSModule;
import com.google.javascript.jscomp.Result;
import com.google.javascript.jscomp.SourceFile;
import com.google.javascript.jscomp.SyntheticAst;
import com.google.javascript.jscomp.VariableRenamingPolicy;
import com.google.javascript.rhino.Node;

import org.json.JSONException;
import org.json.JSONObject;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;

import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * UNDER DEVELOPMENT. DO NOT USE!
 * @author zplin@google.com (Zhongpeng Lin)
 */
public class Driver {
    @Option(name = "--number_of_runs", usage = "The number of runs of the fuzzer. "
            + "If this option is missing, the driver will run forever")
    private int numberOfRuns = -1;

    @Option(name = "--max_ast_size", usage = "The max number of nodes in the generated ASTs. Default: 100")
    private int maxASTSize;

    @Option(name = "--compilation_level", usage = "Specifies the compilation level to use. "
            + "Default: SIMPLE_OPTIMIZATIONS")
    private CompilationLevel compilationLevel = CompilationLevel.SIMPLE_OPTIMIZATIONS;

    @Option(name = "--seed", usage = "Specifies the seed for the fuzzer. "
            + "It will override --number_of_runs to 1. " + "If not given, System.currentTimeMillis() will be used")
    private long seed = -1;

    @Option(name = "--logging_level", usage = "Specifies the logging level for the driver. " + "Default: INFO")
    private LoggingLevel level = LoggingLevel.INFO;

    @Option(name = "--config", required = true, usage = "Specifies the configuration file")
    private String configFileName;

    @Option(name = "--execute", usage = "Whether to execute the generated JavaScript")
    private boolean execute = false;

    @Option(name = "--stop_on_error", usage = "Whether to stop fuzzing once an error is found")
    private boolean stopOnError = false;

    private Logger logger;
    private JSONObject config;

    public Result compile(String code) throws IOException {
        Compiler.setLoggingLevel(level.getLevel());
        Compiler compiler = new Compiler();
        return compiler.compile(CommandLineRunner.getDefaultExterns(),
                Arrays.asList(SourceFile.fromCode("[fuzzedCode]", code)), getOptions());
    }

    public Result compile(Node script) throws IOException {
        CompilerInput input = new CompilerInput(new SyntheticAst(script));
        JSModule jsModule = new JSModule("fuzzedModule");
        jsModule.add(input);

        Compiler.setLoggingLevel(level.getLevel());
        Compiler compiler = new Compiler();
        compiler.setTimeout(30);
        compiler.disableThreads();
        return compiler.compileModules(CommandLineRunner.getDefaultExterns(), Arrays.asList(jsModule),
                getOptions());
    }

    private CompilerOptions getOptions() {
        CompilerOptions options = new CompilerOptions();
        compilationLevel.setOptionsForCompilationLevel(options);
        options.variableRenaming = VariableRenamingPolicy.OFF;
        return options;
    }

    private JSONObject getConfig() {
        if (config == null) {
            File file = new File(configFileName);
            try {
                config = new JSONObject(Files.toString(file, StandardCharsets.UTF_8));
            } catch (JSONException | IOException e) {
                e.printStackTrace();
            }
        }
        return config;
    }

    private Logger getLogger() {
        if (logger == null) {
            logger = Logger.getLogger(Driver.class.getName());
            logger.setLevel(level.getLevel());
            for (Handler handler : logger.getHandlers()) {
                handler.setLevel(Level.ALL);
            }
        }
        return logger;
    }

    private Node fuzz(FuzzingContext context) {
        ScriptFuzzer fuzzer = new ScriptFuzzer(context);
        return fuzzer.generate(maxASTSize);
    }

    private boolean executeJS(String js1, String js2) {
        ExecutorService executor = Executors.newCachedThreadPool();
        NodeRunner node1 = new NodeRunner(js1);
        NodeRunner node2 = new NodeRunner(js2);
        String[] output1 = null, output2 = null;
        try {
            // set the timeout to maxASTSize milliseconds
            List<Future<String[]>> futures = executor.invokeAll(Lists.newArrayList(node1, node2), maxASTSize,
                    TimeUnit.MILLISECONDS);

            Future<String[]> future1 = futures.get(0);
            if (!future1.isCancelled()) {
                output1 = future1.get();
            }
            Future<String[]> future2 = futures.get(1);
            if (!future2.isCancelled()) {
                output2 = future2.get();
            }
        } catch (InterruptedException e) {
            getLogger().log(Level.INFO, "Timeout in executing JavaScript", e);
        } catch (ExecutionException e) {
            getLogger().log(Level.SEVERE, "Error in executing JavaScript", e);
        } finally {
            node1.process.destroy();
            node2.process.destroy();
        }
        if (output1 == null && output2 == null) {
            getLogger().info("Infinite loop!");
            return true;
        } else if (NodeRunner.isSame(output1, output2)) {
            boolean hasError = false;
            if (output1 != null && output1[1].length() > 0) {
                getLogger().warning("First JavaScript has a runtime error: " + output1[1]);
                hasError = true;
            }
            if (output2 != null && output2[1].length() > 0) {
                getLogger().warning("Second JavaScript has a runtime error: " + output2[1]);
                hasError = true;
            }
            return !(hasError && getLogger().getLevel().intValue() < Level.WARNING.intValue());
        } else {
            StringBuilder sb = new StringBuilder("Different outputs!");
            sb.append("\nOutput 1:");
            if (output1 != null) {
                sb.append(output1[0]).append(output1[1]);
            } else {
                sb.append("null");
            }
            sb.append("\nOutput 2:");
            if (output2 != null) {
                sb.append(output2[0]).append(output2[1]);
            } else {
                sb.append("null");
            }
            getLogger().severe(sb.toString());
            return false;
        }
    }

    private void run() {
        if (seed != -1) {
            // When user specifies seed, only run once
            numberOfRuns = 1;
        }
        long currentSeed;
        for (int i = 0; numberOfRuns == -1 || i < numberOfRuns; i++) {
            currentSeed = seed == -1 ? System.currentTimeMillis() : seed;
            getLogger().info("Running fuzzer [" + i + " of " + numberOfRuns + "]");
            Random random = currentSeed == -1 ? new Random(currentSeed) : new Random(currentSeed);
            FuzzingContext context = new FuzzingContext(random, getConfig(), execute);
            Node script = null;
            try {
                script = fuzz(context);
            } catch (RuntimeException e) {
                getLogger().log(Level.SEVERE, "Fuzzer error: ", e);
                if (stopOnError) {
                    break;
                } else {
                    continue;
                }
            }
            String code1 = ScriptFuzzer.getPrettyCode(script);
            StringBuilder debugInfo = new StringBuilder("Seed: ").append(currentSeed);
            debugInfo.append("\nJavaScript: ").append(code1);
            try {
                Result result = compile(script);
                if (result.success) {
                    if (result.warnings.length == 0) {
                        getLogger().info(debugInfo.toString());
                    } else {
                        getLogger().warning(debugInfo.toString());
                    }
                } else {
                    getLogger().severe(debugInfo.toString());
                    if (stopOnError) {
                        break;
                    }
                }
            } catch (Exception e) {
                getLogger().log(Level.SEVERE, "Compiler Crashed!", e);
                getLogger().severe(debugInfo.toString());
                if (stopOnError) {
                    break;
                }
            }
            String code2 = ScriptFuzzer.getPrettyCode(script);
            debugInfo.append("\nCompiled Code: " + code2);
            String setUpCode = getSetupCode(context.scopeManager);
            //      System.out.print(setUpCode);
            if (execute) {
                if (!executeJS(setUpCode + code1, setUpCode + code2)) {
                    getLogger().severe(debugInfo.toString());
                    if (stopOnError) {
                        break;
                    }
                }
            }
            getLogger().info(debugInfo.toString());
        }
    }

    private static String getSetupCode(ScopeManager scopeManager) {
        Collection<String> vars = Collections2.transform(Lists.newArrayList(scopeManager.localScope().symbols),
                new Function<Symbol, String>() {
                    @Override
                    public String apply(Symbol s) {
                        return "'" + s.name + "'=" + s.name;
                    }
                });
        String setUpCode = "function toString(value) {\n" + "    if (value instanceof Array) {\n"
                + "        var string = \"[\";\n" + "        for (var i in value) {\n"
                + "            string += toString(value[i]) + \",\";\n" + "        }\n" + "        string += ']';\n"
                + "        return string;\n" + "    } else if (value instanceof Function) {\n"
                + "        return value.length;\n" + "    } else {\n" + "        return value;\n" + "    }\n"
                + "}\n" + "\n" + "process.on('uncaughtException', function(e) {\n"
                + "    console.log(\"Errors: \");\n" + "    if (e instanceof Error) {\n"
                + "        console.log(e.name);\n" + "    } else {\n" + "        console.log(typeof(e));\n"
                + "    }\n" + "});\n" + "\n" + "process.on(\"exit\", function(e) {\n"
                + "    console.log(\"Variables:\");\n" + "    var allvars = " + vars + ";\n"
                + "    console.log(toString(allvars));\n" + "});\n" + "";
        return setUpCode;
    }

    public static void main(String[] args) throws Exception {
        Driver driver = new Driver();
        CmdLineParser parser = new CmdLineParser(driver);
        try {
            parser.parseArgument(args);
        } catch (CmdLineException e) {
            // handling of wrong arguments
            System.err.println(e.getMessage());
            parser.printUsage(System.err);
            System.exit(1);
        }
        driver.run();
        System.exit(0);
    }

    enum LoggingLevel {
        OFF(Level.OFF), SEVERE(Level.SEVERE), WARNING(Level.WARNING), INFO(Level.INFO), CONFIG(Level.CONFIG), FINE(
                Level.FINE), FINER(Level.FINER), FINEST(Level.FINEST), ALL(Level.ALL);

        private Level level;

        private LoggingLevel(Level l) {
            level = l;
        }

        /**
         * @return the level
         */
        public Level getLevel() {
            return level;
        }
    }

    static class NodeRunner implements Callable<String[]> {
        private String js;
        private Process process;

        NodeRunner(String js) {
            this.js = js;
        }

        /* (non-Javadoc)
         * @see java.util.concurrent.Callable#call()
         */
        @Override
        public String[] call() throws IOException {
            String[] command = { "node", "-e", js };
            Runtime runtime = Runtime.getRuntime();
            process = runtime.exec(command);
            String[] results = new String[2];
            results[0] = CharStreams.toString(new InputStreamReader(process.getInputStream()));
            results[1] = CharStreams.toString(new InputStreamReader(process.getErrorStream()));
            return results;
        }

        public static boolean isSame(String[] output1, String[] output2) {
            if (output1 == null && output2 == null) {
                return true;
            } else if (output1 == null || output2 == null) {
                return false;
            } else {
                return output1[0].equals(output2[0]);
            }
        }
    }
}