From 976bdbf762134930aeca731b60e18b6ad52c678c Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Wed, 5 Jul 2023 16:29:37 -0700 Subject: [PATCH] Add ability to only analyze a subset of standard lib from running JVM (#1286) --- .../core/util/config/AnalysisScopeReader.java | 21 ++++++- .../ibm/wala/ipa/callgraph/AnalysisScope.java | 16 ++++++ .../ibm/wala/properties/WalaProperties.java | 26 +++++++-- core/src/main/resources/primordial-base.txt | 2 + .../core/tests/callGraph/CallGraphTest.java | 2 +- .../tests/callGraph/KawaCallGraphTest.java | 7 ++- .../core/tests/cha/AnalysisScopeTest.java | 17 ++++++ .../wala/core/tests/cha/InterfaceTest.java | 2 +- core/src/test/resources/base.txt | 2 +- core/src/test/resources/baseAndDesktop.txt | 3 + core/src/test/resources/hello.txt | 2 +- core/src/test/resources/ocaml_compr.txt | 2 +- core/src/test/resources/ocaml_hello_hash.txt | 2 +- core/src/test/resources/wala.testdata.txt | 2 +- core/src/testFixtures/resources/JLex.txt | 2 +- core/src/testFixtures/resources/bcel.txt | 2 +- core/src/testFixtures/resources/java_cup.txt | 2 +- .../java/com/ibm/wala/util/PlatformUtil.java | 55 ++++++++++--------- 18 files changed, 123 insertions(+), 44 deletions(-) create mode 100644 core/src/main/resources/primordial-base.txt create mode 100644 core/src/test/resources/baseAndDesktop.txt diff --git a/core/src/main/java/com/ibm/wala/core/util/config/AnalysisScopeReader.java b/core/src/main/java/com/ibm/wala/core/util/config/AnalysisScopeReader.java index bdf21ce673..d5729b9939 100644 --- a/core/src/main/java/com/ibm/wala/core/util/config/AnalysisScopeReader.java +++ b/core/src/main/java/com/ibm/wala/core/util/config/AnalysisScopeReader.java @@ -218,10 +218,13 @@ public void processScopeDefLine(AnalysisScope scope, ClassLoader javaLoader, Str } else if ("loaderImpl".equals(entryType)) { scope.setLoaderImpl(walaLoader, entryPathname); } else if ("stdlib".equals(entryType)) { - String[] stdlibs = WalaProperties.getJ2SEJarFiles(); + boolean justBase = entryPathname.equals("base"); + String[] stdlibs = WalaProperties.getJDKLibraryFiles(justBase); for (String stdlib : stdlibs) { scope.addToScope(walaLoader, new JarFile(stdlib, false)); } + } else if ("jdkModule".equals(entryType)) { + scope.addJDKModuleToScope(entryPathname); } else if (!handleInSubclass(scope, walaLoader, language, entryType, entryPathname)) { Assertions.UNREACHABLE(); } @@ -238,6 +241,10 @@ protected boolean handleInSubclass( } /** + * Creates an AnalysisScope containing only the JDK standard libraries. If no explicit JDK library + * paths are given in the WALA properties file, the scope contains all library modules for the + * running JVM. + * * @param exclusionsFile file holding class hierarchy exclusions. may be null * @throws IllegalStateException if there are problmes reading wala properties */ @@ -245,6 +252,18 @@ public AnalysisScope makePrimordialScope(File exclusionsFile) throws IOException return readJavaScope(BASIC_FILE, exclusionsFile, MY_CLASSLOADER); } + /** + * Creates an AnalysisScope containing only the JDK standard libraries. If no explicit JDK library + * paths are given in the WALA properties file, the scope contains only the {@code java.base} + * module for the running JVM. + * + * @param exclusionsFile file holding class hierarchy exclusions. may be null + * @throws IllegalStateException if there are problmes reading wala properties + */ + public AnalysisScope makeBasePrimordialScope(File exclusionsFile) throws IOException { + return readJavaScope("primordial-base.txt", exclusionsFile, MY_CLASSLOADER); + } + /** * @param classPath class path to analyze, delimited by {@link File#pathSeparator} * @param exclusionsFile file holding class hierarchy exclusions. may be null diff --git a/core/src/main/java/com/ibm/wala/ipa/callgraph/AnalysisScope.java b/core/src/main/java/com/ibm/wala/ipa/callgraph/AnalysisScope.java index 9fbeef4236..44dec9bf7c 100644 --- a/core/src/main/java/com/ibm/wala/ipa/callgraph/AnalysisScope.java +++ b/core/src/main/java/com/ibm/wala/ipa/callgraph/AnalysisScope.java @@ -28,6 +28,7 @@ import com.ibm.wala.types.MethodReference; import com.ibm.wala.types.TypeName; import com.ibm.wala.types.TypeReference; +import com.ibm.wala.util.PlatformUtil; import com.ibm.wala.util.collections.FilterIterator; import com.ibm.wala.util.collections.HashMapFactory; import com.ibm.wala.util.collections.HashSetFactory; @@ -40,6 +41,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.NotSerializableException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -194,6 +197,19 @@ public void addClassFileToScope(ClassLoaderReference loader, File file) s.add(new ClassFileModule(file, null)); } + /** + * Adds a module from the Java standard library to the analysis scope. + * + * @param moduleName the name of the module, e.g., {@code "java.sql"} + * @throws IOException if a module by that name cannot successfully be loaded + */ + public void addJDKModuleToScope(String moduleName) throws IOException { + Path path = PlatformUtil.getPathForJDKModule(moduleName); + if (!Files.exists(path)) { + throw new IOException("cannot find jmod file for module " + moduleName + ", tried " + path); + } + addToScope(ClassLoaderReference.Primordial, new JarFile(path.toString(), false)); + } /** * Add a jar file to the scope via an {@link InputStream}. NOTE: The InputStream should *not* be a * {@link java.util.jar.JarInputStream}; it should be a regular {@link InputStream} for the raw diff --git a/core/src/main/java/com/ibm/wala/properties/WalaProperties.java b/core/src/main/java/com/ibm/wala/properties/WalaProperties.java index 34c89f0542..a7881ad1fd 100644 --- a/core/src/main/java/com/ibm/wala/properties/WalaProperties.java +++ b/core/src/main/java/com/ibm/wala/properties/WalaProperties.java @@ -51,29 +51,43 @@ public final class WalaProperties { /** * Determine the classpath noted in wala.properties for J2SE standard libraries * - *

If wala.properties cannot be loaded, returns jar files in boot classpath. + *

If wala.properties cannot be loaded, returns library files in boot classpath. * - * @throws IllegalStateException if jar files cannot be discovered - * @see PlatformUtil#getBootClassPathJars() + * @throws IllegalStateException if library files cannot be discovered + * @see PlatformUtil#getJDKModules(boolean) */ public static String[] getJ2SEJarFiles() { + return getJDKLibraryFiles(false); + } + + /** + * Determine the classpath noted in wala.properties for J2SE standard libraries + * + *

If wala.properties cannot be loaded, returns library files in boot classpath. + * + * @param justBase Only relevant if wala.properties cannot be loaded. If {@code true}, only + * returns the {@code java.base} library from boot classpath. Otherwise, returns all library + * modules from boot classpath. + * @see PlatformUtil#getJDKModules(boolean) + */ + public static String[] getJDKLibraryFiles(boolean justBase) { Properties p = null; try { p = WalaProperties.loadProperties(); } catch (WalaException e) { - return PlatformUtil.getBootClassPathJars(); + return PlatformUtil.getJDKModules(justBase); } String dir = p.getProperty(WalaProperties.J2SE_DIR); if (dir == null) { - return PlatformUtil.getBootClassPathJars(); + return PlatformUtil.getJDKModules(justBase); } if (!new File(dir).isDirectory()) { System.err.println( "WARNING: java_runtime_dir " + dir + " in wala.properties is invalid. Using boot class path instead."); - return PlatformUtil.getBootClassPathJars(); + return PlatformUtil.getJDKModules(justBase); } return getJarsInDirectory(dir); } diff --git a/core/src/main/resources/primordial-base.txt b/core/src/main/resources/primordial-base.txt new file mode 100644 index 0000000000..056765993e --- /dev/null +++ b/core/src/main/resources/primordial-base.txt @@ -0,0 +1,2 @@ +Primordial,Java,stdlib,base +Primordial,Java,jarFile,primordial.jar.model diff --git a/core/src/test/java/com/ibm/wala/core/tests/callGraph/CallGraphTest.java b/core/src/test/java/com/ibm/wala/core/tests/callGraph/CallGraphTest.java index a17bb9c291..b14b85e0b8 100644 --- a/core/src/test/java/com/ibm/wala/core/tests/callGraph/CallGraphTest.java +++ b/core/src/test/java/com/ibm/wala/core/tests/callGraph/CallGraphTest.java @@ -265,7 +265,7 @@ public void testIO() throws ClassHierarchyException, IllegalArgumentException, CancelException, IOException { AnalysisScope scope = CallGraphTestUtil.makeJ2SEAnalysisScope( - "primordial.txt", CallGraphTestUtil.REGRESSION_EXCLUSIONS); + "primordial-base.txt", CallGraphTestUtil.REGRESSION_EXCLUSIONS); ClassHierarchy cha = ClassHierarchyFactory.make(scope); Iterable entrypoints = makePrimordialPublicEntrypoints(cha, "java/io"); AnalysisOptions options = CallGraphTestUtil.makeAnalysisOptions(scope, entrypoints); diff --git a/core/src/test/java/com/ibm/wala/core/tests/callGraph/KawaCallGraphTest.java b/core/src/test/java/com/ibm/wala/core/tests/callGraph/KawaCallGraphTest.java index bda64477b6..05912993e2 100644 --- a/core/src/test/java/com/ibm/wala/core/tests/callGraph/KawaCallGraphTest.java +++ b/core/src/test/java/com/ibm/wala/core/tests/callGraph/KawaCallGraphTest.java @@ -52,6 +52,7 @@ public void testKawaChess() CallGraph CG = testKawa( new ResourceJarFileModule(getClass().getClassLoader().getResource("kawachess.jar")), + "baseAndDesktop.txt", "main"); Set status = @@ -89,6 +90,7 @@ public void testKawaTest() CallGraph CG = testKawa( new ResourceJarFileModule(getClass().getClassLoader().getResource("kawatest.jar")), + "base.txt", "test"); Set nodes = getNodes(CG, "Ltest", "plusish$V", "(Lgnu/lists/LList;)Ljava/lang/Object;"); @@ -113,14 +115,15 @@ private static Set getNodes(CallGraph CG, String cls, String method, Str * #MAX_ITERATIONS} runs of the outer fixed point loop of call graph construction. * * @param code the module + * @param scopeFile the scope file to use * @param main entrypoint method for the call graph * @return the call graph */ - private CallGraph testKawa(Module code, String main) + private CallGraph testKawa(Module code, String scopeFile, String main) throws ClassHierarchyException, IllegalArgumentException, IOException, SecurityException { AnalysisScope scope = CallGraphTestUtil.makeJ2SEAnalysisScope( - "base.txt", CallGraphTestUtil.REGRESSION_EXCLUSIONS_FOR_GUI); + scopeFile, CallGraphTestUtil.REGRESSION_EXCLUSIONS_FOR_GUI); scope.addToScope( ClassLoaderReference.Application, new ResourceJarFileModule(getClass().getClassLoader().getResource("kawa.jar"))); diff --git a/core/src/test/java/com/ibm/wala/core/tests/cha/AnalysisScopeTest.java b/core/src/test/java/com/ibm/wala/core/tests/cha/AnalysisScopeTest.java index 41b1759df9..452d2b51a7 100644 --- a/core/src/test/java/com/ibm/wala/core/tests/cha/AnalysisScopeTest.java +++ b/core/src/test/java/com/ibm/wala/core/tests/cha/AnalysisScopeTest.java @@ -36,4 +36,21 @@ public void testJarInputStream() throws IOException, ClassHierarchyException { TypeReference.findOrCreate( ClassLoaderReference.Application, "Lorg/apache/bcel/verifier/Verifier"))); } + + @Test + public void testBaseScope() throws IOException, ClassHierarchyException { + AnalysisScope scope = + AnalysisScopeReader.instance.readJavaScope( + "primordial-base.txt", null, AnalysisScopeTest.class.getClassLoader()); + ClassHierarchy cha = ClassHierarchyFactory.make(scope); + Assert.assertNotNull( + "couldn't find expected class", + cha.lookupClass( + TypeReference.findOrCreate(ClassLoaderReference.Application, "Ljava/util/ArrayList"))); + Assert.assertNull( + "found unexpected class", + cha.lookupClass( + TypeReference.findOrCreate( + ClassLoaderReference.Application, "Ljava/awt/AlphaComposite"))); + } } diff --git a/core/src/test/java/com/ibm/wala/core/tests/cha/InterfaceTest.java b/core/src/test/java/com/ibm/wala/core/tests/cha/InterfaceTest.java index 1b3ee56c82..9f11c425db 100644 --- a/core/src/test/java/com/ibm/wala/core/tests/cha/InterfaceTest.java +++ b/core/src/test/java/com/ibm/wala/core/tests/cha/InterfaceTest.java @@ -48,7 +48,7 @@ public static void beforeClass() throws Exception { TestConstants.WALA_TESTDATA, new FileProvider().getFile("J2SEClassHierarchyExclusions.txt"), MY_CLASSLOADER); - + scope.addJDKModuleToScope("java.sql"); ClassLoaderFactory factory = new ClassLoaderFactoryImpl(scope.getExclusions()); try { diff --git a/core/src/test/resources/base.txt b/core/src/test/resources/base.txt index 6e347aa7ae..056765993e 100644 --- a/core/src/test/resources/base.txt +++ b/core/src/test/resources/base.txt @@ -1,2 +1,2 @@ -Primordial,Java,stdlib,none +Primordial,Java,stdlib,base Primordial,Java,jarFile,primordial.jar.model diff --git a/core/src/test/resources/baseAndDesktop.txt b/core/src/test/resources/baseAndDesktop.txt new file mode 100644 index 0000000000..3be03b156b --- /dev/null +++ b/core/src/test/resources/baseAndDesktop.txt @@ -0,0 +1,3 @@ +Primordial,Java,stdlib,base +Primordial,Java,jdkModule,java.desktop +Primordial,Java,jarFile,primordial.jar.model diff --git a/core/src/test/resources/hello.txt b/core/src/test/resources/hello.txt index 8474bd585f..4b43897c76 100644 --- a/core/src/test/resources/hello.txt +++ b/core/src/test/resources/hello.txt @@ -1,4 +1,4 @@ -Primordial,Java,stdlib,none +Primordial,Java,stdlib,base Primordial,Java,jarFile,primordial.jar.model Application,Java,classFile,hello/Hello.class Application,Java,sourceFile,hello/Hello.java diff --git a/core/src/test/resources/ocaml_compr.txt b/core/src/test/resources/ocaml_compr.txt index 33b1166012..e6807ab1d9 100644 --- a/core/src/test/resources/ocaml_compr.txt +++ b/core/src/test/resources/ocaml_compr.txt @@ -1,3 +1,3 @@ -Primordial,Java,stdlib,none +Primordial,Java,stdlib,base Primordial,Java,jarFile,primordial.jar.model Application,Java,jarFile,compr diff --git a/core/src/test/resources/ocaml_hello_hash.txt b/core/src/test/resources/ocaml_hello_hash.txt index 1c1f577f3a..e75dbc0fea 100644 --- a/core/src/test/resources/ocaml_hello_hash.txt +++ b/core/src/test/resources/ocaml_hello_hash.txt @@ -1,3 +1,3 @@ -Primordial,Java,stdlib,none +Primordial,Java,stdlib,base Primordial,Java,jarFile,primordial.jar.model Application,Java,jarFile,hello_hash.jar diff --git a/core/src/test/resources/wala.testdata.txt b/core/src/test/resources/wala.testdata.txt index 71da3fa205..494dbb85d5 100644 --- a/core/src/test/resources/wala.testdata.txt +++ b/core/src/test/resources/wala.testdata.txt @@ -1,3 +1,3 @@ -Primordial,Java,stdlib,none +Primordial,Java,stdlib,base Primordial,Java,jarFile,primordial.jar.model Application,Java,jarFile,com.ibm.wala.core.testdata_1.0.0.jar diff --git a/core/src/testFixtures/resources/JLex.txt b/core/src/testFixtures/resources/JLex.txt index b29015f8c0..3e412888b3 100644 --- a/core/src/testFixtures/resources/JLex.txt +++ b/core/src/testFixtures/resources/JLex.txt @@ -1,3 +1,3 @@ -Primordial,Java,stdlib,none +Primordial,Java,stdlib,base Primordial,Java,jarFile,primordial.jar.model Application,Java,jarFile,JLex.jar diff --git a/core/src/testFixtures/resources/bcel.txt b/core/src/testFixtures/resources/bcel.txt index 3a4169faf2..b52289eaca 100644 --- a/core/src/testFixtures/resources/bcel.txt +++ b/core/src/testFixtures/resources/bcel.txt @@ -1,3 +1,3 @@ -Primordial,Java,stdlib,none +Primordial,Java,stdlib,base Primordial,Java,jarFile,primordial.jar.model Application,Java,jarFile,bcel-5.2.jar diff --git a/core/src/testFixtures/resources/java_cup.txt b/core/src/testFixtures/resources/java_cup.txt index 6f5e5f33c0..0278a0bdb0 100644 --- a/core/src/testFixtures/resources/java_cup.txt +++ b/core/src/testFixtures/resources/java_cup.txt @@ -1,3 +1,3 @@ -Primordial,Java,stdlib,none +Primordial,Java,stdlib,base Primordial,Java,jarFile,primordial.jar.model Application,Java,jarFile,java-cup-11a.jar diff --git a/util/src/main/java/com/ibm/wala/util/PlatformUtil.java b/util/src/main/java/com/ibm/wala/util/PlatformUtil.java index 3df6b86565..9c8a274d82 100644 --- a/util/src/main/java/com/ibm/wala/util/PlatformUtil.java +++ b/util/src/main/java/com/ibm/wala/util/PlatformUtil.java @@ -10,12 +10,11 @@ */ package com.ibm.wala.util; -import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; +import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -47,38 +46,44 @@ public static boolean onIKVM() { } /** - * get the jars in the boot classpath. TODO test on more JVMs + * Gets the standard JDK modules shipped with the running JDK * - * @throws IllegalStateException if boot classpath cannot be found + * @param justBase if {@code true}, only include the file corresponding to the {@code java.base} + * module + * @return array of {@code .jmod} module files + * @throws IllegalStateException if modules cannot be found */ - public static String[] getBootClassPathJars() { - String classpath = null; - String javaVersion = System.getProperty("java.specification.version"); - if (!javaVersion.equals("1.8")) { - // java11 support for jmod files + public static String[] getJDKModules(boolean justBase) { + List jmods; + if (justBase) { + Path basePath = Paths.get(System.getProperty("java.home"), "jmods", "java.base.jmod"); + if (!Files.exists(basePath)) { + throw new IllegalStateException("could not find java.base.jmod"); + } + jmods = List.of(basePath.toString()); + } else { try (Stream stream = Files.list(Paths.get(System.getProperty("java.home"), "jmods"))) { - classpath = - String.join( - File.pathSeparator, stream.map(Path::toString).collect(Collectors.toList())); + jmods = + stream + .map(Path::toString) + .filter(p -> p.endsWith(".jmod")) + .collect(Collectors.toList()); } catch (IOException e) { throw new IllegalStateException(e); } - } else { - classpath = System.getProperty("sun.boot.class.path"); - } - if (classpath == null) { - throw new IllegalStateException("could not find boot classpath"); - } - String[] jars = classpath.split(File.pathSeparator); - ArrayList result = new ArrayList<>(); - for (String jar : jars) { - if ((jar.endsWith(".jar") || jar.endsWith(".jmod")) && new File(jar).exists()) { - result.add(jar); - } } - return result.toArray(new String[0]); + return jmods.toArray(new String[0]); } + /** + * Returns the filesystem path for a JDK module from the running JVM + * + * @param moduleName name of the module, e.g., {@code "java.sql"} + * @return path to the module + */ + public static Path getPathForJDKModule(String moduleName) { + return Paths.get(System.getProperty("java.home"), "jmods", moduleName + ".jmod"); + } /** @return the major version of the Java runtime we are running on. */ public static int getJavaRuntimeVersion() { String version = System.getProperty("java.version");