Skip to content

Commit

Permalink
Fix #62 by adding configuration mode for downstream dependency analys…
Browse files Browse the repository at this point in the history
…is. (#63)

This Resolves #62 by adding a configuration mode `Strict`.

With this PR, we can now configure the analysis mode which will impact inference decisions.

We have four different modes of analysis:
1. `Local`: Only considers the local effect of fixes.
2. `Lower Bound`: Considers the lower bounds of number of errors on downstream dependencies.
3. `Upper Bound`: Considers the upper bound of number of errors on downstream dependencies.
4.  `Strict`: Guarantees that all errors in downstream due to changes in upstream will be resolved.

To set the above modes via command line, pass `-am` or `--analysis-mode` followed by one of the options below:
- `default`
- `upper_bound`
- `lower_bound`
- `strict`

Or set one of the values above In the `.json` config file at:
```json
"DOWNSTREAM_DEPENDENCY_ANALYSIS":{
     "ANALYSIS_MODE": "mode"
}
```
  • Loading branch information
nimakarimipour authored Sep 9, 2022
1 parent c824d26 commit 742a564
Show file tree
Hide file tree
Showing 21 changed files with 859 additions and 10 deletions.
157 changes: 157 additions & 0 deletions core/src/main/java/edu/ucr/cs/riple/core/AnalysisMode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
* 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 static edu.ucr.cs.riple.core.Report.Tag.APPROVE;
import static edu.ucr.cs.riple.core.Report.Tag.REJECT;

import com.google.common.base.Preconditions;
import edu.ucr.cs.riple.core.global.GlobalAnalyzer;
import java.util.Collection;

/** Analysis mode in making inference decisions. */
public enum AnalysisMode {
/**
* Only effects in target module is considered. Default mode if downstream dependencies analysis
* is not activated.
*/
LOCAL {
@Override
public void tag(Config config, GlobalAnalyzer analyzer, Collection<Report> reports) {
reports.forEach(
report -> {
if (report.localEffect < 1) {
report.tag(APPROVE);
} else {
report.tag(REJECT);
}
});
}
},

/**
* Guarantees that no error will happen on downstream dependencies in the result of changes in
* upstream.
*/
STRICT {
@Override
public void tag(Config config, GlobalAnalyzer analyzer, Collection<Report> reports) {
reports.forEach(
report -> {
// Check for destructive methods.
if (report.containsDestructiveMethod(analyzer)) {
report.tag(REJECT);
return;
}
// Just a sanity check.
Preconditions.checkArgument(report.getUpperBoundEffectOnDownstreamDependencies() == 0);
// Apply if effect is less than 1.
if (report.localEffect < 1) {
report.tag(APPROVE);
return;
}
// Discard.
report.tag(REJECT);
});
}
},

/**
* Experimental: Upper bound of number of errors on downstream dependencies will be considered.
*/
UPPER_BOUND {
@Override
public void tag(Config config, GlobalAnalyzer analyzer, Collection<Report> reports) {
reports.forEach(
report -> {
if (report.localEffect + report.getUpperBoundEffectOnDownstreamDependencies() < 1) {
report.tag(APPROVE);
} else {
report.tag(REJECT);
}
});
}
},

/**
* Lower bound of number of errors on downstream dependencies will be considered. Default mode if
* downstream dependencies analysis is enabled.
*/
LOWER_BOUND {
@Override
public void tag(Config config, GlobalAnalyzer analyzer, Collection<Report> reports) {
reports.forEach(
report -> {
if (report.localEffect + report.getLowerBoundEffectOnDownstreamDependencies() < 1) {
report.tag(APPROVE);
} else {
report.tag(REJECT);
}
});
}
};

/**
* Tags reports based on the analysis mode.
*
* @param config Annotator config.
* @param analyzer Downstream dependency instance.
* @param reports Reports to be processed.
*/
public abstract void tag(Config config, GlobalAnalyzer analyzer, Collection<Report> reports);

/**
* Parses the received option and returns the corresponding {@link AnalysisMode}. Can only be one
* of [default|upper_bound|lower_bound|strict] values.
*
* @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(
boolean downStreamDependenciesAnalysisActivated, String mode) {
if (!downStreamDependenciesAnalysisActivated) {
return LOCAL;
}
mode = mode.toLowerCase();
if (mode.equals("lower_bound") || mode.equals("default")) {
return LOWER_BOUND;
}
if (mode.equals("upper_bound")) {
return UPPER_BOUND;
}
if (mode.equals("strict")) {
return STRICT;
}
throw new IllegalArgumentException(
"Unrecognized mode request: "
+ mode
+ " .Can only be [default|upper_bound|lower_bound|strict].");
}
}
5 changes: 4 additions & 1 deletion core/src/main/java/edu/ucr/cs/riple/core/Annotator.java
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,13 @@ private void explore() {
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 -> report.getOverallEffect(config) < 1)
.filter(Report::approved)
.flatMap(report -> config.chain ? report.tree.stream() : Stream.of(report.root))
.collect(Collectors.toSet());
injector.injectFixes(selectedFixes);
Expand Down
29 changes: 29 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 @@ -110,6 +110,11 @@ public class Config {
public final Path nullawayLibraryModelLoaderPath;
/** Command to build the all downstream dependencies at once. */
public final String downstreamDependenciesBuildCommand;
/**
* Analysis mode. Will impact inference decisions when downstream dependency analysis is
* activated.
*/
public final AnalysisMode mode;
/** Global counter for assigning unique id for each instance. */
public int moduleCounterID;

Expand Down Expand Up @@ -260,6 +265,15 @@ public Config(String[] args) {
"NullAway Library Model loader path");
nullawayLibraryModelLoaderPathOption.setRequired(false);
options.addOption(nullawayLibraryModelLoaderPathOption);
// Down stream analysis: Analysis mode.
Option analysisMode =
new Option(
"am",
"analysis-mode",
true,
"Analysis mode. Can be [default|upper_bound|lower_bound|strict]");
analysisMode.setRequired(false);
options.addOption(analysisMode);

HelpFormatter formatter = new HelpFormatter();
CommandLineParser parser = new DefaultParser();
Expand Down Expand Up @@ -330,6 +344,10 @@ public Config(String[] args) {
this.exhaustiveSearch = cmd.hasOption(exhaustiveSearchOption.getLongOpt());
this.downStreamDependenciesAnalysisActivated =
cmd.hasOption(downstreamDependenciesActivationOption.getLongOpt());
this.mode =
AnalysisMode.parseMode(
this.downStreamDependenciesAnalysisActivated,
cmd.getOptionValue(analysisMode, "default"));
if (this.downStreamDependenciesAnalysisActivated) {
moduleInfoList.remove(0);
this.downstreamInfo = ImmutableSet.copyOf(moduleInfoList);
Expand Down Expand Up @@ -408,6 +426,12 @@ public Config(Path configPath) {
? null
: Paths.get(nullawayLibraryModelLoaderPathString);
moduleInfoList.remove(0);
this.mode =
AnalysisMode.parseMode(
this.downStreamDependenciesAnalysisActivated,
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 Expand Up @@ -522,6 +546,7 @@ public static class Builder {
public boolean outerLoopActivation = true;
public boolean downStreamDependenciesAnalysisActivated = false;
public Path nullawayLibraryModelLoaderPath;
public AnalysisMode mode = AnalysisMode.LOCAL;
public String downstreamBuildCommand;
public int depth = 1;

Expand Down Expand Up @@ -570,10 +595,14 @@ public void write(Path path) {
Preconditions.checkNotNull(
nullawayLibraryModelLoaderPath,
"nullawayLibraryModelLoaderPath cannot be null to enable down stream dependency analysis.");
Preconditions.checkArgument(
!mode.equals(AnalysisMode.LOCAL),
"Cannot perform downstream dependencies analysis with mode: \"Local\", use one of [default|lower_bound|upper_bound].");
downstreamDependency.put(
"LIBRARY_MODEL_LOADER_PATH", nullawayLibraryModelLoaderPath.toString());
Preconditions.checkNotNull(downstreamBuildCommand);
downstreamDependency.put("BUILD_COMMAND", downstreamBuildCommand);
downstreamDependency.put("ANALYSIS_MODE", mode.name());
}
json.put("DOWNSTREAM_DEPENDENCY_ANALYSIS", downstreamDependency);
try (BufferedWriter file =
Expand Down
66 changes: 60 additions & 6 deletions core/src/main/java/edu/ucr/cs/riple/core/Report.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,17 @@ public class Report {
*/
private int upperBoundEffectOnDownstreamDependencies;

/** Denotes the final decision regarding the injection of the report. */
public enum Tag {
/** If tagged with this tag, report tree will be injected. */
APPROVE,
/** If tagged with this tag, report tree will not be injected and will be discarded. */
REJECT,
}

/** Status of the report. */
private Tag tag;

public Report(Fix root, int localEffect) {
this.localEffect = localEffect;
this.root = root;
Expand All @@ -77,6 +88,46 @@ public Report(Fix root, int localEffect) {
this.triggeredErrors = ImmutableList.of();
this.lowerBoundEffectOnDownstreamDependencies = 0;
this.upperBoundEffectOnDownstreamDependencies = 0;
this.tag = Tag.REJECT;
}

/**
* Checks if any of the fix in tree, will trigger an unresolvable error in downstream
* dependencies.
*
* @param analyzer Analyzer to check impact of method.
* @return true, if report contains a fix which will trigger an unresolvable error in downstream
* dependency.
*/
public boolean containsDestructiveMethod(GlobalAnalyzer analyzer) {
return this.tree.stream().anyMatch(analyzer::isNotFixableOnTarget);
}

/**
* Setter for tag.
*
* @param tag tag value.
*/
public void tag(Tag tag) {
this.tag = tag;
}

/**
* Getter for tag.
*
* @return Reports tag.
*/
public Tag getTag() {
return this.tag;
}

/**
* Checks if report's fix tree is approved by analysis and should be applied.
*
* @return true, if fix tree should be applied and false otherwise.
*/
public boolean approved() {
return this.tag.equals(Tag.APPROVE);
}

@Override
Expand Down Expand Up @@ -150,18 +201,21 @@ public void computeBoundariesOfEffectivenessOnDownstreamDependencies(GlobalAnaly
}

/**
* Returns the overall effect of applying fix tree associated to this report. If downstream
* dependency analysis is activated, overall effect will be sum of local effect and lower bound of
* number of errors on downstream dependencies.
* Returns the overall effect of applying fix tree associated to this report according to {@link
* AnalysisMode}.
*
* @param config Annotator config.
* @return Overall effect ot applying the fix tree.
*/
public int getOverallEffect(Config config) {
if (config.downStreamDependenciesAnalysisActivated) {
return this.localEffect + this.lowerBoundEffectOnDownstreamDependencies;
AnalysisMode mode = config.mode;
if (mode.equals(AnalysisMode.LOCAL)) {
return this.localEffect;
}
if (mode.equals(AnalysisMode.UPPER_BOUND)) {
return this.localEffect + this.upperBoundEffectOnDownstreamDependencies;
}
return this.localEffect;
return this.localEffect + this.lowerBoundEffectOnDownstreamDependencies;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,14 @@ public interface GlobalAnalyzer {
* @param fixes Set of injected fixes.
*/
void updateImpactsAfterInjection(Set<Fix> fixes);

/**
* Checks if fix triggers any unresolvable error in downstream dependencies. Unresolvable errors
* are errors that either no annotation can resolve them, or the corresponding fix is targeting an
* element outside the target module.
*
* @param fix Fix to apply
* @return true if the method triggers an unresolvable error in downstream dependencies.
*/
boolean isNotFixableOnTarget(Fix fix);
}
Original file line number Diff line number Diff line change
Expand Up @@ -212,4 +212,14 @@ public List<Error> getTriggeredErrors(Fix fix) {
public void updateImpactsAfterInjection(Set<Fix> fixes) {
this.methods.values().forEach(methodImpact -> methodImpact.updateStatus(fixes));
}

@Override
public boolean isNotFixableOnTarget(Fix fix) {
// For unresolvable errors, nonnullTarget is initialized with location instance which all fields
// are initialized to "null" string value. declaredInModule method in methodDeclarationTree
// will return false for these locations. Hence, both the existence of fix and fix targeting an
// element in target module is covered.
return getTriggeredErrors(fix).stream()
.anyMatch(error -> !tree.declaredInModule(error.nonnullTarget));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public MethodImpact(MethodNode node) {
this.node = node;
this.effect = 0;
this.impactedParametersMap = new HashMap<>();
this.triggeredErrors = new ArrayList<>();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,9 @@ public List<Error> getTriggeredErrors(Fix fix) {
public void updateImpactsAfterInjection(Set<Fix> fixes) {
// No operation needed.
}

@Override
public boolean isNotFixableOnTarget(Fix fix) {
return false;
}
}
Loading

0 comments on commit 742a564

Please sign in to comment.