Clément Béra | c8b2c15 | 2022-06-22 08:23:47 +0200 | [diff] [blame] | 1 | // Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file |
| 2 | // for details. All rights reserved. Use of this source code is governed by a |
| 3 | // BSD-style license that can be found in the LICENSE file. |
| 4 | |
| 5 | package desugaredlibrary; |
| 6 | |
| 7 | import static desugaredlibrary.AsmRewriter.ASM_VERSION; |
| 8 | import static desugaredlibrary.CustomConversionAsmRewriteDescription.CONVERT; |
Clément Béra | c8b2c15 | 2022-06-22 08:23:47 +0200 | [diff] [blame] | 9 | import static desugaredlibrary.CustomConversionAsmRewriteDescription.WRAP_CONVERT; |
| 10 | import static desugaredlibrary.CustomConversionAsmRewriter.CustomConversionVersion.LEGACY; |
| 11 | import static org.objectweb.asm.Opcodes.INVOKESTATIC; |
| 12 | |
| 13 | import com.google.common.io.ByteStreams; |
| 14 | import desugaredlibrary.AsmRewriter.MethodTransformer; |
| 15 | import java.io.BufferedOutputStream; |
| 16 | import java.io.IOException; |
| 17 | import java.io.InputStream; |
| 18 | import java.nio.charset.StandardCharsets; |
| 19 | import java.nio.file.Files; |
| 20 | import java.nio.file.OpenOption; |
| 21 | import java.nio.file.Path; |
| 22 | import java.nio.file.StandardOpenOption; |
| 23 | import java.util.Enumeration; |
| 24 | import java.util.Map; |
| 25 | import java.util.zip.CRC32; |
| 26 | import java.util.zip.ZipEntry; |
| 27 | import java.util.zip.ZipFile; |
| 28 | import java.util.zip.ZipOutputStream; |
| 29 | |
| 30 | public class CustomConversionAsmRewriter { |
| 31 | |
| 32 | public enum CustomConversionVersion { |
| 33 | LEGACY, |
| 34 | LATEST |
| 35 | } |
| 36 | |
| 37 | public CustomConversionAsmRewriter(CustomConversionVersion legacy) { |
| 38 | this.legacy = legacy; |
| 39 | } |
| 40 | |
| 41 | private final CustomConversionVersion legacy; |
Clément Béra | 45fd3fd | 2022-11-28 13:49:21 +0100 | [diff] [blame] | 42 | private final Map<String, String> wrapConvertOwnerMap = |
| 43 | CustomConversionAsmRewriteDescription.getWrapConvertOwnerMap(); |
Clément Béra | c8b2c15 | 2022-06-22 08:23:47 +0200 | [diff] [blame] | 44 | |
| 45 | public static void generateJars(Path jar, Path outputDirectory) throws IOException { |
| 46 | for (CustomConversionVersion version : CustomConversionVersion.values()) { |
| 47 | new CustomConversionAsmRewriter(version).convert(jar, outputDirectory); |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | private void convert(Path jar, Path outputDirectory) throws IOException { |
| 52 | String fileName = jar.getFileName().toString(); |
| 53 | String newFileName = |
| 54 | fileName.substring(0, fileName.length() - "_raw.jar".length()) |
| 55 | + (legacy == LEGACY ? "_legacy" : "") |
| 56 | + ".jar"; |
| 57 | Path convertedJar = outputDirectory.resolve(newFileName); |
| 58 | internalConvert(jar, convertedJar); |
| 59 | } |
| 60 | |
| 61 | private void internalConvert(Path jar, Path convertedJar) throws IOException { |
| 62 | OpenOption[] options = |
| 63 | new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING}; |
| 64 | try (ZipOutputStream out = |
| 65 | new ZipOutputStream( |
| 66 | new BufferedOutputStream(Files.newOutputStream(convertedJar, options)))) { |
| 67 | new CustomConversionAsmRewriter(legacy).convert(jar, out, legacy); |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | private void convert( |
| 72 | Path desugaredLibraryFiles, ZipOutputStream out, CustomConversionVersion legacy) |
| 73 | throws IOException { |
| 74 | try (ZipFile zipFile = new ZipFile(desugaredLibraryFiles.toFile(), StandardCharsets.UTF_8)) { |
| 75 | final Enumeration<? extends ZipEntry> entries = zipFile.entries(); |
| 76 | while (entries.hasMoreElements()) { |
| 77 | ZipEntry entry = entries.nextElement(); |
| 78 | try (InputStream entryStream = zipFile.getInputStream(entry)) { |
| 79 | handleFile(entry, entryStream, out, legacy); |
| 80 | } |
| 81 | } |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | private void handleFile( |
| 86 | ZipEntry entry, InputStream input, ZipOutputStream out, CustomConversionVersion legacy) |
| 87 | throws IOException { |
| 88 | if (!entry.getName().endsWith(".class")) { |
| 89 | return; |
| 90 | } |
| 91 | if (legacy == LEGACY |
Søren Gjesse | e18fa6e | 2022-06-24 15:14:53 +0200 | [diff] [blame] | 92 | && (entry.getName().contains("java/nio/file") |
Clément Béra | c8b2c15 | 2022-06-22 08:23:47 +0200 | [diff] [blame] | 93 | || entry.getName().contains("ApiFlips") |
Søren Gjesse | e18fa6e | 2022-06-24 15:14:53 +0200 | [diff] [blame] | 94 | || entry.getName().contains("java/adapter"))) { |
Clément Béra | c8b2c15 | 2022-06-22 08:23:47 +0200 | [diff] [blame] | 95 | return; |
| 96 | } |
| 97 | final byte[] bytes = ByteStreams.toByteArray(input); |
| 98 | input.close(); |
| 99 | final byte[] rewrittenBytes = transformInvoke(bytes); |
| 100 | writeToZipStream(out, entry.getName(), rewrittenBytes, ZipEntry.STORED); |
| 101 | } |
| 102 | |
| 103 | public static void writeToZipStream( |
| 104 | ZipOutputStream stream, String entry, byte[] content, int compressionMethod) |
| 105 | throws IOException { |
| 106 | int offset = 0; |
| 107 | int length = content.length; |
| 108 | CRC32 crc = new CRC32(); |
| 109 | crc.update(content, offset, length); |
| 110 | ZipEntry zipEntry = new ZipEntry(entry); |
| 111 | zipEntry.setMethod(compressionMethod); |
| 112 | zipEntry.setSize(length); |
| 113 | zipEntry.setCrc(crc.getValue()); |
| 114 | zipEntry.setTime(0); |
| 115 | stream.putNextEntry(zipEntry); |
| 116 | stream.write(content, offset, length); |
| 117 | stream.closeEntry(); |
| 118 | } |
| 119 | |
| 120 | private byte[] transformInvoke(byte[] bytes) { |
| 121 | return AsmRewriter.transformInvoke(bytes, new CustomConversionRewriter(ASM_VERSION)); |
| 122 | } |
| 123 | |
| 124 | class CustomConversionRewriter extends MethodTransformer { |
| 125 | |
| 126 | protected CustomConversionRewriter(int api) { |
| 127 | super(api); |
| 128 | } |
| 129 | |
| 130 | @Override |
| 131 | public void visitMethodInsn( |
| 132 | int opcode, String owner, String name, String descriptor, boolean isInterface) { |
Clément Béra | 77eb291 | 2022-10-12 12:38:05 +0200 | [diff] [blame] | 133 | if (opcode == INVOKESTATIC && name.equals(WRAP_CONVERT)) { |
| 134 | convertInvoke(opcode, owner, descriptor, isInterface); |
| 135 | return; |
Clément Béra | c8b2c15 | 2022-06-22 08:23:47 +0200 | [diff] [blame] | 136 | } |
| 137 | super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); |
| 138 | } |
| 139 | |
Clément Béra | 77eb291 | 2022-10-12 12:38:05 +0200 | [diff] [blame] | 140 | private String extractFirstArg(String descriptor) { |
| 141 | assert descriptor.startsWith("(L"); |
| 142 | int end = descriptor.indexOf(';'); |
| 143 | assert end > 2; |
| 144 | return descriptor.substring(2, end); |
| 145 | } |
| 146 | |
| 147 | private void convertInvoke(int opcode, String owner, String descriptor, boolean isInterface) { |
| 148 | String firstArg = extractFirstArg(descriptor); |
Clément Béra | 45fd3fd | 2022-11-28 13:49:21 +0100 | [diff] [blame] | 149 | if (!wrapConvertOwnerMap.containsKey(firstArg) |
Clément Béra | 77eb291 | 2022-10-12 12:38:05 +0200 | [diff] [blame] | 150 | || !(firstArg.startsWith("java") || firstArg.startsWith("j$"))) { |
| 151 | throw new RuntimeException( |
| 152 | "Cannot transform wrap_convert method for " + firstArg + " (owner: " + owner + ")"); |
Clément Béra | c8b2c15 | 2022-06-22 08:23:47 +0200 | [diff] [blame] | 153 | } |
Clément Béra | 45fd3fd | 2022-11-28 13:49:21 +0100 | [diff] [blame] | 154 | String newOwner = wrapConvertOwnerMap.get(firstArg); |
Clément Béra | c8b2c15 | 2022-06-22 08:23:47 +0200 | [diff] [blame] | 155 | super.visitMethodInsn(opcode, newOwner, CONVERT, descriptor, isInterface); |
| 156 | } |
Clément Béra | 77eb291 | 2022-10-12 12:38:05 +0200 | [diff] [blame] | 157 | |
Clément Béra | c8b2c15 | 2022-06-22 08:23:47 +0200 | [diff] [blame] | 158 | } |
| 159 | } |