diff --git a/java-benchmarks b/java-benchmarks index 0a251e328..bfebc81c2 160000 --- a/java-benchmarks +++ b/java-benchmarks @@ -1 +1 @@ -Subproject commit 0a251e328018ecd6d5c77022f849fe131eda403c +Subproject commit bfebc81c258e0e1dc6d29811f10c1a31efabfe67 diff --git a/src/main/java/pascal/taie/AbstractWorldBuilder.java b/src/main/java/pascal/taie/AbstractWorldBuilder.java index 06f04aefe..e31a263c3 100644 --- a/src/main/java/pascal/taie/AbstractWorldBuilder.java +++ b/src/main/java/pascal/taie/AbstractWorldBuilder.java @@ -69,7 +69,7 @@ public abstract class AbstractWorldBuilder implements WorldBuilder { protected static String getClassPath(Options options) { if (options.isPrependJVM()) { - return options.getClassPath(); + return String.join(File.pathSeparator, options.getClassPath()); } else { // when prependJVM is not set, we manually specify JRE jars // check existence of JREs File jreDir = new File(JREs); @@ -86,8 +86,8 @@ protected static String getClassPath(Options options) { try (Stream paths = Files.walk(Path.of(jrePath))) { return Streams.concat( paths.map(Path::toString).filter(p -> p.endsWith(".jar")), - Stream.ofNullable(options.getAppClassPath()), - Stream.ofNullable(options.getClassPath())) + options.getAppClassPath().stream(), + options.getClassPath().stream()) .collect(Collectors.joining(File.pathSeparator)); } catch (IOException e) { throw new RuntimeException("Analysis on Java " + @@ -124,11 +124,8 @@ protected static List getInputClasses(Options options) { } }); // process --app-class-path - String appClassPath = options.getAppClassPath(); - if (appClassPath != null) { - for (String path : appClassPath.split(File.pathSeparator)) { - classes.addAll(ClassNameExtractor.extract(path)); - } + for (String path : options.getAppClassPath()) { + classes.addAll(ClassNameExtractor.extract(path)); } return classes; } diff --git a/src/main/java/pascal/taie/config/Options.java b/src/main/java/pascal/taie/config/Options.java index 2a1a4004d..77c46f3fa 100644 --- a/src/main/java/pascal/taie/config/Options.java +++ b/src/main/java/pascal/taie/config/Options.java @@ -49,9 +49,12 @@ import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; /** * Option class for Tai-e. @@ -95,21 +98,25 @@ public void printHelp() { // ---------- program options ---------- @JsonProperty + @JsonSerialize(contentUsing = FilePathSerializer.class) @Option(names = {"-cp", "--class-path"}, - description = "Class path. Multiple paths are split by system path separator.") - private String classPath; + description = "Class path. Multiple paths are split by system path separator.", + converter = ClassPathConverter.class) + private List classPath = List.of(); - public String getClassPath() { + public List getClassPath() { return classPath; } @JsonProperty + @JsonSerialize(contentUsing = FilePathSerializer.class) @Option(names = {"-acp", "--app-class-path"}, description = "Application class path." + - " Multiple paths are split by system path separator.") - private String appClassPath; + " Multiple paths are split by system path separator.", + converter = ClassPathConverter.class) + private List appClassPath = List.of(); - public String getAppClassPath() { + public List getAppClassPath() { return appClassPath; } @@ -449,6 +456,32 @@ public File deserialize(JsonParser p, DeserializationContext ctxt) } } + /** + * Converter for classpath with system path separator. + */ + private static class ClassPathConverter implements CommandLine.ITypeConverter> { + @Override + public List convert(String value) throws Exception { + return Arrays.stream(value.split(File.pathSeparator)) + .map(String::trim) + .filter(Predicate.not(String::isEmpty)) + .collect(Collectors.toList()); + } + } + + /** + * Serializer for file path. Ensures a path is serialized as a relative path + * from the working directory rather than an absolute path, thus + * preserving the portability of the dumped options file. + */ + private static class FilePathSerializer extends JsonSerializer { + @Override + public void serialize(String value, JsonGenerator gen, + SerializerProvider serializers) throws IOException { + gen.writeString(toSerializedFilePath(value)); + } + } + /** * Convert a file to a relative path using the "/" (forward slash) * from the working directory, thus preserving the portability of diff --git a/src/main/java/pascal/taie/frontend/cache/CachedWorldBuilder.java b/src/main/java/pascal/taie/frontend/cache/CachedWorldBuilder.java index cf9d362b9..bc08d4aa4 100644 --- a/src/main/java/pascal/taie/frontend/cache/CachedWorldBuilder.java +++ b/src/main/java/pascal/taie/frontend/cache/CachedWorldBuilder.java @@ -38,7 +38,6 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; /** @@ -162,14 +161,8 @@ private static int getWorldCacheHash(Options options) { ? options.getWorldBuilderClass().getName().hashCode() : 0); // add the timestamp to the cache key calculation List paths = new ArrayList<>(); - if (options.getClassPath() != null) { - paths.addAll(Arrays.asList(options.getClassPath() - .split(File.pathSeparator))); - } - if (options.getAppClassPath() != null) { - paths.addAll(Arrays.asList(options.getAppClassPath() - .split(File.pathSeparator))); - } + paths.addAll(options.getClassPath()); + paths.addAll(options.getAppClassPath()); for (String path : paths) { File file = new File(path); if (file.exists()) { diff --git a/src/test/java/pascal/taie/config/OptionsTest.java b/src/test/java/pascal/taie/config/OptionsTest.java index 57805f653..2767e5fd6 100644 --- a/src/test/java/pascal/taie/config/OptionsTest.java +++ b/src/test/java/pascal/taie/config/OptionsTest.java @@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test; +import java.io.File; import java.util.List; import java.util.Set; @@ -90,4 +91,17 @@ void testKeepResult() { options = Options.parse("-kr", "pta,def-use"); assertEquals(Set.of("pta", "def-use"), options.getKeepResult()); } + + @Test + void testClasspath() { + Options options = Options.parse( + "-cp", ".\\a.jar", + "-cp", "./dir\\b", + "-cp", "./c" + File.pathSeparator + "d.jar", + "-cp", "e.jar" + File.pathSeparator + ); + assertEquals(List.of(".\\a.jar", "./dir\\b", "./c", "d.jar", "e.jar"), + options.getClassPath()); + } + }