From c6f20aa84297c6a77f64458625a41a6bc56c6887 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Tue, 27 Jun 2023 14:45:16 -0700 Subject: [PATCH] Support running on JDK 17 (#1281) With this change, WALA's regression tests pass on JDK 17, and WALA can also load and process some JDK 17 bytecodes. We have _not_ thoroughly tested recent bytecode features and they may still not work, but whatever is exercised by current regression tests seems to be working. Getting things working on JDK 17 required various minor changes. --- .github/workflows/continuous-integration.yml | 2 + .../com/ibm/wala/gradle/java.gradle.kts | 4 +- .../ibm/wala/cast/java/test/JavaIRTests.java | 4 +- cast/java/test/data/build.gradle.kts | 6 +++ core/build.gradle.kts | 2 +- .../exceptionanalysis/ExceptionAnalysis.java | 15 ++++++-- .../reflection/java7/MethodHandles.java | 13 ++++++- .../ibm/wala/classLoader/SyntheticMethod.java | 3 +- .../com/ibm/wala/ipa/slicer/SlicerUtil.java | 38 +++++++++---------- .../wala/core/tests/slicer/SlicerTest.java | 29 +++++++------- .../ibm/wala/shrike/shrikeCT/ClassReader.java | 3 +- util/.settings/org.eclipse.jdt.core.prefs | 2 +- 12 files changed, 73 insertions(+), 48 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 4db61652fa..d22c639dae 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -19,6 +19,8 @@ jobs: java: 11 - os: windows-latest java: 11 + - os: ubuntu-latest + java: 17 fail-fast: false runs-on: ${{ matrix.os }} steps: diff --git a/build-logic/src/main/kotlin/com/ibm/wala/gradle/java.gradle.kts b/build-logic/src/main/kotlin/com/ibm/wala/gradle/java.gradle.kts index b6c8953419..51ded7d7b1 100644 --- a/build-logic/src/main/kotlin/com/ibm/wala/gradle/java.gradle.kts +++ b/build-logic/src/main/kotlin/com/ibm/wala/gradle/java.gradle.kts @@ -43,9 +43,9 @@ dependencies { "javadocSource"(sourceSets.main.get().allJava) } -the().toolchain.languageVersion.set(JavaLanguageVersion.of(11)) - tasks.withType().configureEach { + // Generate JDK 11 bytecodes; that is the minimum version supported by WALA + options.release.set(11) options.errorprone { // don't run warning-level checks by default as they add too much noise to build output // NOTE: until https://github.com/google/error-prone/pull/3462 makes it to a release, diff --git a/cast/java/src/testFixtures/java/com/ibm/wala/cast/java/test/JavaIRTests.java b/cast/java/src/testFixtures/java/com/ibm/wala/cast/java/test/JavaIRTests.java index 7974b86232..b1c54ae674 100644 --- a/cast/java/src/testFixtures/java/com/ibm/wala/cast/java/test/JavaIRTests.java +++ b/cast/java/src/testFixtures/java/com/ibm/wala/cast/java/test/JavaIRTests.java @@ -722,7 +722,7 @@ public void testMiniaturSliceBug() throws IllegalArgumentException, CancelExcept AstJavaSlicer.computeAssertionSlice(cg, pa, roots, false); Collection slice = y.fst; dumpSlice(slice); - Assert.assertEquals(0, SlicerUtil.countAllocations(slice)); + Assert.assertEquals(0, SlicerUtil.countAllocations(slice, false)); Assert.assertEquals(1, SlicerUtil.countPutfields(slice)); // test slice from main @@ -731,7 +731,7 @@ public void testMiniaturSliceBug() throws IllegalArgumentException, CancelExcept y = AstJavaSlicer.computeAssertionSlice(cg, pa, roots, false); slice = y.fst; // SlicerUtil.dumpSlice(slice); - Assert.assertEquals(2, SlicerUtil.countAllocations(slice)); + Assert.assertEquals(2, SlicerUtil.countAllocations(slice, false)); Assert.assertEquals(2, SlicerUtil.countPutfields(slice)); } diff --git a/cast/java/test/data/build.gradle.kts b/cast/java/test/data/build.gradle.kts index f850269360..0d7d25a80d 100644 --- a/cast/java/test/data/build.gradle.kts +++ b/cast/java/test/data/build.gradle.kts @@ -59,3 +59,9 @@ sourceSets.test.get().java.srcDir(downloadJLex.map { it.extra["downloadedSourceD // tasks.register("prepareMavenBuild") { dependsOn("eclipseClasspath", "eclipseProject") } + +// On JDK 17, deprecation errors in ECJ cannot be disabled when compiling JLex code. So, we disable +// the ECJ task on JDK 17+. +if (JavaVersion.current() >= JavaVersion.VERSION_17) { + tasks.named("compileTestJavaUsingEcj") { enabled = false } +} diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 1a1dfa8582..2c0984ddc9 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -347,7 +347,7 @@ tasks.named("processTestResources") { } tasks.named("test") { - maxHeapSize = "1500M" + maxHeapSize = "2000M" systemProperty("com.ibm.wala.junit.profile", "short") classpath += files(sourceSets.test.get().output.classesDirs) testLogging { diff --git a/core/src/main/java/com/ibm/wala/analysis/exceptionanalysis/ExceptionAnalysis.java b/core/src/main/java/com/ibm/wala/analysis/exceptionanalysis/ExceptionAnalysis.java index 2fc7ec5637..35dc438a5e 100644 --- a/core/src/main/java/com/ibm/wala/analysis/exceptionanalysis/ExceptionAnalysis.java +++ b/core/src/main/java/com/ibm/wala/analysis/exceptionanalysis/ExceptionAnalysis.java @@ -13,6 +13,7 @@ import com.ibm.wala.analysis.arraybounds.ArrayOutOfBoundsAnalysis; import com.ibm.wala.analysis.nullpointer.IntraproceduralNullPointerAnalysis; import com.ibm.wala.classLoader.CallSiteReference; +import com.ibm.wala.classLoader.IClass; import com.ibm.wala.dataflow.graph.BitVectorFramework; import com.ibm.wala.dataflow.graph.BitVectorSolver; import com.ibm.wala.fixpoint.BitVectorVariable; @@ -127,10 +128,18 @@ public boolean catchesException( boolean isCaught = false; while (caughtExceptions.hasNext() && !isCaught) { TypeReference caughtException = caughtExceptions.next(); + IClass caughtExceptionClass = cha.lookupClass(caughtException); + if (caughtExceptionClass == null) { + // for now, assume it is not caught + continue; + } for (TypeReference thrownException : thrownExceptions) { - isCaught |= - cha.isAssignableFrom( - cha.lookupClass(caughtException), cha.lookupClass(thrownException)); + IClass thrownExceptionClass = cha.lookupClass(thrownException); + if (thrownExceptionClass == null) { + // for now, assume it is not caught + continue; + } + isCaught |= cha.isAssignableFrom(caughtExceptionClass, thrownExceptionClass); if (isCaught) break; } } diff --git a/core/src/main/java/com/ibm/wala/analysis/reflection/java7/MethodHandles.java b/core/src/main/java/com/ibm/wala/analysis/reflection/java7/MethodHandles.java index 1f037a31f2..24efc5e8f0 100644 --- a/core/src/main/java/com/ibm/wala/analysis/reflection/java7/MethodHandles.java +++ b/core/src/main/java/com/ibm/wala/analysis/reflection/java7/MethodHandles.java @@ -48,6 +48,7 @@ import com.ibm.wala.util.collections.FilterIterator; import com.ibm.wala.util.collections.HashMapFactory; import com.ibm.wala.util.collections.MapIterator; +import com.ibm.wala.util.debug.UnimplementedError; import com.ibm.wala.util.intset.IntSet; import com.ibm.wala.util.intset.IntSetUtil; import com.ibm.wala.util.intset.MutableIntSet; @@ -301,7 +302,17 @@ public IMethod getCalleeTarget(CGNode caller, CallSiteReference site, IClass rec .getClassHierarchy() .lookupClass(TypeReference.JavaLangInvokeMethodHandle), false, - false); + false) { + @Override + public IR makeIR(Context context, SSAOptions options) throws UnimplementedError { + // MS: On JDK 17, sometimes makeIR() is getting called, and the default + // implementation fails with an error. I don't fully understand the invariants of + // this class, but overriding and returning null makes the tests pass. + // Eventually, we should document this class and figure out if this is the right + // fix. + return null; + } + }; impls.put(target, invokeExactTrampoline); } diff --git a/core/src/main/java/com/ibm/wala/classLoader/SyntheticMethod.java b/core/src/main/java/com/ibm/wala/classLoader/SyntheticMethod.java index 5951386072..16723cc977 100644 --- a/core/src/main/java/com/ibm/wala/classLoader/SyntheticMethod.java +++ b/core/src/main/java/com/ibm/wala/classLoader/SyntheticMethod.java @@ -269,7 +269,8 @@ public SSAInstruction[] getStatements(@SuppressWarnings("unused") SSAOptions opt * @param options options governing IR conversion */ public IR makeIR(Context context, SSAOptions options) throws UnimplementedError { - throw new UnimplementedError("haven't implemented IR yet for class " + getClass()); + throw new UnimplementedError( + "haven't implemented IR yet for class " + getClass() + ", method " + method); } @Override diff --git a/core/src/main/java/com/ibm/wala/ipa/slicer/SlicerUtil.java b/core/src/main/java/com/ibm/wala/ipa/slicer/SlicerUtil.java index c92fcaa66b..5525db5664 100644 --- a/core/src/main/java/com/ibm/wala/ipa/slicer/SlicerUtil.java +++ b/core/src/main/java/com/ibm/wala/ipa/slicer/SlicerUtil.java @@ -93,28 +93,13 @@ public static void dumpSliceToFile(Collection slice, String fileName) } } - public static int countAllocations(Collection slice) { + public static int countAllocations(Collection slice, boolean applicationOnly) { int count = 0; for (Statement s : slice) { if (s.getKind().equals(Statement.Kind.NORMAL)) { NormalStatement ns = (NormalStatement) s; if (ns.getInstruction() instanceof SSANewInstruction) { - count++; - } - } - } - return count; - } - - public static int countApplicationAllocations(Collection slice) { - int count = 0; - for (Statement s : slice) { - if (s.getKind().equals(Statement.Kind.NORMAL)) { - NormalStatement ns = (NormalStatement) s; - if (ns.getInstruction() instanceof SSANewInstruction) { - AnalysisScope scope = s.getNode().getClassHierarchy().getScope(); - if (scope.isApplicationLoader( - s.getNode().getMethod().getDeclaringClass().getClassLoader())) { + if (!applicationOnly || fromApplicationLoader(s)) { count++; } } @@ -123,13 +108,22 @@ public static int countApplicationAllocations(Collection slice) { return count; } - public static int countThrows(Collection slice) { + private static boolean fromApplicationLoader(Statement s) { + return s.getNode() + .getClassHierarchy() + .getScope() + .isApplicationLoader(s.getNode().getMethod().getDeclaringClass().getClassLoader()); + } + + public static int countThrows(Collection slice, boolean applicationOnly) { int count = 0; for (Statement s : slice) { if (s.getKind().equals(Statement.Kind.NORMAL)) { NormalStatement ns = (NormalStatement) s; if (ns.getInstruction() instanceof SSAAbstractThrowInstruction) { - count++; + if (!applicationOnly || fromApplicationLoader(s)) { + count++; + } } } } @@ -228,7 +222,7 @@ public static int countReturns(Collection slice) { return count; } - public static int countGetfields(Collection slice) { + public static int countGetfields(Collection slice, boolean applicationOnly) { int count = 0; for (Statement s : slice) { if (s.getKind().equals(Statement.Kind.NORMAL)) { @@ -236,7 +230,9 @@ public static int countGetfields(Collection slice) { if (ns.getInstruction() instanceof SSAGetInstruction) { SSAGetInstruction p = (SSAGetInstruction) ns.getInstruction(); if (!p.isStatic()) { - count++; + if (!applicationOnly || fromApplicationLoader(s)) { + count++; + } } } } diff --git a/core/src/test/java/com/ibm/wala/core/tests/slicer/SlicerTest.java b/core/src/test/java/com/ibm/wala/core/tests/slicer/SlicerTest.java index 57a2e7e83f..fb0389c2a7 100644 --- a/core/src/test/java/com/ibm/wala/core/tests/slicer/SlicerTest.java +++ b/core/src/test/java/com/ibm/wala/core/tests/slicer/SlicerTest.java @@ -209,7 +209,7 @@ public void testSlice3() Slicer.computeBackwardSlice( s, cg, pointerAnalysis, DataDependenceOptions.FULL, ControlDependenceOptions.NONE); SlicerUtil.dumpSlice(slice); - Assert.assertEquals(slice.toString(), 1, SlicerUtil.countAllocations(slice)); + Assert.assertEquals(slice.toString(), 1, SlicerUtil.countAllocations(slice, false)); } @Test @@ -598,7 +598,7 @@ public void testTestId() Slicer.computeBackwardSlice( s, cg, pointerAnalysis, DataDependenceOptions.FULL, ControlDependenceOptions.NONE); SlicerUtil.dumpSlice(slice); - Assert.assertEquals(slice.toString(), 1, SlicerUtil.countAllocations(slice)); + Assert.assertEquals(slice.toString(), 1, SlicerUtil.countAllocations(slice, false)); } @Test @@ -626,7 +626,7 @@ public void testTestArrays() Slicer.computeBackwardSlice( s, cg, pointerAnalysis, DataDependenceOptions.FULL, ControlDependenceOptions.NONE); SlicerUtil.dumpSlice(slice); - Assert.assertEquals(slice.toString(), 2, SlicerUtil.countAllocations(slice)); + Assert.assertEquals(slice.toString(), 2, SlicerUtil.countAllocations(slice, false)); Assert.assertEquals(slice.toString(), 1, SlicerUtil.countAloads(slice)); } @@ -655,7 +655,7 @@ public void testTestFields() Slicer.computeBackwardSlice( s, cg, pointerAnalysis, DataDependenceOptions.FULL, ControlDependenceOptions.NONE); SlicerUtil.dumpSlice(slice); - Assert.assertEquals(slice.toString(), 2, SlicerUtil.countAllocations(slice)); + Assert.assertEquals(slice.toString(), 2, SlicerUtil.countAllocations(slice, false)); Assert.assertEquals(slice.toString(), 1, SlicerUtil.countPutfields(slice)); } @@ -686,7 +686,7 @@ public void testThin1() Slicer.computeBackwardSlice( s, cg, pointerAnalysis, DataDependenceOptions.FULL, ControlDependenceOptions.NONE); SlicerUtil.dumpSlice(slice); - Assert.assertEquals(3, SlicerUtil.countAllocations(slice)); + Assert.assertEquals(3, SlicerUtil.countAllocations(slice, false)); Assert.assertEquals(2, SlicerUtil.countPutfields(slice)); // compute thin slice .. ignore base pointers @@ -699,7 +699,7 @@ public void testThin1() ControlDependenceOptions.NONE); slice = computeBackwardSlice; SlicerUtil.dumpSlice(slice); - Assert.assertEquals(slice.toString(), 2, SlicerUtil.countAllocations(slice)); + Assert.assertEquals(slice.toString(), 2, SlicerUtil.countAllocations(slice, false)); Assert.assertEquals(slice.toString(), 1, SlicerUtil.countPutfields(slice)); } @@ -728,7 +728,7 @@ public void testTestGlobal() Slicer.computeBackwardSlice( s, cg, pointerAnalysis, DataDependenceOptions.FULL, ControlDependenceOptions.NONE); SlicerUtil.dumpSlice(slice); - Assert.assertEquals(slice.toString(), 1, SlicerUtil.countAllocations(slice)); + Assert.assertEquals(slice.toString(), 1, SlicerUtil.countAllocations(slice, false)); Assert.assertEquals(slice.toString(), 2, SlicerUtil.countPutstatics(slice)); Assert.assertEquals(slice.toString(), 2, SlicerUtil.countGetstatics(slice)); } @@ -758,7 +758,7 @@ public void testTestMultiTarget() Slicer.computeBackwardSlice( s, cg, pointerAnalysis, DataDependenceOptions.FULL, ControlDependenceOptions.NONE); SlicerUtil.dumpSlice(slice); - Assert.assertEquals(slice.toString(), 2, SlicerUtil.countAllocations(slice)); + Assert.assertEquals(slice.toString(), 2, SlicerUtil.countAllocations(slice, false)); } @Test @@ -787,7 +787,7 @@ public void testTestRecursion() Slicer.computeBackwardSlice( s, cg, pointerAnalysis, DataDependenceOptions.FULL, ControlDependenceOptions.NONE); SlicerUtil.dumpSlice(slice); - Assert.assertEquals(slice.toString(), 3, SlicerUtil.countAllocations(slice)); + Assert.assertEquals(slice.toString(), 3, SlicerUtil.countAllocations(slice, false)); Assert.assertEquals(slice.toString(), 2, SlicerUtil.countPutfields(slice)); } @@ -819,7 +819,7 @@ public void testPrimGetterSetter() Slicer.computeBackwardSlice( s, pcg, pointerAnalysis, DataDependenceOptions.FULL, ControlDependenceOptions.FULL); SlicerUtil.dumpSlice(slice); - Assert.assertEquals(slice.toString(), 0, SlicerUtil.countAllocations(slice)); + Assert.assertEquals(slice.toString(), 0, SlicerUtil.countAllocations(slice, false)); Assert.assertEquals(slice.toString(), 1, SlicerUtil.countPutfields(slice)); } @@ -878,7 +878,7 @@ public void testPrimGetterSetter2() Slicer.computeBackwardSlice( s, pcg, pointerAnalysis, DataDependenceOptions.FULL, ControlDependenceOptions.NONE); SlicerUtil.dumpSlice(slice); - Assert.assertEquals(slice.toString(), 1, SlicerUtil.countAllocations(slice)); + Assert.assertEquals(slice.toString(), 1, SlicerUtil.countAllocations(slice, false)); Assert.assertEquals(slice.toString(), 1, SlicerUtil.countPutfields(slice)); } @@ -906,10 +906,9 @@ public void testTestThrowCatch() Collection slice = Slicer.computeBackwardSlice( s, cg, pointerAnalysis, DataDependenceOptions.FULL, ControlDependenceOptions.NONE); - SlicerUtil.dumpSlice(slice); - Assert.assertEquals(slice.toString(), 1, SlicerUtil.countApplicationAllocations(slice)); - Assert.assertEquals(slice.toString(), 1, SlicerUtil.countThrows(slice)); - Assert.assertEquals(slice.toString(), 1, SlicerUtil.countGetfields(slice)); + Assert.assertEquals("wrong number of allocations", 1, SlicerUtil.countAllocations(slice, true)); + Assert.assertEquals("wrong number of throws", 1, SlicerUtil.countThrows(slice, true)); + Assert.assertEquals("wrong number of getfields", 1, SlicerUtil.countGetfields(slice, true)); } @Test diff --git a/shrike/src/main/java/com/ibm/wala/shrike/shrikeCT/ClassReader.java b/shrike/src/main/java/com/ibm/wala/shrike/shrikeCT/ClassReader.java index ac51a17423..f8d1e6038f 100644 --- a/shrike/src/main/java/com/ibm/wala/shrike/shrikeCT/ClassReader.java +++ b/shrike/src/main/java/com/ibm/wala/shrike/shrikeCT/ClassReader.java @@ -67,7 +67,8 @@ private void parse() throws InvalidClassFileException { if (magic != MAGIC) { throw new InvalidClassFileException(offset, "bad magic number: " + magic); } - if (majorVersion < 45 || majorVersion > 60) { + // Support class files up through JDK 17 (version 61) + if (majorVersion < 45 || majorVersion > 61) { throw new InvalidClassFileException( offset, "unknown class file version: " + majorVersion + '.' + minorVersion); } diff --git a/util/.settings/org.eclipse.jdt.core.prefs b/util/.settings/org.eclipse.jdt.core.prefs index 2f09fba067..1ae6eb0923 100644 --- a/util/.settings/org.eclipse.jdt.core.prefs +++ b/util/.settings/org.eclipse.jdt.core.prefs @@ -23,7 +23,7 @@ org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.autoboxing=ignore org.eclipse.jdt.core.compiler.problem.comparingIdentical=error org.eclipse.jdt.core.compiler.problem.deadCode=warning -org.eclipse.jdt.core.compiler.problem.deprecation=error +org.eclipse.jdt.core.compiler.problem.deprecation=disabled org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled org.eclipse.jdt.core.compiler.problem.discouragedReference=error