Skip to content

Commit

Permalink
Fix #56 by introducing reports cache and reruning analysis at the end…
Browse files Browse the repository at this point in the history
… of the iteration (#68)

#### ~~This PR is build upon GH-62.~~ GH-62 is landed.

This PR resolves #56 by introducing `ReportCache` class and breaking down `explore` method in annotator. With this PR, at the analysis it runs reruns the `inference/injection` phase without exclusion of already processed `fixes`.
  • Loading branch information
nimakarimipour authored Sep 11, 2022
1 parent 742a564 commit cbc7bea
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 53 deletions.
3 changes: 0 additions & 3 deletions core/src/main/java/edu/ucr/cs/riple/core/AnalysisMode.java
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,6 @@ public void tag(Config config, GlobalAnalyzer analyzer, Collection<Report> repor
* @param downStreamDependenciesAnalysisActivated if true, downstream dependency analysis is
* activated.
* @param mode passed mode.
* @param useDefault if true, no value is provided by the user and the default mode will be
* returned. (If downstream dependency analysis is activated default is {@link
* AnalysisMode#LOWER_BOUND}, otherwise the default is {@link AnalysisMode#LOCAL}).
* @return the corresponding {@link AnalysisMode}.
*/
public static AnalysisMode parseMode(
Expand Down
106 changes: 57 additions & 49 deletions core/src/main/java/edu/ucr/cs/riple/core/Annotator.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
import edu.ucr.cs.riple.injector.changes.AddAnnotation;
import edu.ucr.cs.riple.injector.location.OnField;
import edu.ucr.cs.riple.scanner.Serializer;
import java.util.HashMap;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Expand All @@ -61,16 +60,13 @@ public class Annotator {
private final AnnotationInjector injector;
/** Annotator config. */
private final Config config;
/**
* Map of fix to their corresponding reports, used to detect processed fixes in a new batch of fix
* suggestion. Note: Set does not have get method, here we use map which retrieves elements
* efficiently.
*/
public final HashMap<Fix, Report> cachedReports;

/** Reports cache. */
public final ReportCache cache;

public Annotator(Config config) {
this.config = config;
this.cachedReports = new HashMap<>();
this.cache = new ReportCache(config);
this.injector = new PhysicalInjector(config);
}

Expand All @@ -96,7 +92,6 @@ public void start() {
private void preprocess() {
System.out.println("Preprocessing...");
Utility.setScannerCheckerActivation(config.target, true);
this.cachedReports.clear();
System.out.println("Making the first build...");
Utility.buildTarget(config, true);
Set<OnField> uninitializedFields =
Expand Down Expand Up @@ -132,66 +127,79 @@ private void explore() {
? new GlobalAnalyzerImpl(config, tree)
: new NoOpGlobalAnalyzer();
globalAnalyzer.analyzeDownstreamDependencies();
boolean noNewFixTriggered = false;
while (!noNewFixTriggered) {
ImmutableSet<Report> latestReports =
executeNextIteration(globalAnalyzer, fieldDeclarationAnalysis);
int sizeBefore = cachedReports.size();
// Update cached reports store.
latestReports.forEach(
report -> {
if (config.downStreamDependenciesAnalysisActivated) {
report.computeBoundariesOfEffectivenessOnDownstreamDependencies(globalAnalyzer);
}
cachedReports.putIfAbsent(report.root, report);
Report cachedReport = cachedReports.get(report.root);
cachedReport.localEffect = report.localEffect;
cachedReport.finished = report.finished;
cachedReport.tree = report.tree;
cachedReport.triggeredFixes = report.triggeredFixes;
cachedReport.triggeredErrors = report.triggeredErrors;
});

// Tag reports according to selected analysis mode.
config.mode.tag(config, globalAnalyzer, latestReports);

// Inject approved fixes.
Set<Fix> selectedFixes =
latestReports.stream()
.filter(Report::approved)
.flatMap(report -> config.chain ? report.tree.stream() : Stream.of(report.root))
.collect(Collectors.toSet());
injector.injectFixes(selectedFixes);

// Update impact saved state.
globalAnalyzer.updateImpactsAfterInjection(selectedFixes);

// Outer loop starts.
while (cache.isUpdated()) {
executeNextIteration(globalAnalyzer, fieldDeclarationAnalysis);
if (config.disableOuterLoop) {
break;
}
noNewFixTriggered = sizeBefore == this.cachedReports.size();
}

// Perform once last iteration including all fixes.
if (!config.disableOuterLoop) {
cache.disable();
executeNextIteration(globalAnalyzer, fieldDeclarationAnalysis);
cache.enable();
}

System.out.println("\nFinished annotating.");
Utility.writeReports(
config, cachedReports.values().stream().collect(ImmutableSet.toImmutableSet()));
Utility.writeReports(config, cache.reports().stream().collect(ImmutableSet.toImmutableSet()));
}

/**
* Performs single iteration of inference/injection.
*
* @param globalAnalyzer Global analyzer instance to detect impact of fixes outside of target
* module.
* @param fieldDeclarationAnalysis Field declaration instance to detect fixes targeting inline
* multiple field declaration statements.
*/
private void executeNextIteration(
GlobalAnalyzer globalAnalyzer, FieldDeclarationAnalysis fieldDeclarationAnalysis) {
ImmutableSet<Report> latestReports =
processTriggeredFixes(globalAnalyzer, fieldDeclarationAnalysis);
// Compute boundaries of effects on downstream dependencies.
latestReports.forEach(
report -> {
if (config.downStreamDependenciesAnalysisActivated) {
report.computeBoundariesOfEffectivenessOnDownstreamDependencies(globalAnalyzer);
}
});
// Update cached reports store.
cache.update(latestReports);

// Tag reports according to selected analysis mode.
config.mode.tag(config, globalAnalyzer, latestReports);

// Inject approved fixes.
Set<Fix> selectedFixes =
latestReports.stream()
.filter(Report::approved)
.flatMap(report -> config.chain ? report.tree.stream() : Stream.of(report.root))
.collect(Collectors.toSet());
injector.injectFixes(selectedFixes);

// Update impact saved state.
globalAnalyzer.updateImpactsAfterInjection(selectedFixes);
}

/**
* Executes the next iteration of exploration.
* Processes triggered fixes.
*
* @param globalAnalyzer Global Analyzer instance.
* @param fieldDeclarationAnalysis Field Declaration analysis to detect fixes on multiple inline
* field declaration statements.
* @return Immutable set of reports from the executed iteration.
* @return Immutable set of reports from the triggered fixes.
*/
private ImmutableSet<Report> executeNextIteration(
private ImmutableSet<Report> processTriggeredFixes(
GlobalAnalyzer globalAnalyzer, FieldDeclarationAnalysis fieldDeclarationAnalysis) {
Utility.buildTarget(config);
// Suggested fixes of target at the current state.
ImmutableSet<Fix> fixes =
Utility.readFixesFromOutputDirectory(
config.target, Fix.factory(config, fieldDeclarationAnalysis))
.filter(fix -> !config.useCache || !cachedReports.containsKey(fix))
.filter(fix -> !cache.processedFix(fix))
.collect(ImmutableSet.toImmutableSet());

// Initializing required explorer instances.
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/java/edu/ucr/cs/riple/core/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ public Config(String[] args) {
AnalysisMode.parseMode(
this.downStreamDependenciesAnalysisActivated,
cmd.getOptionValue(analysisMode, "default"));

if (this.downStreamDependenciesAnalysisActivated) {
moduleInfoList.remove(0);
this.downstreamInfo = ImmutableSet.copyOf(moduleInfoList);
Expand Down Expand Up @@ -432,6 +433,7 @@ public Config(Path configPath) {
getValueFromKey(
jsonObject, "DOWNSTREAM_DEPENDENCY_ANALYSIS:ANALYSIS_MODE", String.class)
.orElse("default"));

this.downstreamInfo = ImmutableSet.copyOf(moduleInfoList);
this.moduleCounterID = 0;
this.log = new Log();
Expand Down
109 changes: 109 additions & 0 deletions core/src/main/java/edu/ucr/cs/riple/core/ReportCache.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* MIT License
*
* Copyright (c) 2022 Nima Karimipour
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package edu.ucr.cs.riple.core;

import com.google.common.collect.ImmutableSet;
import edu.ucr.cs.riple.core.metadata.index.Fix;
import java.util.HashMap;
import java.util.Map;

/** Reports cache. Used to detect fixes that has already been processed. */
public class ReportCache {

/**
* Map of fix to their corresponding reports, used to detect processed fixes in a new batch of fix
* suggestion. Note: Set does not have get method, here we use map which retrieves elements
* efficiently.
*/
private final Map<Fix, Report> store;
/** Cache activation switch. */
private boolean enabled;

/**
* If true, the content of cache has been updated when {@link ReportCache#update(ImmutableSet)}
* method is called. Used to determine if annotator should perform another iteration of analysis.
*/
private boolean stateUpdated;

public ReportCache(Config config) {
this.store = new HashMap<>();
this.enabled = config.useCache;
this.stateUpdated = true;
}

/**
* Checks if the corresponding report is stored.
*
* @param fix Fix instance.
* @return true if the fix is already processed.
*/
public boolean processedFix(Fix fix) {
if (!enabled) {
return false;
}
return store.containsKey(fix);
}

/**
* Updates the state of cache with the latest processed reports.
*
* @param reports Set of the latest processed reports.
*/
public void update(ImmutableSet<Report> reports) {
int size = store.keySet().size();
reports.forEach(report -> store.put(report.root, report));
if (size == store.keySet().size()) {
stateUpdated = false;
}
}

/**
* Returns true if the content of cache is updated after calling update method.
*
* @return true if the content has been updated.
*/
public boolean isUpdated() {
return stateUpdated;
}

/** Enables cache. */
public void enable() {
this.enabled = true;
}

/** Disabled cache. */
public void disable() {
this.enabled = false;
}

/**
* Getter for all stored reports.
*
* @return Immutable set of stored reports.
*/
public ImmutableSet<Report> reports() {
return ImmutableSet.copyOf(store.values());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ public void start() {
expected.root.change.location.equals(found.root.change.location)
&& expected.getExpectedValue() == found.getOverallEffect(config);
}
compare(annotator.cachedReports.values());
compare(new ArrayList<>(annotator.cache.reports()));
}

/** Checks if all src inputs are subpackages of test package. */
Expand Down

0 comments on commit cbc7bea

Please sign in to comment.