fr.inria.lille.repair.nopol.NoPol.java Source code

Java tutorial

Introduction

Here is the source code for fr.inria.lille.repair.nopol.NoPol.java

Source

/*
 * Copyright (C) 2013 INRIA
 *
 * This software is governed by the CeCILL-C License under French law and
 * abiding by the rules of distribution of free software. You can use, modify
 * and/or redistribute the software under the terms of the CeCILL-C license as
 * circulated by CEA, CNRS and INRIA at http://www.cecill.info.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
 *
 * The fact that you are presently reading this means that you have had
 * knowledge of the CeCILL-C license and that you accept its terms.
 */
package fr.inria.lille.repair.nopol;

import com.gzoltar.core.components.Statement;
import fr.inria.lille.commons.spoon.SpoonedClass;
import fr.inria.lille.commons.spoon.SpoonedProject;
import fr.inria.lille.commons.synthesis.ConstraintBasedSynthesis;
import fr.inria.lille.commons.synthesis.operator.Operator;
import fr.inria.lille.localization.CocoSpoonBasedSpectrumBasedFaultLocalizer;
import fr.inria.lille.localization.DumbFaultLocalizerImpl;
import fr.inria.lille.localization.FaultLocalizer;
import fr.inria.lille.localization.GZoltarFaultLocalizer;
import fr.inria.lille.localization.TestResult;
import fr.inria.lille.localization.metric.Ochiai;
import fr.inria.lille.repair.common.BottomTopURLClassLoader;
import fr.inria.lille.repair.common.config.NopolContext;
import fr.inria.lille.repair.common.finder.TestClassesFinder;
import fr.inria.lille.repair.common.patch.Patch;
import fr.inria.lille.repair.common.synth.RepairType;
import fr.inria.lille.repair.nopol.patch.TestPatch;
import fr.inria.lille.repair.nopol.spoon.NopolProcessor;
import fr.inria.lille.repair.nopol.spoon.NopolProcessorBuilder;
import fr.inria.lille.repair.nopol.spoon.symbolic.AssertReplacer;
import fr.inria.lille.repair.nopol.spoon.symbolic.TestExecutorProcessor;
import fr.inria.lille.repair.nopol.synth.SMTNopolSynthesizer;
import fr.inria.lille.repair.nopol.synth.Synthesizer;
import fr.inria.lille.repair.nopol.synth.SynthesizerFactory;
import org.apache.commons.io.FileUtils;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import spoon.reflect.cu.SourcePosition;
import spoon.reflect.declaration.CtClass;
import spoon.reflect.declaration.CtType;
import xxl.java.compiler.DynamicCompilationException;
import xxl.java.junit.TestCase;
import xxl.java.junit.TestCasesListener;
import xxl.java.junit.TestSuiteExecution;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
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.concurrent.TimeoutException;

/**
 * @author Favio D. DeMarco
 */
public class NoPol {

    private FaultLocalizer localizer;

    public static Statement currentStatement;
    private URL[] classpath;
    private final TestPatch testPatch;
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final SpoonedProject spooner;
    private final File[] sourceFiles;
    private String[] testClasses;
    public long startTime;
    private NopolContext nopolContext;
    private NopolResult nopolResult;

    public NoPol(NopolContext nopolContext) {
        this.startTime = System.currentTimeMillis();
        this.nopolContext = nopolContext;
        this.classpath = nopolContext.getProjectClasspath();
        this.sourceFiles = nopolContext.getProjectSources();
        this.nopolResult = new NopolResult(nopolContext, this.startTime);

        RepairType type = nopolContext.getType();
        logger.info("Source files: " + Arrays.toString(sourceFiles));
        logger.info("Classpath: " + Arrays.toString(classpath));
        logger.info("Statement type: " + type);
        logger.info("Args: " + Arrays.toString(nopolContext.getProjectTests()));
        logger.info("Config: " + nopolContext);
        this.logSystemInformation();

        this.spooner = new SpoonedProject(this.sourceFiles, nopolContext);
        this.testClasses = nopolContext.getProjectTests();
        this.testPatch = new TestPatch(this.sourceFiles[0], this.spooner, nopolContext);
    }

    /**
     * This getter should only be used after an error on build() method (e.g. after a timeout), to get a partial result informations.
     * @return
     */
    public NopolResult getNopolResult() {
        return nopolResult;
    }

    public NopolResult build() {
        if (this.testClasses == null) {
            this.testClasses = new TestClassesFinder().findIn(classpath, false);
        }

        this.localizer = this.createLocalizer();

        nopolResult.setNbTests(this.testClasses.length);
        if (nopolContext.getOracle() == NopolContext.NopolOracle.SYMBOLIC) {
            try {
                SpoonedProject jpfSpoon = new SpoonedProject(this.sourceFiles, nopolContext);
                String mainClass = "nopol.repair.NopolTestRunner";
                TestExecutorProcessor.createMainTestClass(jpfSpoon, mainClass);
                jpfSpoon.process(new AssertReplacer());

                final File outputSourceFile = new File("src-gen");
                final File outputCompiledFile = new File("target-gen");
                // generate the output file
                jpfSpoon.dumpedToClassLoader();
                jpfSpoon.generateOutputFile(outputSourceFile);
                jpfSpoon.generateOutputCompiledFile(outputCompiledFile);
            } catch (IOException e) {
                throw new RuntimeException("Unable to write transformed test", e);
            }
        }
        Map<SourceLocation, List<TestResult>> testListPerStatement = this.localizer.getTestListPerStatement();

        this.nopolResult.setNbStatements(testListPerStatement.keySet().size());
        solveWithMultipleBuild(testListPerStatement);

        this.logResultInfo(this.nopolResult.getPatches());

        this.nopolResult.setDurationInMilliseconds(System.currentTimeMillis() - this.startTime);

        NopolStatus status;
        if (nopolResult.getPatches().size() > 0) {
            status = NopolStatus.PATCH;
        } else {
            if (nopolResult.getNbAngelicValues() == 0) {
                status = NopolStatus.NO_ANGELIC_VALUE;
            } else {
                status = NopolStatus.NO_SYNTHESIS;
            }
        }

        nopolResult.setNopolStatus(status);

        return this.nopolResult;
    }

    private FaultLocalizer createLocalizer() {
        switch (this.nopolContext.getLocalizer()) {
        case GZOLTAR:
            return GZoltarFaultLocalizer.createInstance(this.nopolContext);
        case DUMB:
            return new DumbFaultLocalizerImpl(this.nopolContext);
        case COCOSPOON: // default
            return new CocoSpoonBasedSpectrumBasedFaultLocalizer(this.nopolContext, new Ochiai());
        default:
            return GZoltarFaultLocalizer.createInstance(this.nopolContext);
        }
    }

    /*
     * First algorithm of Nopol,
     * build the initial model
     * apply only one modification
     * build
     * try to find patch
     */
    private void solveWithMultipleBuild(Map<SourceLocation, List<TestResult>> testListPerStatement) {
        int n = 0;
        if (testListPerStatement.size() == 0) {
            logger.debug("OOPS, no statement at all, no test results");
        }
        for (SourceLocation sourceLocation : testListPerStatement.keySet()) {
            n++;
            List<TestResult> tests = testListPerStatement.get(sourceLocation);

            // no failing test case executes this location
            // so there is nothing to repair here
            if (getFailingTestCasesAsList(tests).size() == 0) {
                continue;
            }

            logger.debug("statement #" + n);

            runOnStatement(sourceLocation, tests);
            if (nopolContext.isOnlyOneSynthesisResult() && !this.nopolResult.getPatches().isEmpty()) {
                return;
            }
        }
    }

    private void runOnStatement(SourceLocation sourceLocation, List<TestResult> tests) {
        logger.debug("Analysing {} which is executed by {} tests", sourceLocation, tests.size());
        SpoonedClass spoonCl = spooner.forked(sourceLocation.getRootClassName());
        if (spoonCl == null || spoonCl.getSimpleType() == null) {
            logger.debug("cannot spoon " + sourceLocation.toString());
            return;
        }
        System.out.println(spoonCl.getSimpleType().hashCode());
        NopolProcessorBuilder builder = new NopolProcessorBuilder(spoonCl.getSimpleType().getPosition().getFile(),
                sourceLocation.getLineNumber(), nopolContext);

        // here, we only collect the processors to be applied later
        // this does not change the class itself
        spoonCl.process(builder);

        final List<NopolProcessor> nopolProcessors = builder.getNopolProcessors();
        for (NopolProcessor nopolProcessor : nopolProcessors) {
            logger.debug("looking with " + nopolProcessor.getClass().toString());

            SourcePosition position = nopolProcessor.getTarget().getPosition();
            sourceLocation.setSourceStart(position.getSourceStart());
            sourceLocation.setSourceEnd(position.getSourceEnd());

            List<Patch> patches = executeNopolProcessor(tests, sourceLocation, spoonCl, nopolProcessor);
            this.nopolResult.addPatches(patches);

            if (nopolContext.isOnlyOneSynthesisResult() && !patches.isEmpty()) {
                return;
            }
        }
    }

    /**
     * Method used as proxy for runNopolProcessor to handle timeout
     */
    private List<Patch> executeNopolProcessor(final List<TestResult> tests, final SourceLocation sourceLocation,
            final SpoonedClass spoonCl, final NopolProcessor nopolProcessor) {
        final ExecutorService executor = Executors.newSingleThreadExecutor();
        final Future nopolExecution = executor.submit(new Callable() {
            @Override
            public Object call() throws Exception {
                return runNopolProcessor(tests, sourceLocation, spoonCl, nopolProcessor);
            }
        });
        try {
            executor.shutdown();
            return (List) nopolExecution.get(nopolContext.getMaxTimeEachTypeOfFixInMinutes(), TimeUnit.MINUTES);
        } catch (ExecutionException exception) {
            LoggerFactory.getLogger(this.getClass()).error("Error ExecutionException " + exception.toString());
            return Collections.emptyList();
        } catch (InterruptedException execption) {
            LoggerFactory.getLogger(this.getClass()).error("Repair interrupted");
            return Collections.emptyList();
        } catch (TimeoutException exception) {
            LoggerFactory.getLogger(this.getClass()).error("Timeout: execution time > "
                    + nopolContext.getMaxTimeEachTypeOfFixInMinutes() + " " + TimeUnit.MINUTES, exception);
            return Collections.emptyList();
        }
    }

    private List<Patch> runNopolProcessor(List<TestResult> tests, SourceLocation sourceLocation,
            SpoonedClass spoonCl, NopolProcessor nopolProcessor) {

        String[] failingTestCase = getFailingTestCase(tests);
        if (failingTestCase.length == 0) {
            throw new RuntimeException("failingTestCase: nothing to repair, no failing test cases");
        }

        Collection<TestCase> failingTestCasesValidated = reRunFailingTestCases(failingTestCase, classpath);

        if (failingTestCasesValidated.isEmpty()) {
            throw new RuntimeException("failingTestCasesValidated: nothing to repair, no failing test cases");
        }

        // selecting the synthesizer, typically SMT or Dynamoth
        Synthesizer synth = SynthesizerFactory.build(sourceFiles, spooner, nopolContext, sourceLocation,
                nopolProcessor, spoonCl);

        // Collecting the patches
        List<Patch> tmpPatches = synth.findAngelicValuesAndBuildPatch(classpath, tests, failingTestCasesValidated,
                nopolContext.getMaxTimeBuildPatch(), nopolResult);

        // Final check: we recompile the patch and run all tests again
        List<Patch> finalPatches = new ArrayList<>();
        if (tmpPatches.size() > 0) {
            for (int i = 0; i < tmpPatches.size(); i++) {
                Patch patch = tmpPatches.get(i);
                if (nopolContext.isSkipRegressionStep() || isOk(patch, tests, synth.getProcessor())) {
                    finalPatches.add(patch);
                } else {
                    logger.debug("Could not find a valid patch in {}", sourceLocation);
                }
            }
        }

        return finalPatches;
    }

    private boolean isOk(Patch newRepair, List<TestResult> testClasses, NopolProcessor processor) {
        logger.trace("Suggested patch: {}", newRepair);
        try {
            return testPatch.passesAllTests(newRepair, testClasses, processor);
        } catch (DynamicCompilationException e) {
            logger.error("Patch malformed " + newRepair.asString(), e);
            throw new RuntimeException("invalid patch");
        }
    }

    private List<TestCase> getFailingTestCasesAsList(List<TestResult> tests) {
        List<TestCase> failingClassTest = new ArrayList<>();
        for (int i = 0; i < tests.size(); i++) {
            TestResult testResult = tests.get(i);
            if (!testResult.isSuccessful()) {
                failingClassTest.add(testResult.getTestCase());
            }
        }
        return failingClassTest;
    }

    private String[] getFailingTestCase(List<TestResult> tests) {
        List<TestCase> failingTests = getFailingTestCasesAsList(tests);
        String[] array = new String[failingTests.size()];
        for (int i = 0; i < failingTests.size(); i++) {
            array[i] = failingTests.get(i).className();
        }
        return array;
    }

    private Collection<TestCase> reRunFailingTestCases(String[] testClasses, URL[] deps) {
        TestCasesListener listener = new TestCasesListener();
        TestSuiteExecution.runCasesIn(testClasses,
                new BottomTopURLClassLoader(deps, Thread.currentThread().getContextClassLoader()), listener,
                this.nopolContext);
        return listener.failedTests();
    }

    public SpoonedProject getSpooner() {
        return spooner;
    }

    public FaultLocalizer getLocalizer() {
        return localizer;
    }

    private void logSystemInformation() {
        this.logger.info("Available processors (cores): " + Runtime.getRuntime().availableProcessors());

        /* Total amount of free memory available to the JVM */
        this.logger.info("Free memory: " + FileUtils.byteCountToDisplaySize(Runtime.getRuntime().freeMemory()));

        /* This will return Long.MAX_VALUE if there is no preset limit */
        long maxMemory = Runtime.getRuntime().maxMemory();
        /* Maximum amount of memory the JVM will attempt to use */
        this.logger.info("Maximum memory: "
                + (maxMemory == Long.MAX_VALUE ? "no limit" : FileUtils.byteCountToDisplaySize(maxMemory)));

        /* Total memory currently available to the JVM */
        this.logger.info("Total memory available to JVM: "
                + FileUtils.byteCountToDisplaySize(Runtime.getRuntime().totalMemory()));

        this.logger.info("Java version: " + Runtime.class.getPackage().getImplementationVersion());
        this.logger.info("JAVA_HOME: " + System.getenv("JAVA_HOME"));
        this.logger.info("PATH: " + System.getenv("PATH"));
    }

    private void logResultInfo(List<Patch> patches) {
        long durationTime = System.currentTimeMillis() - this.startTime;
        this.logger.info("----INFORMATION----");
        List<CtType<?>> allClasses = this.getSpooner().spoonFactory().Class().getAll();
        int nbMethod = 0;
        for (int i = 0; i < allClasses.size(); i++) {
            CtType<?> ctSimpleType = allClasses.get(i);
            if (ctSimpleType instanceof CtClass) {
                Set methods = ((CtClass) ctSimpleType).getMethods();
                nbMethod += methods.size();
            }
        }

        this.logger.info("Nb classes : " + allClasses.size());
        this.logger.info("Nb methods : " + nbMethod);
        if (NoPol.currentStatement != null) {
            BitSet coverage = NoPol.currentStatement.getCoverage();
            int countStatementSuccess = 0;
            int countStatementFailed = 0;
            int nextTest = coverage.nextSetBit(0);
            /*while (nextTest != -1) {
               TestResultImpl testResult = nopol.getgZoltar().getGzoltar().getTestResults().get(nextTest);
               if (testResult.wasSuccessful()) {
                  countStatementSuccess += testResult.getCoveredComponents().size();
               } else {
                  countStatementFailed += testResult.getCoveredComponents().size();
               }
               nextTest = coverage.nextSetBit(nextTest + 1);
            }*/

            this.logger.info(
                    "Nb statement executed by the passing tests of the patched line: " + countStatementSuccess);
            this.logger.info(
                    "Nb statement executed by the failing tests of the patched line: " + countStatementFailed);
        }

        this.logger.info("Nb Statements Analyzed : " + SynthesizerFactory.getNbStatementsAnalysed());
        this.logger.info("Nb Statements with Angelic Value Found : "
                + SMTNopolSynthesizer.getNbStatementsWithAngelicValue());
        if (nopolContext.getSynthesis() == NopolContext.NopolSynthesis.SMT) {
            this.logger.info("Nb inputs in SMT : " + SMTNopolSynthesizer.getDataSize());
            this.logger.info("Nb SMT level: " + ConstraintBasedSynthesis.level);
            if (ConstraintBasedSynthesis.operators != null) {
                this.logger.info("Nb SMT components: [" + ConstraintBasedSynthesis.operators.size() + "] "
                        + ConstraintBasedSynthesis.operators);
                Iterator<Operator<?>> iterator = ConstraintBasedSynthesis.operators.iterator();
                Map<Class, Integer> mapType = new HashMap<>();
                while (iterator.hasNext()) {
                    Operator<?> next = iterator.next();
                    if (!mapType.containsKey(next.type())) {
                        mapType.put(next.type(), 1);
                    } else {
                        mapType.put(next.type(), mapType.get(next.type()) + 1);
                    }
                }
                for (Iterator<Class> patchIterator = mapType.keySet().iterator(); patchIterator.hasNext();) {
                    Class next = patchIterator.next();
                    this.logger.info("                  " + next + ": " + mapType.get(next));
                }
            }

            this.logger.info("Nb variables in SMT : " + SMTNopolSynthesizer.getNbVariables());
        }
        //this.logger.info("Nb run failing test  : " + nbFailingTestExecution);
        //this.logger.info("Nb run passing test : " + nbPassedTestExecution);

        this.logger.info("NoPol Execution time : " + durationTime + "ms");
        this.logger.info("".equals(nopolContext.getIdentifier()) ? "" : "  for " + nopolContext.getIdentifier());

        if (patches != null && !patches.isEmpty()) {
            this.logger.info("----PATCH FOUND----");
            for (int i = 0; i < patches.size(); i++) {
                Patch patch = patches.get(i);
                this.logger.info(patch.asString());
                this.logger.info("Nb test that executes the patch: "
                        + this.getLocalizer().getTestListPerStatement().get(patch.getSourceLocation()).size());
                this.logger.info(String.format("%s:%d: %s", patch.getSourceLocation().getContainingClassName(),
                        patch.getLineNumber(), patch.getType()));
                String diffPatch = patch.toDiff(this.getSpooner().spoonFactory(), nopolContext);
                this.logger.info(diffPatch);

                if (nopolContext.getOutputFolder() != null) {
                    File patchLocation = new File(nopolContext.getOutputFolder() + "/patch_" + (i + 1) + ".diff");
                    try {
                        PrintWriter writer = new PrintWriter(patchLocation, "UTF-8");
                        writer.print(diffPatch);
                        writer.close();
                    } catch (IOException e) {
                        System.err.println("Unable to write the patch: " + e.getMessage());
                    }
                }
            }
        }
        if (nopolContext.isJson()) {
            JSONObject output = new JSONObject();

            output.put("nb_classes", allClasses.size());
            output.put("nb_methods", nbMethod);
            output.put("nbStatement", SynthesizerFactory.getNbStatementsAnalysed());
            output.put("nbAngelicValue", SMTNopolSynthesizer.getNbStatementsWithAngelicValue());
            //output.put("nb_failing_test", nbFailingTestExecution);
            //output.put("nb_passing_test", nbPassedTestExecution);
            output.put("executionTime", durationTime);
            output.put("date", new Date());
            if (patches != null) {
                for (int i = 0; i < patches.size(); i++) {
                    Patch patch = patches.get(i);

                    JSONObject patchOutput = new JSONObject();

                    JSONObject locationOutput = new JSONObject();
                    locationOutput.put("class", patch.getSourceLocation().getContainingClassName());
                    locationOutput.put("line", patch.getLineNumber());
                    patchOutput.put("patchLocation", locationOutput);
                    patchOutput.put("patchType", patch.getType());
                    patchOutput.put("nb_test_that_execute_statement",
                            this.getLocalizer().getTestListPerStatement().get(patch.getSourceLocation()).size());
                    patchOutput.put("patch", patch.toDiff(this.getSpooner().spoonFactory(), nopolContext));

                    output.append("patch", patchOutput);
                }
            }

            try (FileWriter writer = new FileWriter(nopolContext.getOutputFolder() + "/output.json")) {
                output.write(writer);
                writer.close();
            } catch (IOException ignore) {
            }
        }
    }
}