Skip to content

Commit 821eabd

Browse files
committed
Move the addition of reflection log classes to Options.postProcess
This commit involves 3 changes: 1. The type of `Options.analyses` is changed from `Map<String, String>` to `Map<String, AnalysisOptions>`. This means the parsing logic of `AnalysisOptions` is moved from `PlanConfig` to `Options` via picocli converters. 2. The logic of adding classes in the reflection log is moved from `SootWorldBuilder` to `Options.postProcess`. This leaves world builders now unaware of reflection and analyses. 3. `WorldBuilder.build` does not need its `analyses` argument anymore, since `AnalysisOptions` can be now accessed via `Options`. This simplifies the interface of all world builders.
1 parent 5dc8ae3 commit 821eabd

6 files changed

Lines changed: 97 additions & 115 deletions

File tree

src/main/java/pascal/taie/Main.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public static void main(String... args) {
5858
logger.info("No analyses are specified");
5959
System.exit(0);
6060
}
61-
buildWorld(options, plan.analyses());
61+
buildWorld(options);
6262
executePlan(plan);
6363
}, "Tai-e");
6464
LoggerConfigs.reconfigure();
@@ -117,12 +117,11 @@ private static Plan processConfigs(Options options) {
117117
public static void buildWorld(String... args) {
118118
Options options = Options.parse(args);
119119
LoggerConfigs.setOutput(options.getOutputDir());
120-
Plan plan = processConfigs(options);
121-
buildWorld(options, plan.analyses());
120+
buildWorld(options);
122121
LoggerConfigs.reconfigure();
123122
}
124123

125-
private static void buildWorld(Options options, List<AnalysisConfig> analyses) {
124+
private static void buildWorld(Options options) {
126125
Monitor.runAndCount(() -> {
127126
try {
128127
Class<? extends WorldBuilder> builderClass = options.getWorldBuilderClass();
@@ -131,7 +130,7 @@ private static void buildWorld(Options options, List<AnalysisConfig> analyses) {
131130
if (options.isWorldCacheMode()) {
132131
builder = new CachedWorldBuilder(builder);
133132
}
134-
builder.build(options, analyses);
133+
builder.build(options);
135134
logger.info("{} classes with {} methods in the world",
136135
World.get()
137136
.getClassHierarchy()
@@ -143,7 +142,7 @@ private static void buildWorld(Options options, List<AnalysisConfig> analyses) {
143142
.mapToInt(c -> c.getDeclaredMethods().size())
144143
.sum());
145144
} catch (InstantiationException | IllegalAccessException |
146-
NoSuchMethodException | InvocationTargetException e) {
145+
NoSuchMethodException | InvocationTargetException e) {
147146
System.err.println("Failed to build world due to " + e);
148147
System.exit(1);
149148
}

src/main/java/pascal/taie/WorldBuilder.java

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,8 @@
2222

2323
package pascal.taie;
2424

25-
import pascal.taie.config.AnalysisConfig;
2625
import pascal.taie.config.Options;
2726

28-
import java.util.List;
29-
3027
/**
3128
* Interface for {@link World} builder.
3229
*/
@@ -35,10 +32,8 @@ public interface WorldBuilder {
3532
/**
3633
* Builds a new instance of {@link World} and make it globally accessible
3734
* through static methods of {@link World}.
38-
* TODO: remove {@code analyses}.
3935
*
40-
* @param options the options
41-
* @param analyses the analyses to be executed
36+
* @param options the options
4237
*/
43-
void build(Options options, List<AnalysisConfig> analyses);
38+
void build(Options options);
4439
}

src/main/java/pascal/taie/config/Options.java

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
import com.fasterxml.jackson.annotation.JsonProperty;
2626
import com.fasterxml.jackson.core.JsonGenerator;
2727
import com.fasterxml.jackson.core.JsonParser;
28+
import com.fasterxml.jackson.core.JsonProcessingException;
2829
import com.fasterxml.jackson.databind.DeserializationContext;
30+
import com.fasterxml.jackson.databind.JavaType;
2931
import com.fasterxml.jackson.databind.JsonDeserializer;
3032
import com.fasterxml.jackson.databind.JsonSerializer;
3133
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -37,6 +39,9 @@
3739
import org.apache.logging.log4j.LogManager;
3840
import org.apache.logging.log4j.Logger;
3941
import pascal.taie.WorldBuilder;
42+
import pascal.taie.analysis.pta.PointerAnalysis;
43+
import pascal.taie.analysis.pta.plugin.reflection.LogItem;
44+
import pascal.taie.language.classes.StringReps;
4045
import picocli.CommandLine;
4146
import picocli.CommandLine.Command;
4247
import picocli.CommandLine.Option;
@@ -48,10 +53,12 @@
4853
import java.time.Instant;
4954
import java.time.ZoneId;
5055
import java.time.format.DateTimeFormatter;
56+
import java.util.ArrayList;
5157
import java.util.Arrays;
5258
import java.util.List;
5359
import java.util.Map;
5460
import java.util.Set;
61+
import java.util.StringJoiner;
5562
import java.util.function.Predicate;
5663

5764
/**
@@ -261,9 +268,9 @@ public File getPlanFile() {
261268
description = "Analyses to be executed",
262269
paramLabel = "<analysisID[=<options>]>",
263270
mapFallbackValue = "")
264-
private Map<String, String> analyses = Map.of();
271+
private Map<String, AnalysisOptions> analyses = Map.of();
265272

266-
public Map<String, String> getAnalyses() {
273+
public Map<String, AnalysisOptions> getAnalyses() {
267274
return analyses;
268275
}
269276

@@ -293,7 +300,9 @@ public Set<String> getKeepResult() {
293300
* Parses arguments and return the parsed and post-processed Options.
294301
*/
295302
public static Options parse(String... args) {
296-
Options options = CommandLine.populateCommand(new Options(), args);
303+
CommandLine commandLine = new CommandLine(new Options())
304+
.registerConverter(AnalysisOptions.class, new AnalysisOptionsConverter());
305+
Options options = (Options) commandLine.parseArgs(args).commandSpec().userObject();
297306
return postProcess(options);
298307
}
299308

@@ -328,6 +337,7 @@ private static Options postProcess(Options options) {
328337
"at least one of --main-class, --input-classes " +
329338
"or --app-class-path should be specified");
330339
}
340+
options.addReflectionLogClasses();
331341
// mkdir for output dir
332342
if (!options.outputDir.exists()) {
333343
options.outputDir.mkdirs();
@@ -499,6 +509,72 @@ private static String toSerializedFilePath(String file) {
499509
.replace('\\', '/');
500510
}
501511

512+
private static class AnalysisOptionsConverter implements CommandLine.ITypeConverter<AnalysisOptions> {
513+
@Override
514+
public AnalysisOptions convert(String value) {
515+
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
516+
JavaType mapType = mapper.getTypeFactory()
517+
.constructMapType(Map.class, String.class, Object.class);
518+
String optStr = toYAMLString(value);
519+
try {
520+
Map<String, Object> optsMap = optStr.isBlank()
521+
? Map.of()
522+
// Leverage Jackson to parse YAML string to Map
523+
: mapper.readValue(optStr, mapType);
524+
return new AnalysisOptions(optsMap);
525+
} catch (JsonProcessingException e) {
526+
throw new ConfigException("Invalid analysis options: " + value, e);
527+
}
528+
}
529+
530+
/**
531+
* Converts option string to a valid YAML string.
532+
* The option string is of format "key1:value1;key2:value2;...".
533+
*/
534+
private static String toYAMLString(String optValue) {
535+
StringJoiner joiner = new StringJoiner("\n");
536+
for (String keyValue : optValue.split(";")) {
537+
if (!keyValue.isBlank()) {
538+
int i = keyValue.indexOf(':'); // split keyValue
539+
joiner.add(keyValue.substring(0, i) + ": "
540+
+ keyValue.substring(i + 1));
541+
}
542+
}
543+
return joiner.toString();
544+
}
545+
}
546+
547+
/**
548+
* Add classes in reflection log to the input classes.
549+
* <p>
550+
* TODO: this is still a tentative solution.
551+
*/
552+
private void addReflectionLogClasses() {
553+
List<String> inputClasses = new ArrayList<>(this.inputClasses);
554+
AnalysisOptions analysisOptions = analyses.get(PointerAnalysis.ID);
555+
if (analysisOptions == null || !analysisOptions.has("reflection-log")) {
556+
return;
557+
}
558+
String path = analysisOptions.getString("reflection-log");
559+
if (path != null) {
560+
LogItem.load(path).forEach(item -> {
561+
// add target class
562+
String target = item.target;
563+
String targetClass;
564+
if (target.startsWith("<")) {
565+
targetClass = StringReps.getClassNameOf(target);
566+
} else {
567+
targetClass = target;
568+
}
569+
if (StringReps.isArrayType(targetClass)) {
570+
targetClass = StringReps.getBaseTypeNameOf(target);
571+
}
572+
inputClasses.add(targetClass);
573+
});
574+
}
575+
this.inputClasses = List.copyOf(inputClasses);
576+
}
577+
502578
@Override
503579
public String toString() {
504580
return "Options{" +

src/main/java/pascal/taie/config/PlanConfig.java

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424

2525
import com.fasterxml.jackson.annotation.JsonCreator;
2626
import com.fasterxml.jackson.annotation.JsonProperty;
27-
import com.fasterxml.jackson.core.JsonProcessingException;
2827
import com.fasterxml.jackson.databind.JavaType;
2928
import com.fasterxml.jackson.databind.ObjectMapper;
3029
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
@@ -35,9 +34,7 @@
3534
import java.io.File;
3635
import java.io.IOException;
3736
import java.util.List;
38-
import java.util.Map;
3937
import java.util.Objects;
40-
import java.util.StringJoiner;
4138

4239
/**
4340
* Configuration for an analysis to be executed.
@@ -108,48 +105,16 @@ public static List<PlanConfig> readConfigs(File file) {
108105
* Reads a list of PlanConfig from options.
109106
*/
110107
public static List<PlanConfig> readConfigs(Options options) {
111-
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
112-
JavaType mapType = mapper.getTypeFactory()
113-
.constructMapType(Map.class, String.class, Object.class);
114108
return options.getAnalyses().entrySet()
115109
.stream()
116110
.map(entry -> {
117111
String id = entry.getKey();
118-
String optStr = toYAMLString(entry.getValue());
119-
try {
120-
Map<String, Object> optsMap = optStr.isBlank()
121-
? Map.of()
122-
// Leverage Jackson to parse YAML string to Map
123-
: mapper.readValue(optStr, mapType);
124-
return new PlanConfig(id, new AnalysisOptions(optsMap));
125-
} catch (JsonProcessingException e) {
126-
throw new ConfigException("Invalid analysis options: " +
127-
entry.getKey() + ":" + entry.getValue(), e);
128-
}
112+
AnalysisOptions analysisOptions = entry.getValue();
113+
return new PlanConfig(id, analysisOptions);
129114
})
130115
.toList();
131116
}
132117

133-
/**
134-
* Converts option string to a valid YAML string.
135-
* The option string is of format "key1:value1;key2:value2;...".
136-
*/
137-
private static String toYAMLString(String optValue) {
138-
StringJoiner joiner = new StringJoiner("\n");
139-
for (String keyValue : optValue.split(";")) {
140-
if (!keyValue.isBlank()) {
141-
int i = keyValue.indexOf(':'); // split keyValue
142-
if (i == -1) {
143-
throw new IllegalArgumentException("Invalid argument format '" + keyValue
144-
+ "'. Expected format: 'key:value'");
145-
}
146-
joiner.add(keyValue.substring(0, i) + ": "
147-
+ keyValue.substring(i + 1));
148-
}
149-
}
150-
return joiner.toString();
151-
}
152-
153118
/**
154119
* Writes a list of PlanConfigs to given file.
155120
*/

src/main/java/pascal/taie/frontend/cache/CachedWorldBuilder.java

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import org.apache.logging.log4j.Logger;
2727
import pascal.taie.World;
2828
import pascal.taie.WorldBuilder;
29-
import pascal.taie.config.AnalysisConfig;
3029
import pascal.taie.config.Options;
3130
import pascal.taie.util.Monitor;
3231

@@ -69,12 +68,12 @@ public CachedWorldBuilder(WorldBuilder delegate) {
6968
}
7069

7170
@Override
72-
public void build(Options options, List<AnalysisConfig> analyses) {
71+
public void build(Options options) {
7372
File worldCacheFile = getWorldCacheFile(options);
7473
if (loadCache(options, worldCacheFile)) {
7574
return;
7675
}
77-
runWorldBuilder(options, analyses);
76+
runWorldBuilder(options);
7877
saveCache(worldCacheFile);
7978
}
8079

@@ -86,10 +85,8 @@ private boolean loadCache(Options options, File worldCacheFile) {
8685
logger.info("Loading the world cache from {}", worldCacheFile);
8786
Monitor monitor = new Monitor("Load the world cache");
8887
monitor.start();
89-
ObjectInputStream ois = null;
90-
try {
91-
ois = new ObjectInputStream(
92-
new BufferedInputStream(new FileInputStream(worldCacheFile)));
88+
try (ObjectInputStream ois = new ObjectInputStream(
89+
new BufferedInputStream(new FileInputStream(worldCacheFile)))) {
9390
World world = (World) ois.readObject();
9491
World.set(world);
9592
world.setOptions(options);
@@ -98,24 +95,17 @@ private boolean loadCache(Options options, File worldCacheFile) {
9895
logger.error("Failed to load world cache from {} due to {}",
9996
worldCacheFile, e);
10097
} finally {
101-
if (ois != null) {
102-
try {
103-
ois.close();
104-
} catch (Exception e) {
105-
logger.error("Failed to close input stream", e);
106-
}
107-
}
10898
monitor.stop();
10999
logger.info(monitor);
110100
}
111101
return false;
112102
}
113103

114-
private void runWorldBuilder(Options options, List<AnalysisConfig> analyses) {
104+
private void runWorldBuilder(Options options) {
115105
logger.info("Running the WorldBuilder ...");
116106
Monitor monitor = new Monitor("Run the WorldBuilder");
117107
monitor.start();
118-
delegate.build(options, analyses);
108+
delegate.build(options);
119109
monitor.stop();
120110
logger.info(monitor);
121111
}

0 commit comments

Comments
 (0)