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