From 351403792542753275895746e8d775d6399850f0 Mon Sep 17 00:00:00 2001 From: Wagyourtail <wagyourtail@wagyourtail.xyz> Date: Mon, 17 Jun 2024 23:59:00 -0500 Subject: [PATCH] Rework logger, Warn on missing stubs --- build.gradle.kts | 1 + .../gradle/coverage/CoverageRunTask.kt | 56 ++++ .../gradle/ctsym/GenerateCtSymTask.kt | 11 +- gradle-plugin/build.gradle.kts | 2 +- java-api/build.gradle.kts | 48 +-- .../jvmdg/coverage/ApiCoverageChecker.java | 16 +- .../jvmdg/providers/Java8Downgrader.java | 27 +- site/build.gradle.kts | 2 +- .../xyz/wagyourtail/jvmdg/site/html/About.kt | 35 ++- .../jvmdg/site/maven/MavenClient.kt | 7 +- .../jvmdg/site/maven/html/Maven.kt | 21 +- .../wagyourtail/jvmdg/ClassDowngrader.java | 33 +- .../classloader/DowngradingClassLoader.java | 15 +- .../xyz/wagyourtail/jvmdg/logging/Logger.java | 179 +++++++++++ .../wagyourtail/jvmdg/version/Coverage.java | 93 ++++++ .../jvmdg/version/VersionProvider.java | 286 ++++++++++-------- .../jvmdg/version/all/stub/J_L_Class.java | 8 +- .../jvmdg/version/map/ClassMapping.java | 45 ++- .../xyz/wagyourtail/jvmdg/util/Utils.java | 6 + .../jvmdg/internal/JvmDowngraderTest.java | 2 +- 20 files changed, 664 insertions(+), 229 deletions(-) create mode 100644 buildSrc/src/main/kotlin/xyz/wagyourtail/gradle/coverage/CoverageRunTask.kt create mode 100644 src/main/java/xyz/wagyourtail/jvmdg/logging/Logger.java create mode 100644 src/main/java/xyz/wagyourtail/jvmdg/version/Coverage.java diff --git a/build.gradle.kts b/build.gradle.kts index 5dc5e9c8..f8ec05e0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,7 @@ import xyz.wagyourtail.gradle.shadow.ShadowJar plugins { + kotlin("jvm") version "1.9.22" apply false java `maven-publish` `java-library` diff --git a/buildSrc/src/main/kotlin/xyz/wagyourtail/gradle/coverage/CoverageRunTask.kt b/buildSrc/src/main/kotlin/xyz/wagyourtail/gradle/coverage/CoverageRunTask.kt new file mode 100644 index 00000000..a6813980 --- /dev/null +++ b/buildSrc/src/main/kotlin/xyz/wagyourtail/gradle/coverage/CoverageRunTask.kt @@ -0,0 +1,56 @@ +package xyz.wagyourtail.gradle.coverage + +import org.gradle.api.JavaVersion +import org.gradle.api.file.FileCollection +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.internal.ConventionTask +import org.gradle.api.provider.Property +import org.gradle.api.tasks.* +import org.gradle.jvm.toolchain.JavaLanguageVersion +import org.gradle.jvm.toolchain.JavaToolchainService + +abstract class CoverageRunTask : ConventionTask() { + + @get:InputFile + abstract val apiJar: RegularFileProperty + + @get:Internal + abstract var classpath: FileCollection + + @get:InputFile + abstract val ctSym: RegularFileProperty + + @get:Input + abstract val javaVersion: Property<JavaVersion> + + @get:OutputDirectory + @get:Optional + abstract var coverageReports: FileCollection + + + init { + group = "jvmdg" + coverageReports = project.files(temporaryDir.resolve("coverage")) + } + + @TaskAction + fun run() { + val toolchains = project.extensions.getByType(JavaToolchainService::class.java) + + project.javaexec { spec -> + spec.executable = toolchains.launcherFor { + it.languageVersion.set(JavaLanguageVersion.of(javaVersion.get().majorVersion)) + }.get().executablePath.asFile.absolutePath + + spec.workingDir = temporaryDir + spec.mainClass.set("xyz.wagyourtail.jvmdg.coverage.ApiCoverageChecker") + spec.classpath = classpath + spec.jvmArgs("-Djvmdg.java-api=${apiJar.get().asFile.absolutePath}", "-Djvmdg.quiet=true") + spec.args(ctSym.get().asFile.absolutePath) + + }.assertNormalExitValue().rethrowFailure() + + } + + +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/xyz/wagyourtail/gradle/ctsym/GenerateCtSymTask.kt b/buildSrc/src/main/kotlin/xyz/wagyourtail/gradle/ctsym/GenerateCtSymTask.kt index d073a421..c25da567 100644 --- a/buildSrc/src/main/kotlin/xyz/wagyourtail/gradle/ctsym/GenerateCtSymTask.kt +++ b/buildSrc/src/main/kotlin/xyz/wagyourtail/gradle/ctsym/GenerateCtSymTask.kt @@ -3,6 +3,7 @@ package xyz.wagyourtail.gradle.ctsym import org.apache.commons.compress.archivers.zip.ZipArchiveEntry import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream import org.gradle.api.JavaVersion +import org.gradle.api.file.RegularFileProperty import org.gradle.api.internal.ConventionTask import org.gradle.api.provider.Property import org.gradle.api.tasks.Input @@ -22,9 +23,9 @@ import kotlin.io.path.* abstract class GenerateCtSymTask : ConventionTask() { - @Optional - @OutputFile - var ctSym = temporaryDir.resolve("jvmdg").resolve("ct.sym") + @get:Optional + @get:OutputFile + abstract val ctSym: RegularFileProperty @get:Input @get:Optional @@ -34,7 +35,7 @@ abstract class GenerateCtSymTask : ConventionTask() { abstract val upperVersion: Property<JavaVersion> init { - outputs.upToDateWhen { ctSym.exists() } + ctSym.set(temporaryDir.resolve("jvmdg").resolve("ct.sym")) lowerVersion.convention(JavaVersion.VERSION_1_6).finalizeValueOnRead() upperVersion.convention(JavaVersion.VERSION_22).finalizeValueOnRead() } @@ -72,7 +73,7 @@ abstract class GenerateCtSymTask : ConventionTask() { ZipArchiveOutputStream( - ctSym.toPath().outputStream(StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING) + ctSym.get().asFile.toPath().outputStream(StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING) ).use { zos -> val prevJava = mutableMapOf<String, ClassInfo>() for (java in (lowerVersion.get()..upperVersion.get()).reversed()) { diff --git a/gradle-plugin/build.gradle.kts b/gradle-plugin/build.gradle.kts index 62377003..2f7675de 100644 --- a/gradle-plugin/build.gradle.kts +++ b/gradle-plugin/build.gradle.kts @@ -2,7 +2,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import java.util.* plugins { - kotlin("jvm") version "1.9.22" + kotlin("jvm") `java-gradle-plugin` `java-library` `maven-publish` diff --git a/java-api/build.gradle.kts b/java-api/build.gradle.kts index 18347102..c406d21d 100644 --- a/java-api/build.gradle.kts +++ b/java-api/build.gradle.kts @@ -1,6 +1,7 @@ import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassWriter import org.objectweb.asm.tree.ClassNode +import xyz.wagyourtail.gradle.coverage.CoverageRunTask import xyz.wagyourtail.gradle.ctsym.GenerateCtSymTask import xyz.wagyourtail.gradle.shadow.ShadowJar import xyz.wagyourtail.jvmdg.util.plus @@ -131,9 +132,38 @@ tasks.getByName<JavaCompile>("compileCoverageJava") { configCompile(testVersion) } +val coverageApiJar by tasks.registering(Jar::class) { + from(sourceSets.main.get().output) + from(*((fromVersion..toVersion).map { sourceSets["java${it.ordinal + 1}"].output }).toTypedArray()) + from(rootProject.sourceSets.getByName("shared").output) + + destinationDirectory = temporaryDir +} + + +val genCtSym by tasks.registering(GenerateCtSymTask::class) { + group = "jvmdg" + upperVersion = toVersion - 1 +} + +val coverageReport by tasks.registering(CoverageRunTask::class) { + group = "jvmdg" + dependsOn(coverageApiJar, genCtSym) + apiJar.set(coverageApiJar.get().archiveFile.get().asFile) + classpath = coverage.runtimeClasspath + ctSym.set(genCtSym.get().ctSym) + javaVersion.set(testVersion) +} + tasks.jar { + dependsOn(coverageReport) from(*((fromVersion..toVersion).map { sourceSets["java${it.ordinal + 1}"].output }).toTypedArray()) from(rootProject.sourceSets.getByName("shared").output) + from(coverageReport.get().coverageReports) { + into("META-INF/coverage") + exclude("**/unmatched.txt") + exclude("**/parentOnly.txt") + } from(projectDir.parentFile.resolve("LICENSE.md")) from(projectDir.parentFile.resolve("license")) { into("license") @@ -253,24 +283,6 @@ val downgradeJar8 by tasks.registering(Jar::class) { from(zipTree(tempFile8)) } -val genCySym by tasks.registering(GenerateCtSymTask::class) { - group = "jvmdg" - upperVersion = toVersion - 1 -} - -val coverageReport by tasks.registering(JavaExec::class) { - group = "jvmdg" - dependsOn(tasks.jar, genCySym) - javaLauncher.set(javaToolchains.launcherFor { - languageVersion.set(JavaLanguageVersion.of(testVersion.majorVersion)) - }) - mainClass = "xyz.wagyourtail.jvmdg.coverage.ApiCoverageChecker" - classpath = coverage.runtimeClasspath - jvmArgs("-Djvmdg.java-api=${tasks.jar.get().archiveFile.get().asFile.absolutePath}", "-Djvmdg.quiet=true") - args(genCySym.get().ctSym) - workingDir = project.layout.buildDirectory.get().asFile -} - tasks.assemble { dependsOn(downgradeJar11) dependsOn(downgradeJar8) diff --git a/java-api/src/coverage/java/xyz/wagyourtail/jvmdg/coverage/ApiCoverageChecker.java b/java-api/src/coverage/java/xyz/wagyourtail/jvmdg/coverage/ApiCoverageChecker.java index 3ac32bda..6ba0c35a 100644 --- a/java-api/src/coverage/java/xyz/wagyourtail/jvmdg/coverage/ApiCoverageChecker.java +++ b/java-api/src/coverage/java/xyz/wagyourtail/jvmdg/coverage/ApiCoverageChecker.java @@ -92,7 +92,6 @@ public static void main(String[] args) throws IOException, URISyntaxException { System.out.println("Checking version " + stubVersion); var unmatchedStubs = versionProvider.stubMappings.values().stream().flatMap(value -> Stream.of(value.getMethodStubMap().values().stream(), value.getMethodModifyMap().values().stream()).flatMap(e -> e)).map(Pair::getFirst).collect(Collectors.toList()); - try { var requiredStubs = new ArrayList<MemberInfo>(); compare(versions.get(v), classes, requiredStubs); @@ -106,7 +105,13 @@ public static void main(String[] args) throws IOException, URISyntaxException { var stub = staticAndStub.fqm(); if (stub.getName() != null) { - var stubProvider = versionProvider.getStubMapper(stub.getOwner()); + Set<String> warnings = new HashSet<>(); + var stubProvider = versionProvider.getStubMapper(stub.getOwner(), warnings); + if (!warnings.isEmpty()) { + for (var warning : warnings) { + System.err.println(warning); + } + } // map classes in desc var desc = stub.getDesc(); var descArgs = desc.getArgumentTypes(); @@ -261,6 +266,11 @@ public static ClassNode findClass(String name, List<Path> mods) throws IOExcepti return null; } + private static final Set<String> excludedMods = Set.of( + "jdk.internal.vm.compiler", + "jdk.internal.vm.compiler.management" + ); + public static void compare(List<Path> moduleHolders, Map<String, Pair<String, ClassNode>> currentVersion, List<MemberInfo> removed) throws IOException { var mods = new ArrayList<Path>(); for (var mod : moduleHolders) { @@ -276,6 +286,8 @@ public static void compare(List<Path> moduleHolders, Map<String, Pair<String, Cl return; } var modName = mod.getFileName().toString(); + + if (excludedMods.contains(modName)) return; try (var files = Files.find(mod, Integer.MAX_VALUE, (p, a) -> !a.isDirectory())) { files.parallel().forEach(p -> { try { diff --git a/java-api/src/main/java/xyz/wagyourtail/jvmdg/providers/Java8Downgrader.java b/java-api/src/main/java/xyz/wagyourtail/jvmdg/providers/Java8Downgrader.java index 0f6f5c63..6d28d0c1 100644 --- a/java-api/src/main/java/xyz/wagyourtail/jvmdg/providers/Java8Downgrader.java +++ b/java-api/src/main/java/xyz/wagyourtail/jvmdg/providers/Java8Downgrader.java @@ -9,6 +9,7 @@ import xyz.wagyourtail.jvmdg.j8.stub.*; import xyz.wagyourtail.jvmdg.j8.stub.function.*; import xyz.wagyourtail.jvmdg.j8.stub.stream.*; +import xyz.wagyourtail.jvmdg.logging.Logger; import xyz.wagyourtail.jvmdg.util.Function; import xyz.wagyourtail.jvmdg.util.Pair; import xyz.wagyourtail.jvmdg.version.VersionProvider; @@ -29,7 +30,7 @@ public Java8Downgrader() { @Override public void ensureInit(ClassDowngrader downgrader) { if (!isInitialized()) { - if (!downgrader.flags.quiet) System.err.println("[WARNING] Java 8 -> 7 Stubs are VERY incomplete!"); + if (!downgrader.flags.quiet) downgrader.logger.warn("Java 8 -> 7 Stubs are VERY incomplete!"); } super.ensureInit(downgrader); } @@ -329,12 +330,12 @@ public void init() { } @Override - public ClassNode otherTransforms(ClassNode clazz, Set<ClassNode> extra, Function<String, ClassNode> getReadOnly) { + public ClassNode otherTransforms(ClassNode clazz, Set<ClassNode> extra, Function<String, ClassNode> getReadOnly, Set<String> warnings) { List<ClassNode> classes = new ArrayList<>(extra); classes.add(clazz); for (ClassNode cls : classes) { try { - downgradeInterfaces(cls, extra, getReadOnly); + downgradeInterfaces(cls, extra, getReadOnly, warnings); } catch (IOException e) { throw new RuntimeException(e); } @@ -342,15 +343,15 @@ public ClassNode otherTransforms(ClassNode clazz, Set<ClassNode> extra, Function return super.otherTransforms(clazz, extra, getReadOnly); } - public void downgradeInterfaces(ClassNode clazz, Set<ClassNode> extra, Function<String, ClassNode> getReadOnly) throws IOException { + public void downgradeInterfaces(ClassNode clazz, Set<ClassNode> extra, Function<String, ClassNode> getReadOnly, Set<String> warnings) throws IOException { if ((clazz.access & Opcodes.ACC_INTERFACE) != 0) { - downgradeInterfaceMethods(clazz, extra, getReadOnly); + downgradeInterfaceMethods(clazz, extra, getReadOnly, warnings); } else { - downgradeInterfaceAccesses(clazz, extra, getReadOnly); + downgradeInterfaceAccesses(clazz, extra, getReadOnly, warnings); } } - private void downgradeInterfaceMethods(final ClassNode clazz, Set<ClassNode> extra, final Function<String, ClassNode> getReadOnly) throws IOException { + private void downgradeInterfaceMethods(final ClassNode clazz, Set<ClassNode> extra, final Function<String, ClassNode> getReadOnly, Set<String> warnings) throws IOException { ClassNode interfaceStaticDefaults = new ClassNode(); boolean removed = false; Iterator<MethodNode> mnodes = clazz.methods.iterator(); @@ -410,14 +411,14 @@ public ClassNode apply(String s) { } return getReadOnly.apply(s); } - }); + }, warnings); } } - private void downgradeInterfaceAccesses(ClassNode clazz, Set<ClassNode> extra, Function<String, ClassNode> getReadOnly) throws IOException { + private void downgradeInterfaceAccesses(ClassNode clazz, Set<ClassNode> extra, Function<String, ClassNode> getReadOnly, Set<String> warnings) throws IOException { Map<MemberNameAndDesc, Type> members = new HashMap<>(); if (!clazz.name.endsWith("$jvmdg$StaticDefaults")) { - ClassMapping stubMapper = getStubMapper(Type.getObjectType(clazz.name), (clazz.access & Opcodes.ACC_INTERFACE) != 0); + ClassMapping stubMapper = getStubMapper(Type.getObjectType(clazz.name), (clazz.access & Opcodes.ACC_INTERFACE) != 0, warnings); for (Map.Entry<MemberNameAndDesc, Pair<Boolean, Type>> member : stubMapper.getMembers().entrySet()) { if (member.getValue().getFirst()) { members.put(member.getKey(), member.getValue().getSecond()); @@ -435,7 +436,7 @@ private void downgradeInterfaceAccesses(ClassNode clazz, Set<ClassNode> extra, F min.owner.startsWith("com/sun/") ) { if (min.getOpcode() == Opcodes.INVOKESTATIC || min.getOpcode() == Opcodes.INVOKESPECIAL) { - System.err.println("[Java8 Interface Downgrader] Found java interface missing stub: " + FullyQualifiedMemberNameAndDesc.of(min)); + warnings.add("Found java interface missing stub: " + FullyQualifiedMemberNameAndDesc.of(min)); } continue; } @@ -466,7 +467,7 @@ private void downgradeInterfaceAccesses(ClassNode clazz, Set<ClassNode> extra, F handle.getOwner().startsWith("jdk/") || handle.getOwner().startsWith("com/sun/")) { if (handle.getTag() == Opcodes.H_INVOKESTATIC || handle.getTag() == Opcodes.H_INVOKESPECIAL) { - System.err.println("[Java8 Interface Downgrader] Found java interface missing stub: " + FullyQualifiedMemberNameAndDesc.of(handle)); + warnings.add("Found java interface missing stub: " + FullyQualifiedMemberNameAndDesc.of(handle)); } continue; } @@ -508,7 +509,7 @@ private void downgradeInterfaceAccesses(ClassNode clazz, Set<ClassNode> extra, F internalName.startsWith("sun/") || internalName.startsWith("jdk/") || internalName.startsWith("com/sun/")) { - System.err.println("[Java8 Interface Downgrader] Found java interface default missing implementation: " + member.getKey().toFullyQualified(member.getValue())); + warnings.add("[Java8 Interface Downgrader] Found java interface default missing implementation: " + member.getKey().toFullyQualified(member.getValue())); continue; } // create method redirecting to static default diff --git a/site/build.gradle.kts b/site/build.gradle.kts index d4aa349a..4043fa48 100644 --- a/site/build.gradle.kts +++ b/site/build.gradle.kts @@ -1,7 +1,7 @@ import xyz.wagyourtail.gradle.shadow.ShadowJar plugins { - kotlin("jvm") version "1.9.22" + kotlin("jvm") application } diff --git a/site/src/main/kotlin/xyz/wagyourtail/jvmdg/site/html/About.kt b/site/src/main/kotlin/xyz/wagyourtail/jvmdg/site/html/About.kt index 2047408f..53b253d2 100644 --- a/site/src/main/kotlin/xyz/wagyourtail/jvmdg/site/html/About.kt +++ b/site/src/main/kotlin/xyz/wagyourtail/jvmdg/site/html/About.kt @@ -18,28 +18,31 @@ class About : Template<HTML> { head { title("JvmDowngrader") style { - +""" - body { - max-width: 800px; - margin:0 auto; - } - h1 { - text-align: center; - } - .user-del { - text-decoration: line-through; - } - """.trimIndent() + unsafe { + +""" + body { + max-width: 800px; + margin:0 auto; + } + h1 { + text-align: center; + } + .user-del { + text-decoration: line-through; + } + """.trimIndent() + } } link(rel = "stylesheet", href = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css") script(src = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js") {} script(src = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/gradle.min.js") {} - // <script>hljs.highlightAll();</script> script { defer = true - +""" - hljs.highlightAll(); - """.trimIndent() + unsafe { + +""" + hljs.highlightAll(); + """.trimIndent() + } } } body { diff --git a/site/src/main/kotlin/xyz/wagyourtail/jvmdg/site/maven/MavenClient.kt b/site/src/main/kotlin/xyz/wagyourtail/jvmdg/site/maven/MavenClient.kt index 75248fce..0ebf636c 100644 --- a/site/src/main/kotlin/xyz/wagyourtail/jvmdg/site/maven/MavenClient.kt +++ b/site/src/main/kotlin/xyz/wagyourtail/jvmdg/site/maven/MavenClient.kt @@ -1,3 +1,5 @@ +@file:Suppress("DEPRECATION") + package xyz.wagyourtail.jvmdg.site.maven import org.apache.maven.repository.internal.MavenRepositorySystemUtils @@ -6,16 +8,12 @@ import org.eclipse.aether.RepositorySystem import org.eclipse.aether.artifact.DefaultArtifact import org.eclipse.aether.collection.CollectRequest import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory -import org.eclipse.aether.graph.DefaultDependencyNode import org.eclipse.aether.graph.Dependency import org.eclipse.aether.impl.DefaultServiceLocator -import org.eclipse.aether.metadata.DefaultMetadata -import org.eclipse.aether.metadata.Metadata import org.eclipse.aether.repository.LocalRepository import org.eclipse.aether.repository.RemoteRepository import org.eclipse.aether.resolution.ArtifactRequest import org.eclipse.aether.resolution.DependencyRequest -import org.eclipse.aether.resolution.MetadataRequest import org.eclipse.aether.spi.connector.RepositoryConnectorFactory import org.eclipse.aether.spi.connector.transport.TransporterFactory import org.eclipse.aether.transport.file.FileTransporterFactory @@ -25,7 +23,6 @@ import org.w3c.dom.Document import xyz.wagyourtail.jvmdg.site.maven.impl.ConsoleRepositoryListener import xyz.wagyourtail.jvmdg.site.maven.impl.ConsoleTransferListener import java.io.File -import java.io.InputStream import java.net.URI import java.net.http.HttpClient import java.net.http.HttpRequest diff --git a/site/src/main/kotlin/xyz/wagyourtail/jvmdg/site/maven/html/Maven.kt b/site/src/main/kotlin/xyz/wagyourtail/jvmdg/site/maven/html/Maven.kt index aa8d7562..ff102234 100644 --- a/site/src/main/kotlin/xyz/wagyourtail/jvmdg/site/maven/html/Maven.kt +++ b/site/src/main/kotlin/xyz/wagyourtail/jvmdg/site/maven/html/Maven.kt @@ -10,16 +10,17 @@ class Maven : Template<HTML> { head { title("JvmDowngrader Maven") style { - +""" - body { - max-width: 800px; - margin:0 auto; - } - h1 { - text-align: center; - } - - """.trimIndent() + unsafe { + +""" + body { + max-width: 800px; + margin:0 auto; + } + h1 { + text-align: center; + } + """.trimIndent() + } } } body { diff --git a/src/main/java/xyz/wagyourtail/jvmdg/ClassDowngrader.java b/src/main/java/xyz/wagyourtail/jvmdg/ClassDowngrader.java index 12c757f3..e0622f4a 100644 --- a/src/main/java/xyz/wagyourtail/jvmdg/ClassDowngrader.java +++ b/src/main/java/xyz/wagyourtail/jvmdg/ClassDowngrader.java @@ -1,5 +1,6 @@ package xyz.wagyourtail.jvmdg; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.VisibleForTesting; @@ -14,6 +15,7 @@ import xyz.wagyourtail.jvmdg.asm.ASMUtils; import xyz.wagyourtail.jvmdg.classloader.DowngradingClassLoader; import xyz.wagyourtail.jvmdg.cli.Flags; +import xyz.wagyourtail.jvmdg.logging.Logger; import xyz.wagyourtail.jvmdg.util.Function; import xyz.wagyourtail.jvmdg.util.Pair; import xyz.wagyourtail.jvmdg.util.Utils; @@ -28,6 +30,7 @@ import java.net.URLClassLoader; import java.util.*; import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; public class ClassDowngrader implements Closeable { /** @@ -40,9 +43,13 @@ public class ClassDowngrader implements Closeable { private final Map<Integer, VersionProvider> downgraders; private final DowngradingClassLoader classLoader; + @ApiStatus.Internal + public final Logger logger; + protected ClassDowngrader(@NotNull Flags flags) { this.flags = flags; this.target = flags.classVersion; + logger = new Logger(ClassDowngrader.class, flags.printDebug ? Logger.Level.DEBUG : flags.quiet ? Logger.Level.FATAL : Logger.Level.INFO, System.out); try { classLoader = new DowngradingClassLoader(this, ClassDowngrader.class.getClassLoader()); } catch (IOException e) { @@ -105,14 +112,14 @@ public synchronized Map<Integer, VersionProvider> collectProviders() { return downgraders; } - public Set<MemberNameAndDesc> getMembers(int version, Type type) throws IOException { + public Set<MemberNameAndDesc> getMembers(int version, Type type, Set<String> warnings) throws IOException { for (int vers = version; vers > target; vers--) { VersionProvider downgrader = downgraders.get(vers); if (downgrader == null) { throw new RuntimeException("Unsupported class version: " + vers + " supported: " + downgraders.keySet()); } downgrader.ensureInit(this); - Type stubbed = downgrader.stubClass(type); + Type stubbed = downgrader.stubClass(type, warnings); if (!stubbed.equals(type)) { try (InputStream stream = classLoader.getResourceAsStream(stubbed.getInternalName() + ".class")) { if (stream == null) throw new IOException("Failed to find stubbed class: " + stubbed); @@ -122,7 +129,7 @@ public Set<MemberNameAndDesc> getMembers(int version, Type type) throws IOExcept Set<MemberNameAndDesc> members = new HashSet<>(); for (MethodNode o : node.methods) { if ((o.access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_PRIVATE)) != 0) continue; - members.add(new MemberNameAndDesc(o.name, stubClass(node.version, Type.getMethodType(o.desc)))); + members.add(new MemberNameAndDesc(o.name, stubClass(node.version, Type.getMethodType(o.desc), warnings))); } return members; } @@ -144,20 +151,20 @@ public Set<MemberNameAndDesc> getMembers(int version, Type type) throws IOExcept Set<MemberNameAndDesc> members = new HashSet<>(); for (MethodNode o : node.methods) { if ((o.access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_PRIVATE)) != 0) continue; - members.add(new MemberNameAndDesc(o.name, stubClass(node.version, Type.getMethodType(o.desc)))); + members.add(new MemberNameAndDesc(o.name, stubClass(node.version, Type.getMethodType(o.desc), warnings))); } return members; } } - public List<Pair<Type, Boolean>> getSupertypes(int version, Type type) throws IOException { + public List<Pair<Type, Boolean>> getSupertypes(int version, Type type, Set<String> warnings) throws IOException { for (int vers = version; vers > target; vers--) { VersionProvider downgrader = downgraders.get(vers); if (downgrader == null) { throw new RuntimeException("Unsupported class version: " + vers + " supported: " + downgraders.keySet()); } downgrader.ensureInit(this); - Type stubbed = downgrader.stubClass(type); + Type stubbed = downgrader.stubClass(type, warnings); if (!stubbed.equals(type)) { try (InputStream stream = classLoader.getResourceAsStream(stubbed.getInternalName() + ".class")) { if (stream == null) throw new IOException("Failed to find stubbed class: " + stubbed); @@ -183,14 +190,14 @@ public List<Pair<Type, Boolean>> getSupertypes(int version, Type type) throws IO } } - public Boolean isInterface(int version, Type type) throws IOException { + public Boolean isInterface(int version, Type type, Set<String> warnings) throws IOException { for (int vers = version; vers > target; vers--) { VersionProvider downgrader = downgraders.get(vers); if (downgrader == null) { throw new RuntimeException("Unsupported class version: " + vers + " supported: " + downgraders.keySet()); } downgrader.ensureInit(this); - Type stubbed = downgrader.stubClass(type); + Type stubbed = downgrader.stubClass(type, warnings); if (!stubbed.equals(type)) { try (InputStream stream = classLoader.getResourceAsStream(stubbed.getInternalName() + ".class")) { if (stream == null) throw new IOException("Failed to find stubbed class: " + stubbed); @@ -206,14 +213,14 @@ public Boolean isInterface(int version, Type type) throws IOException { } } - public Type stubClass(int version, Type type) { + public Type stubClass(int version, Type type, Set<String> warnings) { for (int vers = version; vers > target; vers--) { VersionProvider downgrader = downgraders.get(vers); if (downgrader == null) { throw new RuntimeException("Unsupported class version: " + vers + " supported: " + downgraders.keySet()); } downgrader.ensureInit(this); - Type stubbed = downgrader.stubClass(type); + Type stubbed = downgrader.stubClass(type, warnings); if (!stubbed.equals(type)) { return stubbed; } @@ -310,12 +317,12 @@ public ClassNode apply(String s) { } catch (Exception e) { throw new RuntimeException("Failed to downgrade " + name.get(), e); } - if (flags.printDebug) { + if (logger.is(Logger.Level.DEBUG)) { for (Map.Entry<String, byte[]> entry : outputs.entrySet()) { if (!entry.getKey().equals(name.get())) { - System.out.println("Downgraded " + entry.getKey() + " from unknown to " + target); + logger.debug("Downgraded " + entry.getKey() + " from unknown to " + target); } else { - System.out.println("Downgraded " + entry.getKey() + " from " + version + " to " + target); + logger.debug("Downgraded " + entry.getKey() + " from " + version + " to " + target); } writeBytesToDebug(entry.getKey(), entry.getValue()); } diff --git a/src/main/java/xyz/wagyourtail/jvmdg/classloader/DowngradingClassLoader.java b/src/main/java/xyz/wagyourtail/jvmdg/classloader/DowngradingClassLoader.java index bce840e4..ec4ada64 100644 --- a/src/main/java/xyz/wagyourtail/jvmdg/classloader/DowngradingClassLoader.java +++ b/src/main/java/xyz/wagyourtail/jvmdg/classloader/DowngradingClassLoader.java @@ -3,6 +3,7 @@ import xyz.wagyourtail.jvmdg.ClassDowngrader; import xyz.wagyourtail.jvmdg.classloader.providers.ClassLoaderResourceProvider; import xyz.wagyourtail.jvmdg.classloader.providers.JarFileResourceProvider; +import xyz.wagyourtail.jvmdg.logging.Logger; import xyz.wagyourtail.jvmdg.util.Function; import xyz.wagyourtail.jvmdg.util.Utils; @@ -11,8 +12,6 @@ import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; -import java.nio.file.FileSystem; -import java.nio.file.Path; import java.util.*; import java.util.concurrent.atomic.AtomicReference; import java.util.jar.JarFile; @@ -22,6 +21,8 @@ public class DowngradingClassLoader extends ClassLoader implements Closeable { private final ClassDowngrader currentVersionDowngrader; private final List<ResourceProvider> delegates = new ArrayList<>(); + private final Logger logger; + public DowngradingClassLoader(ClassDowngrader downgrader) throws IOException { super(); File apiJar = downgrader.flags.findJavaApi(); @@ -34,6 +35,7 @@ public DowngradingClassLoader(ClassDowngrader downgrader) throws IOException { } else { this.currentVersionDowngrader = downgrader; } + logger = holder.logger.subLogger(DowngradingClassLoader.class); } public DowngradingClassLoader(ClassDowngrader downgrader, ClassLoader parent) throws IOException { @@ -48,6 +50,7 @@ public DowngradingClassLoader(ClassDowngrader downgrader, ClassLoader parent) th } else { this.currentVersionDowngrader = downgrader; } + logger = holder.logger.subLogger(DowngradingClassLoader.class); } public DowngradingClassLoader(ClassDowngrader downgrader, List<ResourceProvider> providers, ClassLoader parent) throws IOException { @@ -108,8 +111,7 @@ public byte[] apply(String s) { returnValue = defineClass(name, bytes, 0, bytes.length); } catch (ClassFormatError e) { currentVersionDowngrader.writeBytesToDebug(name, bytes); -// System.err.println("Failed to load class " + name + " with downgraded bytes, writing to debug folder."); -// throw e; + logger.fatal("Failed to load class " + name + " with downgraded bytes, writing to debug folder.", e); throw new ClassNotFoundException(name, e); } for (Map.Entry<String, byte[]> entry : outputs.entrySet()) { @@ -119,7 +121,7 @@ public byte[] apply(String s) { try { defineClass(extraName, extraBytes, 0, extraBytes.length); } catch (ClassFormatError | ClassCircularityError e) { - System.err.println("Failed to load class " + extraName + " with downgraded bytes, writing to debug folder."); + logger.fatal("Failed to load class " + extraName + " with downgraded bytes, writing to debug folder.", e); currentVersionDowngrader.writeBytesToDebug(extraName, bytes); throw e; } @@ -127,8 +129,7 @@ public byte[] apply(String s) { return returnValue; } catch (ClassFormatError e) { currentVersionDowngrader.writeBytesToDebug(name, bytes); -// System.err.println("Failed to load class " + name + " with original bytes, writing to debug folder."); -// throw e; + logger.fatal("Failed to load class " + name + " with original bytes, writing to debug folder.", e); throw new ClassNotFoundException(name, e); } catch (Throwable e) { throw new ClassNotFoundException(name, e); diff --git a/src/main/java/xyz/wagyourtail/jvmdg/logging/Logger.java b/src/main/java/xyz/wagyourtail/jvmdg/logging/Logger.java new file mode 100644 index 00000000..4528ec75 --- /dev/null +++ b/src/main/java/xyz/wagyourtail/jvmdg/logging/Logger.java @@ -0,0 +1,179 @@ +package xyz.wagyourtail.jvmdg.logging; + +import xyz.wagyourtail.jvmdg.util.Consumer; +import xyz.wagyourtail.jvmdg.util.Utils; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Iterator; + +public class Logger { + private final String prefix; + private final Level level; + private final PrintStream out; + + public Logger(String prefix, Level level, PrintStream out) { + this.prefix = prefix; + this.level = level; + this.out = out; + } + + public Logger(Class<?> clazz, Level level, PrintStream out) { + this(clazz.getSimpleName(), level, out); + } + + public boolean is(Level level) { + return level.ordinal() >= this.level.ordinal(); + } + + public void log(Level level, String message) { + if (is(level)) { + out.println(level.wrap("[" + prefix + "] " + level + ": " + message)); + } + } + + public void trace(String message) { + log(Level.TRACE, message); + } + + public void debug(String message) { + log(Level.DEBUG, message); + } + + public void info(String message) { + log(Level.INFO, message); + } + + public void warn(String message) { + log(Level.WARN, message); + } + + public void error(String message) { + log(Level.ERROR, message); + } + + public void fatal(String message) { + log(Level.FATAL, message); + } + + public void warn(final String message, final Throwable t) { + wrapPrintStream(Level.WARN, new Consumer<PrintStream>() { + @Override + public void accept(PrintStream printStream) { + printStream.println(message); + t.printStackTrace(printStream); + } + }); + } + + public void error(final String message, final Throwable t) { + wrapPrintStream(Level.ERROR, new Consumer<PrintStream>() { + @Override + public void accept(PrintStream printStream) { + printStream.println(message); + t.printStackTrace(printStream); + } + }); + } + + public void fatal(final String message, final Throwable t) { + wrapPrintStream(Level.FATAL, new Consumer<PrintStream>() { + @Override + public void accept(PrintStream printStream) { + printStream.println(message); + t.printStackTrace(printStream); + } + }); + } + + public Logger subLogger(String prefix) { + return new Logger(this.prefix + "/" + prefix, level, out); + } + + public Logger subLogger(Class<?> clazz) { + return subLogger(clazz.getSimpleName()); + } + + public void wrapPrintStream(Level level, Consumer<PrintStream> ps) { + if (is(level)) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream ps2 = new PrintStream(baos, true, StandardCharsets.UTF_8.name()); + ps.accept(ps2); + ps2.close(); + String str = baos.toString(StandardCharsets.UTF_8.name()); + log(level, str); + } catch (UnsupportedEncodingException e) { + Utils.<RuntimeException>sneakyThrow(e); + } + } + } + + public enum Level { + TRACE(AnsiColor.DARK_GRAY), + DEBUG(AnsiColor.LIGHT_GRAY), + INFO(AnsiColor.WHITE), + WARN(AnsiColor.YELLOW), + ERROR(AnsiColor.RED), + FATAL(AnsiColor.LIGHT_RED); + ; + + private final AnsiColor ansiColor; + + Level(AnsiColor ansiColor) { + this.ansiColor = ansiColor; + } + + public AnsiColor getAnsiColor() { + return ansiColor; + } + + public String wrap(String message) { + return ansiColor.wrap(message); + } + } + + public enum AnsiColor { + RESET("\u001B[0m"), + BLACK("\u001B[30m"), + RED("\u001B[31m"), + GREEN("\u001B[32m"), + YELLOW("\u001B[33m"), + BLUE("\u001B[34m"), + PURPLE("\u001B[35m"), + CYAN("\u001B[36m"), + LIGHT_GRAY("\u001B[37m"), + + DARK_GRAY("\u001B[90m"), + LIGHT_RED("\u001B[91m"), + LIGHT_GREEN("\u001B[92m"), + LIGHT_YELLOW("\u001B[93m"), + LIGHT_BLUE("\u001B[94m"), + LIGHT_PURPLE("\u001B[95m"), + LIGHT_CYAN("\u001B[96m"), + WHITE("\u001B[97m"); + + private final String ansiColor; + + AnsiColor(String ansiColor) { + this.ansiColor = ansiColor; + } + + public String getAnsiColor() { + return ansiColor; + } + + public String wrap(String message) { + String[] parts = message.split("\n"); + StringBuilder sb = new StringBuilder(); + Iterator<String> it = Arrays.asList(parts).iterator(); + while (it.hasNext()) { + sb.append(ansiColor).append(it.next()).append(RESET.ansiColor); + if (it.hasNext()) sb.append("\n"); + } + return sb.toString(); + } + + } +} diff --git a/src/main/java/xyz/wagyourtail/jvmdg/version/Coverage.java b/src/main/java/xyz/wagyourtail/jvmdg/version/Coverage.java new file mode 100644 index 00000000..5be5742d --- /dev/null +++ b/src/main/java/xyz/wagyourtail/jvmdg/version/Coverage.java @@ -0,0 +1,93 @@ +package xyz.wagyourtail.jvmdg.version; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Type; +import xyz.wagyourtail.jvmdg.util.Utils; +import xyz.wagyourtail.jvmdg.version.map.FullyQualifiedMemberNameAndDesc; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +public class Coverage { + private final VersionProvider versionProvider; + private final int javaVersion; + private final String path; + + private Map<Type, String> classes; + private Map<FullyQualifiedMemberNameAndDesc, String> members; + + public Coverage(int inputVersion, VersionProvider versionProvider) { + this.javaVersion = Utils.classVersionToMajorVersion(inputVersion); + this.path = "/META-INF/coverage/" + javaVersion+ "/missing.txt"; + this.versionProvider = versionProvider; + } + + private void readLine(String line, Map<Type, String> classes, Map<FullyQualifiedMemberNameAndDesc, String> members) throws IOException { + String[] parts = line.split(";", 3); + if (parts.length != 3) throw new IOException("Invalid line: " + line); + String mod = parts[0]; + String name = parts[1] + ";"; + if (parts[2].equals(";")) { + classes.put(Type.getType(name), mod); + } else { + String[] nameAndDesc = parts[2].split(";", 2); + if (nameAndDesc.length != 2) throw new IOException("Invalid line: " + line); + String desc = nameAndDesc[1].substring(0, nameAndDesc[1].lastIndexOf(";")); + members.put(new FullyQualifiedMemberNameAndDesc(Type.getType(name), nameAndDesc[0], Type.getType(desc)), mod); + } + } + + private synchronized void load() { + try { + if (classes != null) return; + Map<Type, String> classes = new LinkedHashMap<>(); + Map<FullyQualifiedMemberNameAndDesc, String> members = new LinkedHashMap<>(); + try (InputStream is = versionProvider.getClass().getResourceAsStream(path)) { + if (is != null) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) { + String line; + while ((line = reader.readLine()) != null) { + readLine(line, classes, members); + } + } + } + } + this.classes = classes; + this.members = members; + } catch (IOException e) { + Utils.<RuntimeException>sneakyThrow(e); + } + } + + @Nullable + public String checkClass(Type type) { + load(); + return classes.get(type); + } + + @Nullable + public String checkMember(FullyQualifiedMemberNameAndDesc member) { + load(); + return members.get(member); + } + + public void warnClass(Type type, Set<String> warnings) { + String mod = checkClass(type); + if (mod != null) { + warnings.add("Class " + type.getClassName() + " from " + mod + " in " + javaVersion + " is not covered by the api stubs"); + } + } + + public void warnMember(FullyQualifiedMemberNameAndDesc member, Set<String> warnings) { + String mod = checkMember(member); + if (mod != null) { + warnings.add("Member " + member + " from " + mod + " in " + javaVersion + " is not covered by the api stubs"); + } + } + +} diff --git a/src/main/java/xyz/wagyourtail/jvmdg/version/VersionProvider.java b/src/main/java/xyz/wagyourtail/jvmdg/version/VersionProvider.java index 96342e6c..392f4f05 100644 --- a/src/main/java/xyz/wagyourtail/jvmdg/version/VersionProvider.java +++ b/src/main/java/xyz/wagyourtail/jvmdg/version/VersionProvider.java @@ -7,6 +7,7 @@ import xyz.wagyourtail.jvmdg.ClassDowngrader; import xyz.wagyourtail.jvmdg.all.RemovedInterfaces; import xyz.wagyourtail.jvmdg.exc.MissingStubError; +import xyz.wagyourtail.jvmdg.logging.Logger; import xyz.wagyourtail.jvmdg.util.Function; import xyz.wagyourtail.jvmdg.util.IOFunction; import xyz.wagyourtail.jvmdg.util.Lazy; @@ -24,27 +25,26 @@ import java.util.*; public abstract class VersionProvider { - - public final Map<Type, Pair<Type, Pair<Class<?>, Adapter>>> classStubs = new HashMap<>(); - public final Map<Type, ClassMapping> stubMappings = new HashMap<>(); + public final Map<Type, Pair<Type, Pair<Class<?>, Adapter>>> classStubs = new LinkedHashMap<>(); + public final Map<Type, ClassMapping> stubMappings = new LinkedHashMap<>(); public final int inputVersion; public final int outputVersion; + public final Coverage coverage; + /** * lateinit * bound during ensureInit */ protected ClassDowngrader downgrader; + protected Logger logger; private volatile boolean initialized = false; protected VersionProvider(int inputVersion, int outputVersion) { this.inputVersion = inputVersion; this.outputVersion = outputVersion; - } - - public static void main(String[] args) { - System.out.println(Type.getType(boolean.class).getDescriptor()); + this.coverage = new Coverage(inputVersion, this); } public FullyQualifiedMemberNameAndDesc resolveStubTarget(Member member, Ref ref) { @@ -152,22 +152,24 @@ public static FullyQualifiedMemberNameAndDesc resolveModifyTarget(Member member, } public void afterInit() { - if (downgrader.flags.printDebug) { + if (logger.is(Logger.Level.DEBUG)) { + StringBuilder sb = new StringBuilder("Stubs: \n"); for (Map.Entry<Type, Pair<Type, Pair<Class<?>, Adapter>>> stub : classStubs.entrySet()) { - System.out.println(stub.getKey().getInternalName() + " -> " + stub.getValue().getFirst()); + sb.append(stub.getKey().getInternalName()).append(" -> ").append(stub.getValue().getFirst()).append("\n"); } for (Map.Entry<Type, ClassMapping> entry : stubMappings.entrySet()) { for (Map.Entry<MemberNameAndDesc, Pair<Method, Stub>> member : entry.getValue().getMethodStubMap().entrySet()) { - System.out.println(entry.getKey().getInternalName() + "." + member.getKey().getName() + member.getKey().getDesc() + " -> " + member.getValue().getFirst().getDeclaringClass().getCanonicalName().replace('.', '/') + ";" + member.getValue().getFirst().getName() + Type.getMethodDescriptor(member.getValue().getFirst())); + sb.append(entry.getKey().getInternalName()) + .append(".").append(member.getKey().getName()) + .append(member.getKey().getDesc()).append(" -> ") + .append(member.getValue().getFirst().getDeclaringClass().getCanonicalName().replace('.', '/')) + .append(";") + .append(member.getValue().getFirst().getName()) + .append(Type.getMethodDescriptor(member.getValue().getFirst())) + .append("\n"); } } -// for (Map.Entry<Type, Pair<Type, Stub>> entry : classStubs.entrySet()) { -// System.out.println(entry.getKey().getInternalName() + " -> " + entry.getValue().getFirst().getInternalName()); -// } -// for (Map.Entry<String, Pair<Method, Stub>> entry : methodStubs.entrySet()) { -// System.out.println(entry.getKey() + " -> " + entry.getValue().getFirst().getDeclaringClass().getCanonicalName().replace('.', '/') + ";" + entry.getValue().getFirst().getName() + Type.getMethodDescriptor(entry.getValue() -// .getFirst())); -// } + logger.debug(sb.toString()); } } @@ -179,27 +181,27 @@ public void preInit() { public abstract void init(); - public synchronized ClassMapping getStubMapper(Type type) throws IOException { - return getStubMapper(type, downgrader.isInterface(outputVersion, type) == Boolean.TRUE); + public synchronized ClassMapping getStubMapper(Type type, Set<String> warnings) throws IOException { + return getStubMapper(type, downgrader.isInterface(outputVersion, type, warnings) == Boolean.TRUE, warnings); } - public synchronized ClassMapping getStubMapper(Type type, boolean isInterface) throws IOException { + public synchronized ClassMapping getStubMapper(Type type, boolean isInterface, final Set<String> warnings) throws IOException { return getStubMapper(type, isInterface, new IOFunction<Type, Set<MemberNameAndDesc>>() { @Override public Set<MemberNameAndDesc> apply(Type o) throws IOException { - return downgrader.getMembers(inputVersion, o); + return downgrader.getMembers(inputVersion, o, warnings); } - }); + }, warnings); } - public synchronized ClassMapping getStubMapper(Type type, final boolean isInterface, final IOFunction<Type, Set<MemberNameAndDesc>> memberResolver) throws IOException { + public synchronized ClassMapping getStubMapper(Type type, final boolean isInterface, final IOFunction<Type, Set<MemberNameAndDesc>> memberResolver, final Set<String> warnings) throws IOException { return getStubMapper(type, isInterface, memberResolver, new IOFunction<Type, List<Pair<Type, Boolean>>>() { @Override public List<Pair<Type, Boolean>> apply(Type o) throws IOException { - return downgrader.getSupertypes(inputVersion, o); + return downgrader.getSupertypes(inputVersion, o, warnings); } }); @@ -210,7 +212,7 @@ public synchronized ClassMapping getStubMapper(final Type type, final boolean is return stubMappings.get(type); } if (type.getSort() == Type.ARRAY) { - return new ClassMapping(new Lazy<List<ClassMapping>>() { + return new ClassMapping(coverage, new Lazy<List<ClassMapping>>() { @Override public List<ClassMapping> init() { try { @@ -222,7 +224,7 @@ public List<ClassMapping> init() { }, type, isInterface, memberResolver, this); } if (type.getInternalName().equals("java/lang/Object")) { - ClassMapping mapping = new ClassMapping(new Lazy<List<ClassMapping>>() { + ClassMapping mapping = new ClassMapping(coverage, new Lazy<List<ClassMapping>>() { @Override public List<ClassMapping> init() { return Collections.emptyList(); @@ -231,14 +233,13 @@ public List<ClassMapping> init() { stubMappings.put(type, mapping); return mapping; } - ClassMapping mapping = new ClassMapping(new Lazy<List<ClassMapping>>() { + ClassMapping mapping = new ClassMapping(coverage, new Lazy<List<ClassMapping>>() { @Override public List<ClassMapping> init() { try { List<Pair<Type, Boolean>> types = superTypeResolver.apply(type); if (types == null) { - if (!downgrader.flags.quiet) - System.err.println(VersionProvider.this.getClass().getName() + " Could not find class " + type.getInternalName()); + logger.error("Could not find class " + type.getInternalName()); types = Collections.emptyList(); } List<ClassMapping> superTypes = new ArrayList<>(); @@ -256,6 +257,7 @@ public List<ClassMapping> init() { } public void stub(Class<?> clazz) { + Set<String> warnings = new LinkedHashSet<>(); try { if (clazz.isAnnotationPresent(Adapter.class)) { Adapter stub = clazz.getAnnotation(Adapter.class); @@ -292,7 +294,7 @@ public void stub(Class<?> clazz) { FullyQualifiedMemberNameAndDesc target = resolveStubTarget(method, stub.ref()); Type owner = target.getOwner(); MemberNameAndDesc member = target.toMemberNameAndDesc(); - getStubMapper(owner).addStub(member, method, stub); + getStubMapper(owner, warnings).addStub(member, method, stub); } else if (method.isAnnotationPresent(Modify.class)) { Modify modify = method.getAnnotation(Modify.class); FullyQualifiedMemberNameAndDesc target = resolveModifyTarget(method, modify.ref()); @@ -308,20 +310,14 @@ public void stub(Class<?> clazz) { throw new IllegalArgumentException("Class " + clazz.getName() + ", @Modify method " + method.getName() + " parameter " + i + " must be of type " + Modify.MODIFY_SIG[i].getName()); } } - getStubMapper(owner).addModify(member, method, modify); + getStubMapper(owner, warnings).addModify(member, method, modify); } } catch (Throwable e) { - if (!downgrader.flags.quiet) { - System.out.println("ERROR: failed to create stub for " + clazz.getName() + " (" + e.getMessage().split("\n")[0] + ")"); - e.printStackTrace(System.err); - } + logger.warn("failed to create stub for " + clazz.getName(), e); } } } catch (Throwable e) { - if (!downgrader.flags.quiet) { - System.out.println("ERROR: failed to resolve methods for " + clazz.getName()); - e.printStackTrace(System.err); - } + logger.warn("failed to resolve methods for " + clazz.getName(), e); } try { // inner classes @@ -329,20 +325,21 @@ public void stub(Class<?> clazz) { stub(inner); } } catch (Throwable e) { - if (!downgrader.flags.quiet) { - System.out.println("ERROR: failed to resolve inner classes for " + clazz.getName()); - e.printStackTrace(System.err); - } + logger.warn("failed to resolve inner classes for " + clazz.getName(), e); } } catch (Throwable e) { - if (!downgrader.flags.quiet) { - System.out.println("ERROR: failed to create stub(s) for " + clazz.getName()); - e.printStackTrace(System.err); + logger.warn("failed to create stub(s) for " + clazz.getName(), e); + } + if (!warnings.isEmpty() && logger.is(Logger.Level.WARN)) { + StringBuilder sb = new StringBuilder(); + for (String warning : warnings) { + sb.append(" ").append(warning).append("\n"); } + logger.warn("Warnings for " + clazz.getName() + " (" + warnings.size() + ") : \n" + sb); } } - public MethodInsnNode stubTypeInsnNode(TypeInsnNode insn) { + public MethodInsnNode stubTypeInsnNode(TypeInsnNode insn, Set<String> warnings) { Type desc = Type.getObjectType(insn.desc); switch (desc.getSort()) { case Type.ARRAY: @@ -350,10 +347,12 @@ public MethodInsnNode stubTypeInsnNode(TypeInsnNode insn) { if (classStubs.containsKey(type)) { type = classStubs.get(type).getFirst(); } + coverage.warnClass(type, warnings); insn.desc = Type.getType(desc.getDescriptor().substring(0, desc.getDimensions()) + type.getDescriptor()).getInternalName(); return null; case Type.OBJECT: - if (classStubs.containsKey(Type.getObjectType(insn.desc))) { + Type t = Type.getObjectType(insn.desc); + if (classStubs.containsKey(t)) { Pair<Type, Pair<Class<?>, Adapter>> stub = classStubs.get(Type.getObjectType(insn.desc)); // check if clazz has method `jvmdg$opcode switch (insn.getOpcode()) { @@ -374,21 +373,23 @@ public MethodInsnNode stubTypeInsnNode(TypeInsnNode insn) { default: insn.desc = stub.getFirst().getInternalName(); } + return null; } + coverage.warnClass(t, warnings); break; } return null; } - public Type stubClass(Type desc) { + public Type stubClass(Type desc, Set<String> warnings) { switch (desc.getSort()) { case Type.METHOD: Type[] args = desc.getArgumentTypes(); Type ret = desc.getReturnType(); for (int i = 0; i < args.length; i++) { - args[i] = stubClass(args[i]); + args[i] = stubClass(args[i], warnings); } - ret = stubClass(ret); + ret = stubClass(ret, warnings); return Type.getMethodType(ret, args); case Type.ARRAY: Type type = desc.getElementType(); @@ -400,6 +401,7 @@ public Type stubClass(Type desc) { if (classStubs.containsKey(desc)) { return classStubs.get(desc).getFirst(); } +// coverage.warnClass(desc, warnings); return desc; default: return desc; @@ -412,55 +414,63 @@ public ClassNode stubMethods(ClassNode owner, Set<ClassNode> extra, boolean enab } for (MethodNode method : new ArrayList<>(owner.methods)) { - MethodNode newMethod = stubMethods(method, owner, extra, enableRuntime, memberResolver, superTypeResolver); + Set<String> warnings = new LinkedHashSet<>(); + MethodNode newMethod = stubMethod(method, owner, extra, enableRuntime, memberResolver, superTypeResolver, warnings); if (newMethod != method) { owner.methods.set(owner.methods.indexOf(method), newMethod); } + if (!warnings.isEmpty() && logger.is(Logger.Level.WARN)) { + StringBuilder sb = new StringBuilder(); + for (String warning : warnings) { + sb.append(" ").append(warning).append("\n"); + } + logger.warn("Warnings for " + owner.name + "." + method.name + method.desc + " (" + warnings.size() + ") : \n" + sb); + } } return owner; } - public MethodNode stubMethods(MethodNode method, ClassNode owner, Set<ClassNode> extra, boolean enableRuntime, IOFunction<Type, Set<MemberNameAndDesc>> memberResolver, IOFunction<Type, List<Pair<Type, Boolean>>> superTypeResolver) throws IOException { + public MethodNode stubMethod(MethodNode method, ClassNode owner, Set<ClassNode> extra, boolean enableRuntime, IOFunction<Type, Set<MemberNameAndDesc>> memberResolver, IOFunction<Type, List<Pair<Type, Boolean>>> superTypeResolver, Set<String> warnings) throws IOException { for (int i = 0; i < method.instructions.size(); i++) { AbstractInsnNode insn = method.instructions.get(i); if (insn instanceof MethodInsnNode) { MethodInsnNode min = (MethodInsnNode) insn; - min.owner = stubClass(Type.getObjectType(min.owner)).getInternalName(); - min.desc = stubClass(Type.getMethodType(min.desc)).getDescriptor(); + min.owner = stubClass(Type.getObjectType(min.owner), warnings).getInternalName(); + min.desc = stubClass(Type.getMethodType(min.desc), warnings).getDescriptor(); if (!min.owner.startsWith("[")) { - getStubMapper(Type.getObjectType(min.owner), min.itf, memberResolver, superTypeResolver).transform(method, i, owner, extra, enableRuntime); + getStubMapper(Type.getObjectType(min.owner), min.itf, memberResolver, superTypeResolver).transform(method, i, owner, extra, enableRuntime, warnings); } } else if (insn instanceof TypeInsnNode) { TypeInsnNode tin = (TypeInsnNode) insn; - MethodInsnNode min = stubTypeInsnNode(tin); + MethodInsnNode min = stubTypeInsnNode(tin, warnings); if (min != null) { method.instructions.set(tin, min); } } else if (insn instanceof FieldInsnNode) { FieldInsnNode fin = (FieldInsnNode) insn; - fin.owner = stubClass(Type.getObjectType(fin.owner)).getInternalName(); - fin.desc = stubClass(Type.getType(fin.desc)).getDescriptor(); + fin.owner = stubClass(Type.getObjectType(fin.owner), warnings).getInternalName(); + fin.desc = stubClass(Type.getType(fin.desc), warnings).getDescriptor(); //TODO: field stubs (upgrade to method) } else if (insn instanceof InvokeDynamicInsnNode) { InvokeDynamicInsnNode indy = (InvokeDynamicInsnNode) insn; - indy.desc = stubClass(Type.getMethodType(indy.desc)).getDescriptor(); + indy.desc = stubClass(Type.getMethodType(indy.desc), warnings).getDescriptor(); indy.bsm = new Handle( - indy.bsm.getTag(), - stubClass(Type.getObjectType(indy.bsm.getOwner())).getInternalName(), - indy.bsm.getName(), - stubClass(Type.getMethodType(indy.bsm.getDesc())).getDescriptor(), - indy.bsm.isInterface() + indy.bsm.getTag(), + stubClass(Type.getObjectType(indy.bsm.getOwner()), warnings).getInternalName(), + indy.bsm.getName(), + stubClass(Type.getMethodType(indy.bsm.getDesc()), warnings).getDescriptor(), + indy.bsm.isInterface() ); for (int j = 0; j < indy.bsmArgs.length; j++) { Object arg = indy.bsmArgs[j]; if (arg instanceof Handle) { Handle handle = (Handle) arg; handle = new Handle( - handle.getTag(), - stubClass(Type.getObjectType(handle.getOwner())).getInternalName(), - handle.getName(), - stubClass(Type.getType(handle.getDesc())).getDescriptor(), - handle.isInterface() + handle.getTag(), + stubClass(Type.getObjectType(handle.getOwner()), warnings).getInternalName(), + handle.getName(), + stubClass(Type.getType(handle.getDesc()), warnings).getDescriptor(), + handle.isInterface() ); indy.bsmArgs[j] = handle; switch (handle.getTag()) { @@ -488,10 +498,10 @@ public MethodNode stubMethods(MethodNode method, ClassNode owner, Set<ClassNode> ClassMapping stubMapper = getStubMapper(hOwner, handle.isInterface(), memberResolver, superTypeResolver); boolean isStatic = handle.getTag() == Opcodes.H_INVOKESTATIC; boolean isSpecial = handle.getTag() == Opcodes.H_INVOKESPECIAL || handle.getTag() == Opcodes.H_NEWINVOKESPECIAL; - Pair<Method, Stub> min = stubMapper.getStubFor(member, isStatic, enableRuntime, isSpecial); + Pair<Method, Stub> min = stubMapper.getStubFor(member, isStatic, enableRuntime, isSpecial, warnings); if (min != null) { if (min.getSecond().downgradeVersion()) { - System.err.println("Invalid stub for indy handle: " + handle.getOwner() + "." + handle.getName() + handle.getDesc()); + warnings.add("Invalid stub for indy handle: " + handle.getOwner() + "." + handle.getName() + handle.getDesc()); } else if (!min.getSecond().abstractDefault()) { Type hStaticDesc; if (isStatic) { @@ -523,10 +533,10 @@ public MethodNode stubMethods(MethodNode method, ClassNode owner, Set<ClassNode> if (mn instanceof HandleMethodNode) { Handle h = ((HandleMethodNode) mn).ref; if (h.getTag() == handle.getTag() && - h.getOwner().equals(handle.getOwner()) && - h.getName().equals(handle.getName()) && - h.getDesc().equals(handle.getDesc()) && - h.isInterface() == handle.isInterface()) { + h.getOwner().equals(handle.getOwner()) && + h.getName().equals(handle.getName()) && + h.getDesc().equals(handle.getDesc()) && + h.isInterface() == handle.isInterface()) { if (!mn.desc.equals(hStaticDesc.getDescriptor())) { num++; } else { @@ -564,18 +574,18 @@ public MethodNode stubMethods(MethodNode method, ClassNode owner, Set<ClassNode> } } indy.bsmArgs[j] = new Handle( - Opcodes.H_INVOKESTATIC, - newOwner, - name, - desc, - intf + Opcodes.H_INVOKESTATIC, + newOwner, + name, + desc, + intf ); } } else { - Pair<Method, Modify> mod = stubMapper.getModifyFor(member, isStatic); + Pair<Method, Modify> mod = stubMapper.getModifyFor(member, isStatic, warnings); if (mod != null) { if (handle.getTag() != Opcodes.H_NEWINVOKESPECIAL) { - System.err.println("Invalid modify for indy handle: " + handle.getOwner() + "." + handle.getName() + handle.getDesc()); + warnings.add("Invalid modify for indy handle: " + handle.getOwner() + "." + handle.getName() + handle.getDesc()); } else { Type returnType = Type.getObjectType(handle.getOwner()); Type[] arguments = hDesc.getArgumentTypes(); @@ -590,10 +600,10 @@ public MethodNode stubMethods(MethodNode method, ClassNode owner, Set<ClassNode> if (mn instanceof HandleMethodNode) { Handle h = ((HandleMethodNode) mn).ref; if (h.getTag() == handle.getTag() && - h.getOwner().equals(handle.getOwner()) && - h.getName().equals(handle.getName()) && - h.getDesc().equals(handle.getDesc()) && - h.isInterface() == handle.isInterface()) { + h.getOwner().equals(handle.getOwner()) && + h.getName().equals(handle.getName()) && + h.getDesc().equals(handle.getDesc()) && + h.isInterface() == handle.isInterface()) { if (!mn.desc.equals(desc)) { num++; } else { @@ -641,11 +651,11 @@ public MethodNode stubMethods(MethodNode method, ClassNode owner, Set<ClassNode> } indy.bsmArgs[j] = new Handle( - Opcodes.H_INVOKESTATIC, - owner.name, - name, - desc, - intf + Opcodes.H_INVOKESTATIC, + owner.name, + name, + desc, + intf ); } @@ -656,17 +666,17 @@ public MethodNode stubMethods(MethodNode method, ClassNode owner, Set<ClassNode> } else if (arg instanceof Type) { Type type = (Type) arg; - indy.bsmArgs[j] = stubClass(type); + indy.bsmArgs[j] = stubClass(type, warnings); } } - getStubMapper(Type.getObjectType(indy.bsm.getOwner()), indy.bsm.isInterface(), memberResolver, superTypeResolver).transform(method, i, owner, extra, enableRuntime); + getStubMapper(Type.getObjectType(indy.bsm.getOwner()), indy.bsm.isInterface(), memberResolver, superTypeResolver).transform(method, i, owner, extra, enableRuntime, warnings); } else if (insn instanceof MultiANewArrayInsnNode) { MultiANewArrayInsnNode manain = (MultiANewArrayInsnNode) insn; - manain.desc = stubClass(Type.getType(manain.desc)).getDescriptor(); + manain.desc = stubClass(Type.getType(manain.desc), warnings).getDescriptor(); } else if (insn instanceof LdcInsnNode) { LdcInsnNode ldc = (LdcInsnNode) insn; if (ldc.cst instanceof Type) { - ldc.cst = stubClass((Type) ldc.cst); + ldc.cst = stubClass((Type) ldc.cst, warnings); } else if (ldc.cst instanceof ConstantDynamic) { ConstantDynamic condy = (ConstantDynamic) ldc.cst; Handle bsm = condy.getBootstrapMethod(); @@ -678,7 +688,7 @@ public MethodNode stubMethods(MethodNode method, ClassNode owner, Set<ClassNode> for (int j = 0; j < fn.local.size(); j++) { Object o = fn.local.get(j); if (o instanceof String) { - fn.local.set(j, stubClass(Type.getObjectType((String) o)).getInternalName()); + fn.local.set(j, stubClass(Type.getObjectType((String) o), warnings).getInternalName()); } } } @@ -686,7 +696,7 @@ public MethodNode stubMethods(MethodNode method, ClassNode owner, Set<ClassNode> for (int j = 0; j < fn.stack.size(); j++) { Object o = fn.stack.get(j); if (o instanceof String) { - fn.stack.set(j, stubClass(Type.getObjectType((String) o)).getInternalName()); + fn.stack.set(j, stubClass(Type.getObjectType((String) o), warnings).getInternalName()); } } } @@ -697,6 +707,7 @@ public MethodNode stubMethods(MethodNode method, ClassNode owner, Set<ClassNode> public void ensureInit(ClassDowngrader downgrader) { this.downgrader = downgrader; + this.logger = downgrader.logger.subLogger(getClass()); if (!initialized) { synchronized (this) { if (!initialized) { @@ -722,11 +733,12 @@ public ClassNode downgrade(final ClassDowngrader downgrader, ClassNode clazz, fi throw new IllegalArgumentException("Class " + clazz.name + " is not version " + inputVersion); ensureInit(downgrader); + final Set<String> warnings = new LinkedHashSet<>(); final IOFunction<Type, Set<MemberNameAndDesc>> getMembers = new IOFunction<Type, Set<MemberNameAndDesc>>() { @Override public Set<MemberNameAndDesc> apply(Type o) throws IOException { - Set<MemberNameAndDesc> members = downgrader.getMembers(inputVersion, o); + Set<MemberNameAndDesc> members = downgrader.getMembers(inputVersion, o, warnings); // if not found in the classloader, check getReadOnly for it. this should really only happen with the ZipDowngrader if (members == null) { ClassNode ro = getReadOnly.apply(o.getInternalName()); @@ -734,7 +746,7 @@ public Set<MemberNameAndDesc> apply(Type o) throws IOException { members = new HashSet<>(); for (MethodNode method : ro.methods) { if ((method.access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_PRIVATE)) != 0) continue; - members.add(new MemberNameAndDesc(method.name, downgrader.stubClass(ro.version, Type.getMethodType(method.desc)))); + members.add(new MemberNameAndDesc(method.name, downgrader.stubClass(ro.version, Type.getMethodType(method.desc), warnings))); } } } @@ -745,7 +757,7 @@ public Set<MemberNameAndDesc> apply(Type o) throws IOException { members = new HashSet<>(); for (MethodNode method : extraClass.methods) { if ((method.access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_PRIVATE)) != 0) continue; - members.add(new MemberNameAndDesc(method.name, downgrader.stubClass(extraClass.version, Type.getMethodType(method.desc)))); + members.add(new MemberNameAndDesc(method.name, downgrader.stubClass(extraClass.version, Type.getMethodType(method.desc), warnings))); } } } @@ -758,15 +770,15 @@ public Set<MemberNameAndDesc> apply(Type o) throws IOException { @Override public List<Pair<Type, Boolean>> apply(Type o) throws IOException { - List<Pair<Type, Boolean>> types = downgrader.getSupertypes(inputVersion, o); + List<Pair<Type, Boolean>> types = downgrader.getSupertypes(inputVersion, o, warnings); // if not found in the classloader, check getReadOnly for it. this should really only happen with the ZipDowngrader if (types == null) { ClassNode ro = getReadOnly.apply(o.getInternalName()); if (ro != null) { types = new ArrayList<>(); - types.add(new Pair<>(downgrader.stubClass(ro.version, Type.getObjectType(ro.superName)), Boolean.FALSE)); + types.add(new Pair<>(downgrader.stubClass(ro.version, Type.getObjectType(ro.superName), warnings), Boolean.FALSE)); for (String anInterface : ro.interfaces) { - types.add(new Pair<>(downgrader.stubClass(ro.version, Type.getObjectType(anInterface)), Boolean.TRUE)); + types.add(new Pair<>(downgrader.stubClass(ro.version, Type.getObjectType(anInterface), warnings), Boolean.TRUE)); } } } @@ -775,9 +787,9 @@ public List<Pair<Type, Boolean>> apply(Type o) throws IOException { for (ClassNode extraClass : extra) { if (extraClass.name.equals(o.getInternalName())) { types = new ArrayList<>(); - types.add(new Pair<>(downgrader.stubClass(extraClass.version, Type.getObjectType(extraClass.superName)), Boolean.FALSE)); + types.add(new Pair<>(downgrader.stubClass(extraClass.version, Type.getObjectType(extraClass.superName), warnings), Boolean.FALSE)); for (String anInterface : extraClass.interfaces) { - types.add(new Pair<>(downgrader.stubClass(extraClass.version, Type.getObjectType(anInterface)), Boolean.TRUE)); + types.add(new Pair<>(downgrader.stubClass(extraClass.version, Type.getObjectType(anInterface), warnings), Boolean.TRUE)); } } } @@ -786,33 +798,56 @@ public List<Pair<Type, Boolean>> apply(Type o) throws IOException { } }; - clazz = stubClasses(clazz, enableRuntime); - if (clazz == null) return null; + clazz = stubClasses(clazz, enableRuntime, warnings); + if (clazz == null) { + printWarnings(warnings, clazz.name); + return null; + } clazz = stubWithExtras(clazz, extra, new IOFunction<ClassNode, ClassNode>() { @Override public ClassNode apply(ClassNode classNode) throws IOException { return stubMethods(classNode, extra, enableRuntime, getMembers, getSuperTypes); } }); - if (clazz == null) return null; + if (clazz == null) { + printWarnings(warnings, clazz.name); + return null; + } clazz = stubWithExtras(clazz, extra, new IOFunction<ClassNode, ClassNode>() { @Override public ClassNode apply(ClassNode classNode) throws IOException { return insertAbstractMethods(classNode, extra, getMembers, getSuperTypes); } }); - if (clazz == null) return null; + if (clazz == null) { + printWarnings(warnings, clazz.name); + return null; + } clazz = stubWithExtras(clazz, extra, new IOFunction<ClassNode, ClassNode>() { @Override public ClassNode apply(ClassNode classNode) throws IOException { - return otherTransforms(classNode, extra, getReadOnly); + return otherTransforms(classNode, extra, getReadOnly, warnings); } }); - if (clazz == null) return null; + if (clazz == null) { + printWarnings(warnings, clazz.name); + return null; + } + printWarnings(warnings, clazz.name); clazz.version = inputVersion - 1; return clazz; } + private void printWarnings(Set<String> warnings, String className) { + if (!warnings.isEmpty() && logger.is(Logger.Level.WARN)) { + StringBuilder sb = new StringBuilder(); + for (String warning : warnings) { + sb.append(" ").append(warning).append("\n"); + } + logger.warn("Warnings for " + className + " (" + warnings.size() + ") : \n" + sb); + } + } + public ClassNode stubWithExtras(ClassNode clazz, Set<ClassNode> extra, IOFunction<ClassNode, ClassNode> stubber) throws IOException { clazz = stubber.apply(clazz); if (clazz == null) return null; @@ -862,6 +897,11 @@ public ClassNode insertAbstractMethods(ClassNode clazz, Set<ClassNode> extra, IO return clazz; } + public ClassNode otherTransforms(ClassNode clazz, Set<ClassNode> extra, Function<String, ClassNode> getReadOnly, Set<String> warnings) { + clazz = otherTransforms(clazz, extra, getReadOnly); + return clazz; + } + public ClassNode otherTransforms(ClassNode clazz, Set<ClassNode> extra, Function<String, ClassNode> getReadOnly) { clazz = otherTransforms(clazz, extra); return clazz; @@ -876,7 +916,7 @@ public ClassNode otherTransforms(ClassNode clazz) { return clazz; } - public ClassNode stubClasses(ClassNode clazz, boolean enableRuntime) { + public ClassNode stubClasses(ClassNode clazz, boolean enableRuntime, Set<String> warnings) { if (clazz.name.equals("module-info")) { return clazz; } @@ -928,16 +968,16 @@ public ClassNode stubClasses(ClassNode clazz, boolean enableRuntime) { // signature if (clazz.signature != null) { - clazz.signature = transformSignature(clazz.signature); + clazz.signature = transformSignature(clazz.signature, warnings); } // field descriptor if (clazz.fields != null) { for (FieldNode field : clazz.fields) { type = Type.getType(field.desc); - field.desc = stubClass(type).getDescriptor(); + field.desc = stubClass(type, warnings).getDescriptor(); if (field.signature != null) { - field.signature = transformSignature(field.signature); + field.signature = transformSignature(field.signature, warnings); } } } @@ -946,16 +986,16 @@ public ClassNode stubClasses(ClassNode clazz, boolean enableRuntime) { if (clazz.methods != null) { for (MethodNode method : clazz.methods) { type = Type.getMethodType(method.desc); - method.desc = stubClass(type).getDescriptor(); + method.desc = stubClass(type, warnings).getDescriptor(); if (method.signature != null) { - method.signature = transformSignature(method.signature); + method.signature = transformSignature(method.signature, warnings); } if (method.localVariables != null) { for (LocalVariableNode local : method.localVariables) { type = Type.getType(local.desc); - local.desc = stubClass(type).getDescriptor(); + local.desc = stubClass(type, warnings).getDescriptor(); if (local.signature != null) { - local.signature = transformSignature(local.signature); + local.signature = transformSignature(local.signature, warnings); } } } @@ -963,7 +1003,7 @@ public ClassNode stubClasses(ClassNode clazz, boolean enableRuntime) { for (TryCatchBlockNode tryCatch : method.tryCatchBlocks) { if (tryCatch.type != null) { type = Type.getObjectType(tryCatch.type); - tryCatch.type = stubClass(type).getInternalName(); + tryCatch.type = stubClass(type, warnings).getInternalName(); } } } @@ -972,12 +1012,12 @@ public ClassNode stubClasses(ClassNode clazz, boolean enableRuntime) { return clazz; } - public String transformSignature(String signature) { + public String transformSignature(String signature, final Set<String> warnings) { SignatureReader reader = new SignatureReader(signature); SignatureWriter writer = new SignatureWriter() { @Override public void visitClassType(String name) { - super.visitClassType(stubClass(Type.getObjectType(name)).getInternalName()); + super.visitClassType(stubClass(Type.getObjectType(name), warnings).getInternalName()); } @Override diff --git a/src/main/java/xyz/wagyourtail/jvmdg/version/all/stub/J_L_Class.java b/src/main/java/xyz/wagyourtail/jvmdg/version/all/stub/J_L_Class.java index 8f40c37e..2dd14d0e 100644 --- a/src/main/java/xyz/wagyourtail/jvmdg/version/all/stub/J_L_Class.java +++ b/src/main/java/xyz/wagyourtail/jvmdg/version/all/stub/J_L_Class.java @@ -109,10 +109,11 @@ public static Method[] getMethods(Class<?> clazz, int origVersion) { List<VersionProvider> versionProviders = ClassDowngrader.getCurrentVersionDowngrader().versionProviders(origVersion); List<Method> methods = new ArrayList<>(Arrays.asList(clazz.getMethods())); for (VersionProvider vp : versionProviders) { + Set<String> warnings = new HashSet<>(); if (vp.classStubs.containsKey(target)) { try { ClassDowngrader downgrader = ClassDowngrader.getCurrentVersionDowngrader(); - List<Pair<Method, Stub>> targets = vp.getStubMapper(target, downgrader.isInterface(downgrader.target, target), getMethods, getSuperTypes).getStubTargets(); + List<Pair<Method, Stub>> targets = vp.getStubMapper(target, downgrader.isInterface(downgrader.target, target, warnings), getMethods, getSuperTypes).getStubTargets(); for (Pair<Method, Stub> t : targets) { if (!methods.contains(t.getFirst())) { methods.add(t.getFirst()); @@ -122,6 +123,11 @@ public static Method[] getMethods(Class<?> clazz, int origVersion) { throw new RuntimeException(e); } } + if (!warnings.isEmpty()) { + for (String warning : warnings) { + System.err.println(warning); + } + } } return methods.toArray(new Method[0]); } diff --git a/src/main/java/xyz/wagyourtail/jvmdg/version/map/ClassMapping.java b/src/main/java/xyz/wagyourtail/jvmdg/version/map/ClassMapping.java index 338a96a8..9dfda874 100644 --- a/src/main/java/xyz/wagyourtail/jvmdg/version/map/ClassMapping.java +++ b/src/main/java/xyz/wagyourtail/jvmdg/version/map/ClassMapping.java @@ -7,6 +7,7 @@ import xyz.wagyourtail.jvmdg.util.IOFunction; import xyz.wagyourtail.jvmdg.util.Lazy; import xyz.wagyourtail.jvmdg.util.Pair; +import xyz.wagyourtail.jvmdg.version.Coverage; import xyz.wagyourtail.jvmdg.version.Modify; import xyz.wagyourtail.jvmdg.version.Stub; import xyz.wagyourtail.jvmdg.version.VersionProvider; @@ -25,9 +26,12 @@ public class ClassMapping { private final Map<MemberNameAndDesc, Pair<Method, Stub>> methodStub = new HashMap<>(); private final Map<MemberNameAndDesc, Pair<Method, Modify>> methodModify = new HashMap<>(); - public ClassMapping(final Lazy<List<ClassMapping>> parents, final Type current, final boolean isInterface, final IOFunction<Type, Set<MemberNameAndDesc>> members, VersionProvider vp) { + private final Coverage coverage; + + public ClassMapping(final Coverage coverage, final Lazy<List<ClassMapping>> parents, final Type current, final boolean isInterface, final IOFunction<Type, Set<MemberNameAndDesc>> members, VersionProvider vp) { this.parents = parents; this.current = current; + this.coverage = coverage; this.members = new Lazy<Map<MemberNameAndDesc, Pair<Boolean, Type>>>() { @Override @@ -75,7 +79,18 @@ public void addModify(MemberNameAndDesc member, Method method, Modify modify) { methodModify.put(member, new Pair<>(method, modify)); } - public void transform(MethodNode method, int index, ClassNode classNode, Set<ClassNode> extra, boolean runtimeAvailable) { + public void warnMember(MemberNameAndDesc member, Set<String> warnings) { + FullyQualifiedMemberNameAndDesc fqn = member.toFullyQualified(current); + String mod = coverage.checkMember(fqn); + if (mod != null) { + coverage.warnMember(fqn, warnings); + } + for (ClassMapping parent : parents.get()) { + parent.warnMember(member, warnings); + } + } + + public void transform(MethodNode method, int index, ClassNode classNode, Set<ClassNode> extra, boolean runtimeAvailable, Set<String> warnings) { AbstractInsnNode insn = method.instructions.get(index); if (insn instanceof MethodInsnNode) { MethodInsnNode min = (MethodInsnNode) insn; @@ -83,7 +98,7 @@ public void transform(MethodNode method, int index, ClassNode classNode, Set<Cla boolean isStatic = insn.getOpcode() == Opcodes.INVOKESTATIC; boolean isSpecial = insn.getOpcode() == Opcodes.INVOKESPECIAL; - Pair<Method, Stub> newMin = getStubFor(member, isStatic, runtimeAvailable, isSpecial); + Pair<Method, Stub> newMin = getStubFor(member, isStatic, runtimeAvailable, isSpecial, warnings); Type returnType = Type.getReturnType(min.desc); if (newMin != null) { // handled specially, by inserting a call to the stub in the implementation if it's missing an implementation. @@ -139,11 +154,12 @@ public void transform(MethodNode method, int index, ClassNode classNode, Set<Cla method.instructions.remove(min); return; } - Pair<Method, Modify> m = methodModify.get(member); + Pair<Method, Modify> m = getModifyFor(member, isStatic, warnings); if (m != null) { try { List<Object> modifyArgs = Arrays.asList(method, index, classNode, extra); m.getFirst().invoke(null, modifyArgs.subList(0, m.getFirst().getParameterTypes().length).toArray()); + return; } catch (Throwable e) { throw new RuntimeException(e); } @@ -160,13 +176,14 @@ public void transform(MethodNode method, int index, ClassNode classNode, Set<Cla } catch (Throwable e) { throw new RuntimeException(e); } + return; } } } - public Pair<Method, Stub> getParentStubFor(MemberNameAndDesc member, boolean runtimeAvailable, boolean special) { + public Pair<Method, Stub> getParentStubFor(MemberNameAndDesc member, boolean runtimeAvailable, boolean special, Set<String> warnings) { for (ClassMapping parent : parents.get()) { - Pair<Method, Stub> node = parent.getStubFor(member, false, runtimeAvailable, special); + Pair<Method, Stub> node = parent.getStubFor(member, false, runtimeAvailable, special, warnings); if (node != null) { return node; } @@ -174,7 +191,7 @@ public Pair<Method, Stub> getParentStubFor(MemberNameAndDesc member, boolean run return null; } - public Pair<Method, Stub> getStubFor(MemberNameAndDesc member, boolean invoke_static, boolean runtimeAvailable, boolean special) { + public Pair<Method, Stub> getStubFor(MemberNameAndDesc member, boolean invoke_static, boolean runtimeAvailable, boolean special, Set<String> warnings) { try { Pair<Method, Stub> pair = methodStub.get(member); if (pair == null) { @@ -186,13 +203,14 @@ public Pair<Method, Stub> getStubFor(MemberNameAndDesc member, boolean invoke_st // } return null; } - return getParentStubFor(member, runtimeAvailable, special); + return getParentStubFor(member, runtimeAvailable, special, warnings); } + warnMember(member, warnings); return null; } Method m = pair.getFirst(); if (!runtimeAvailable && pair.getSecond().requiresRuntime()) { - System.err.println("WARNING: " + m + " requires runtime transformation but runtime is not available..."); + warnings.add(m + " requires runtime transformation but runtime is not available..."); } if (special && pair.getSecond().noSpecial()) { return null; @@ -207,9 +225,9 @@ public Pair<Method, Stub> getStubFor(MemberNameAndDesc member, boolean invoke_st } } - public Pair<Method, Modify> getParentModifyFor(MemberNameAndDesc member) { + public Pair<Method, Modify> getParentModifyFor(MemberNameAndDesc member, Set<String> warnings) { for (ClassMapping parent : parents.get()) { - Pair<Method, Modify> node = parent.getModifyFor(member, false); + Pair<Method, Modify> node = parent.getModifyFor(member, false, warnings); if (node != null) { return node; } @@ -217,7 +235,7 @@ public Pair<Method, Modify> getParentModifyFor(MemberNameAndDesc member) { return null; } - public Pair<Method, Modify> getModifyFor(MemberNameAndDesc member, boolean invoke_static) { + public Pair<Method, Modify> getModifyFor(MemberNameAndDesc member, boolean invoke_static, Set<String> warnings) { try { Pair<Method, Modify> pair = methodModify.get(member); if (pair == null) { @@ -226,8 +244,9 @@ public Pair<Method, Modify> getModifyFor(MemberNameAndDesc member, boolean invok if (members != null && members.containsKey(member)) { return null; } - return getParentModifyFor(member); + return getParentModifyFor(member, warnings); } + warnMember(member, warnings); return null; } Method m = pair.getFirst(); diff --git a/src/shared/java/xyz/wagyourtail/jvmdg/util/Utils.java b/src/shared/java/xyz/wagyourtail/jvmdg/util/Utils.java index a586d6a6..f7aa913d 100644 --- a/src/shared/java/xyz/wagyourtail/jvmdg/util/Utils.java +++ b/src/shared/java/xyz/wagyourtail/jvmdg/util/Utils.java @@ -1,5 +1,6 @@ package xyz.wagyourtail.jvmdg.util; +import org.objectweb.asm.Opcodes; import sun.misc.Unsafe; import java.io.ByteArrayOutputStream; @@ -71,6 +72,11 @@ public static int getCurrentClassVersion() { throw new UnsupportedOperationException("Unable to determine current class version"); } + public static int classVersionToMajorVersion(int version) { + if (version == Opcodes.V1_1) return 1; + else return version - Opcodes.V1_2 + 2; + } + public static <T extends Throwable> void sneakyThrow(Throwable t) throws T { throw (T) t; } diff --git a/src/test/java/xyz/wagyourtail/jvmdg/internal/JvmDowngraderTest.java b/src/test/java/xyz/wagyourtail/jvmdg/internal/JvmDowngraderTest.java index 8e444fdd..3c0b6866 100644 --- a/src/test/java/xyz/wagyourtail/jvmdg/internal/JvmDowngraderTest.java +++ b/src/test/java/xyz/wagyourtail/jvmdg/internal/JvmDowngraderTest.java @@ -170,7 +170,7 @@ private void testDowngrade(String mainClass, boolean eq) throws Exception { new String[]{ "-a", javaApi.toString(), - "--quiet", +// "--quiet", "bootstrap", "--classpath", original.toString(),