blob: 95a5ecc0c3b00975d1e7d472b092a1eae5ed4647 [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;
Clément Bérac8b2c152022-06-22 08:23:47 +02009import static desugaredlibrary.CustomConversionAsmRewriteDescription.WRAP_CONVERT;
10import static desugaredlibrary.CustomConversionAsmRewriter.CustomConversionVersion.LEGACY;
11import static org.objectweb.asm.Opcodes.INVOKESTATIC;
12
13import com.google.common.io.ByteStreams;
14import desugaredlibrary.AsmRewriter.MethodTransformer;
15import java.io.BufferedOutputStream;
16import java.io.IOException;
17import java.io.InputStream;
18import java.nio.charset.StandardCharsets;
19import java.nio.file.Files;
20import java.nio.file.OpenOption;
21import java.nio.file.Path;
22import java.nio.file.StandardOpenOption;
23import java.util.Enumeration;
24import java.util.Map;
25import java.util.zip.CRC32;
26import java.util.zip.ZipEntry;
27import java.util.zip.ZipFile;
28import java.util.zip.ZipOutputStream;
29
30public 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éra45fd3fd2022-11-28 13:49:21 +010042 private final Map<String, String> wrapConvertOwnerMap =
43 CustomConversionAsmRewriteDescription.getWrapConvertOwnerMap();
Clément Bérac8b2c152022-06-22 08:23:47 +020044
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 Gjessee18fa6e2022-06-24 15:14:53 +020092 && (entry.getName().contains("java/nio/file")
Clément Bérac8b2c152022-06-22 08:23:47 +020093 || entry.getName().contains("ApiFlips")
Søren Gjessee18fa6e2022-06-24 15:14:53 +020094 || entry.getName().contains("java/adapter"))) {
Clément Bérac8b2c152022-06-22 08:23:47 +020095 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éra77eb2912022-10-12 12:38:05 +0200133 if (opcode == INVOKESTATIC && name.equals(WRAP_CONVERT)) {
134 convertInvoke(opcode, owner, descriptor, isInterface);
135 return;
Clément Bérac8b2c152022-06-22 08:23:47 +0200136 }
137 super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
138 }
139
Clément Béra77eb2912022-10-12 12:38:05 +0200140 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éra45fd3fd2022-11-28 13:49:21 +0100149 if (!wrapConvertOwnerMap.containsKey(firstArg)
Clément Béra77eb2912022-10-12 12:38:05 +0200150 || !(firstArg.startsWith("java") || firstArg.startsWith("j$"))) {
151 throw new RuntimeException(
152 "Cannot transform wrap_convert method for " + firstArg + " (owner: " + owner + ")");
Clément Bérac8b2c152022-06-22 08:23:47 +0200153 }
Clément Béra45fd3fd2022-11-28 13:49:21 +0100154 String newOwner = wrapConvertOwnerMap.get(firstArg);
Clément Bérac8b2c152022-06-22 08:23:47 +0200155 super.visitMethodInsn(opcode, newOwner, CONVERT, descriptor, isInterface);
156 }
Clément Béra77eb2912022-10-12 12:38:05 +0200157
Clément Bérac8b2c152022-06-22 08:23:47 +0200158 }
159}