blob: c20de96faaebc7ec2b38750b590197d911e09421 [file] [log] [blame]
Clément Bérac8b2c152022-06-22 08:23:47 +02001// 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
5package desugaredlibrary;
6
7import static desugaredlibrary.AsmRewriter.ASM_VERSION;
8import static desugaredlibrary.CustomConversionAsmRewriteDescription.CONVERT;
9import static desugaredlibrary.CustomConversionAsmRewriteDescription.INVERTED_WRAP_CONVERT;
10import static desugaredlibrary.CustomConversionAsmRewriteDescription.WRAP_CONVERT;
11import static desugaredlibrary.CustomConversionAsmRewriter.CustomConversionVersion.LEGACY;
12import static org.objectweb.asm.Opcodes.INVOKESTATIC;
13
14import com.google.common.io.ByteStreams;
15import desugaredlibrary.AsmRewriter.MethodTransformer;
16import java.io.BufferedOutputStream;
17import java.io.IOException;
18import java.io.InputStream;
19import java.nio.charset.StandardCharsets;
20import java.nio.file.Files;
21import java.nio.file.OpenOption;
22import java.nio.file.Path;
23import java.nio.file.StandardOpenOption;
24import java.util.Enumeration;
25import java.util.Map;
26import java.util.zip.CRC32;
27import java.util.zip.ZipEntry;
28import java.util.zip.ZipFile;
29import java.util.zip.ZipOutputStream;
30
31public 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 Gjessee18fa6e2022-06-24 15:14:53 +020095 && (entry.getName().contains("java/nio/file")
Clément Bérac8b2c152022-06-22 08:23:47 +020096 || entry.getName().contains("ApiFlips")
Søren Gjessee18fa6e2022-06-24 15:14:53 +020097 || entry.getName().contains("java/adapter"))) {
Clément Bérac8b2c152022-06-22 08:23:47 +020098 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}