| // Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| package desugaredlibrary; |
| |
| import static desugaredlibrary.AsmRewriter.ASM_VERSION; |
| import static desugaredlibrary.CustomConversionAsmRewriteDescription.CONVERT; |
| import static desugaredlibrary.CustomConversionAsmRewriteDescription.INVERTED_WRAP_CONVERT; |
| import static desugaredlibrary.CustomConversionAsmRewriteDescription.WRAP_CONVERT; |
| import static desugaredlibrary.CustomConversionAsmRewriter.CustomConversionVersion.LEGACY; |
| import static org.objectweb.asm.Opcodes.INVOKESTATIC; |
| |
| import com.google.common.io.ByteStreams; |
| import desugaredlibrary.AsmRewriter.MethodTransformer; |
| import java.io.BufferedOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.nio.charset.StandardCharsets; |
| import java.nio.file.Files; |
| import java.nio.file.OpenOption; |
| import java.nio.file.Path; |
| import java.nio.file.StandardOpenOption; |
| import java.util.Enumeration; |
| import java.util.Map; |
| import java.util.zip.CRC32; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipFile; |
| import java.util.zip.ZipOutputStream; |
| |
| public class CustomConversionAsmRewriter { |
| |
| public enum CustomConversionVersion { |
| LEGACY, |
| LATEST |
| } |
| |
| public CustomConversionAsmRewriter(CustomConversionVersion legacy) { |
| this.legacy = legacy; |
| } |
| |
| private final CustomConversionVersion legacy; |
| private final Map<String, String> javaWrapConvertOwnerMap = |
| CustomConversionAsmRewriteDescription.getJavaWrapConvertOwnerMap(); |
| private final Map<String, String> j$WrapConvertOwnerMap = |
| CustomConversionAsmRewriteDescription.getJ$WrapConvertOwnerMap(); |
| |
| public static void generateJars(Path jar, Path outputDirectory) throws IOException { |
| for (CustomConversionVersion version : CustomConversionVersion.values()) { |
| new CustomConversionAsmRewriter(version).convert(jar, outputDirectory); |
| } |
| } |
| |
| private void convert(Path jar, Path outputDirectory) throws IOException { |
| String fileName = jar.getFileName().toString(); |
| String newFileName = |
| fileName.substring(0, fileName.length() - "_raw.jar".length()) |
| + (legacy == LEGACY ? "_legacy" : "") |
| + ".jar"; |
| Path convertedJar = outputDirectory.resolve(newFileName); |
| internalConvert(jar, convertedJar); |
| } |
| |
| private void internalConvert(Path jar, Path convertedJar) throws IOException { |
| OpenOption[] options = |
| new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING}; |
| try (ZipOutputStream out = |
| new ZipOutputStream( |
| new BufferedOutputStream(Files.newOutputStream(convertedJar, options)))) { |
| new CustomConversionAsmRewriter(legacy).convert(jar, out, legacy); |
| } |
| } |
| |
| private void convert( |
| Path desugaredLibraryFiles, ZipOutputStream out, CustomConversionVersion legacy) |
| throws IOException { |
| try (ZipFile zipFile = new ZipFile(desugaredLibraryFiles.toFile(), StandardCharsets.UTF_8)) { |
| final Enumeration<? extends ZipEntry> entries = zipFile.entries(); |
| while (entries.hasMoreElements()) { |
| ZipEntry entry = entries.nextElement(); |
| try (InputStream entryStream = zipFile.getInputStream(entry)) { |
| handleFile(entry, entryStream, out, legacy); |
| } |
| } |
| } |
| } |
| |
| private void handleFile( |
| ZipEntry entry, InputStream input, ZipOutputStream out, CustomConversionVersion legacy) |
| throws IOException { |
| if (!entry.getName().endsWith(".class")) { |
| return; |
| } |
| if (legacy == LEGACY |
| && (entry.getName().contains("java/nio/file") |
| || entry.getName().contains("ApiFlips") |
| || entry.getName().contains("java/adapter"))) { |
| return; |
| } |
| final byte[] bytes = ByteStreams.toByteArray(input); |
| input.close(); |
| final byte[] rewrittenBytes = transformInvoke(bytes); |
| writeToZipStream(out, entry.getName(), rewrittenBytes, ZipEntry.STORED); |
| } |
| |
| public static void writeToZipStream( |
| ZipOutputStream stream, String entry, byte[] content, int compressionMethod) |
| throws IOException { |
| int offset = 0; |
| int length = content.length; |
| CRC32 crc = new CRC32(); |
| crc.update(content, offset, length); |
| ZipEntry zipEntry = new ZipEntry(entry); |
| zipEntry.setMethod(compressionMethod); |
| zipEntry.setSize(length); |
| zipEntry.setCrc(crc.getValue()); |
| zipEntry.setTime(0); |
| stream.putNextEntry(zipEntry); |
| stream.write(content, offset, length); |
| stream.closeEntry(); |
| } |
| |
| private byte[] transformInvoke(byte[] bytes) { |
| return AsmRewriter.transformInvoke(bytes, new CustomConversionRewriter(ASM_VERSION)); |
| } |
| |
| class CustomConversionRewriter extends MethodTransformer { |
| |
| protected CustomConversionRewriter(int api) { |
| super(api); |
| } |
| |
| @Override |
| public void visitMethodInsn( |
| int opcode, String owner, String name, String descriptor, boolean isInterface) { |
| if (opcode == INVOKESTATIC) { |
| if (name.equals(WRAP_CONVERT)) { |
| convertInvoke( |
| opcode, |
| owner, |
| descriptor, |
| isInterface, |
| javaWrapConvertOwnerMap, |
| j$WrapConvertOwnerMap); |
| return; |
| } |
| if (name.equals(INVERTED_WRAP_CONVERT)) { |
| convertInvoke( |
| opcode, |
| owner, |
| descriptor, |
| isInterface, |
| j$WrapConvertOwnerMap, |
| javaWrapConvertOwnerMap); |
| return; |
| } |
| } |
| super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); |
| } |
| |
| private void convertInvoke( |
| int opcode, |
| String owner, |
| String descriptor, |
| boolean isInterface, |
| Map<String, String> javaMap, |
| Map<String, String> j$Map) { |
| if (!javaMap.containsKey(owner) |
| || !j$Map.containsKey(owner) |
| || !(owner.startsWith("java") || owner.startsWith("j$"))) { |
| throw new RuntimeException("Cannot transform wrap_convert method for " + owner); |
| } |
| if (owner.startsWith("java")) { |
| String newOwner = j$Map.get(owner); |
| super.visitMethodInsn(opcode, newOwner, CONVERT, descriptor, isInterface); |
| return; |
| } |
| assert owner.startsWith("j$"); |
| String newOwner = javaMap.get(owner); |
| super.visitMethodInsn(opcode, newOwner, CONVERT, descriptor, isInterface); |
| } |
| } |
| } |