Skip to content

Commit 8d2ccd4

Browse files
Googlercopybara-github
Googler
authored andcommitted
Collect the contexts of D8 compiler-synthesized classes.
This completes steps 2 and 3 in b/241351268#comment9 towards resolving #16368 PiperOrigin-RevId: 530238344 Change-Id: I569bf8e3bfa81f7005f3e9b79338d5a5b868e339
1 parent 076ce3e commit 8d2ccd4

File tree

5 files changed

+170
-2
lines changed

5 files changed

+170
-2
lines changed

src/test/java/com/google/devtools/build/android/r8/BUILD

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,15 @@ java_test(
5151
":arithmetic",
5252
":barray",
5353
":naming001",
54+
":testdata_lambda_desugared.jar",
5455
":twosimpleclasses",
5556
],
5657
jvm_flags = [
5758
"-DCompatDexBuilderTests.twosimpleclasses=$(location :twosimpleclasses)",
5859
"-DCompatDexBuilderTests.naming001=$(location :naming001)",
5960
"-DCompatDxTests.arithmetic=$(location :arithmetic)",
6061
"-DCompatDxTests.barray=$(location :barray)",
62+
"-DCompatDexBuilderTests.lambda=$(location :testdata_lambda_desugared.jar)",
6163
],
6264
runtime_deps = [
6365
":tests",
@@ -103,3 +105,21 @@ java_library(
103105
"-target 8",
104106
],
105107
)
108+
109+
java_library(
110+
name = "testdata_lambda",
111+
srcs = glob(["testdata/lambda/*.java"]),
112+
)
113+
114+
genrule(
115+
name = "desugar_testdata_lambda",
116+
srcs = [
117+
":testdata_lambda",
118+
"@bazel_tools//tools/android:android_jar",
119+
],
120+
outs = ["testdata_lambda_desugared.jar"],
121+
cmd = "$(location //src/tools/android/java/com/google/devtools/build/android/r8:desugar) " +
122+
"-i $(location :testdata_lambda) -o $@ " +
123+
"--bootclasspath_entry $(location @bazel_tools//tools/android:android_jar)",
124+
tools = ["//src/tools/android/java/com/google/devtools/build/android/r8:desugar"],
125+
)

src/test/java/com/google/devtools/build/android/r8/CompatDexBuilderTest.java

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@
2121
import com.android.tools.r8.OutputMode;
2222
import com.google.common.collect.ImmutableList;
2323
import com.google.devtools.common.options.OptionsParsingException;
24+
import java.io.BufferedReader;
2425
import java.io.IOException;
26+
import java.io.InputStreamReader;
2527
import java.nio.file.Files;
2628
import java.nio.file.Path;
2729
import java.util.HashSet;
28-
import java.util.List;
2930
import java.util.Set;
3031
import java.util.concurrent.ExecutionException;
32+
import java.util.zip.ZipEntry;
3133
import java.util.zip.ZipFile;
3234
import org.junit.Rule;
3335
import org.junit.Test;
@@ -45,7 +47,7 @@ public void compileManyClasses()
4547
throws IOException, InterruptedException, ExecutionException, OptionsParsingException {
4648
// Random set of classes from the R8 example test directory naming001.
4749
final String inputJar = System.getProperty("CompatDexBuilderTests.naming001");
48-
final List<String> classNames =
50+
final ImmutableList<String> classNames =
4951
ImmutableList.of(
5052
"A",
5153
"B",
@@ -86,6 +88,40 @@ public void compileManyClasses()
8688
assertThat(expectedNames).isEmpty();
8789
}
8890

91+
@Test
92+
public void compileWithSyntheticLambdas() throws Exception {
93+
final String contextName = "com/google/devtools/build/android/r8/testdata/lambda/Lambda";
94+
final String inputJar = System.getProperty("CompatDexBuilderTests.lambda");
95+
final Path outputZip = temp.getRoot().toPath().resolve("out.zip");
96+
CompatDexBuilder.main(
97+
new String[] {"--input_jar", inputJar, "--output_zip", outputZip.toString()});
98+
assertThat(Files.exists(outputZip)).isTrue();
99+
100+
try (ZipFile zipFile = new ZipFile(outputZip.toFile(), UTF_8)) {
101+
assertThat(zipFile.getEntry(contextName + ".class.dex")).isNotNull();
102+
ZipEntry entry = zipFile.getEntry("META-INF/synthetic-contexts.map");
103+
assertThat(entry).isNotNull();
104+
try (BufferedReader reader =
105+
new BufferedReader(new InputStreamReader(zipFile.getInputStream(entry), UTF_8))) {
106+
String line = reader.readLine();
107+
assertThat(line).isNotNull();
108+
// Format of mapping is: <synthetic-binary-name>;<context-binary-name>\n
109+
int sep = line.indexOf(';');
110+
String syntheticNameInMap = line.substring(0, sep);
111+
String contextNameInMap = line.substring(sep + 1);
112+
// The synthetic will be prefixed by the context type. This checks the synthetic name
113+
// is larger than the context to avoid hardcoding the synthetic names, which may change.
114+
assertThat(syntheticNameInMap).startsWith(contextName);
115+
assertThat(syntheticNameInMap).isNotEqualTo(contextName);
116+
// Check expected context.
117+
assertThat(contextNameInMap).isEqualTo(contextName);
118+
// Only one synthetic and its context should be present.
119+
line = reader.readLine();
120+
assertThat(line).isNull();
121+
}
122+
}
123+
}
124+
89125
@Test
90126
public void compileTwoClassesAndRun() throws Exception {
91127
// Run CompatDexBuilder on dexMergeSample.jar
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2023 The Bazel Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
package com.google.devtools.build.android.r8.testdata.lambda;
15+
16+
import java.util.function.Supplier;
17+
18+
/** Test class */
19+
public final class Lambda {
20+
21+
private Lambda() {}
22+
23+
private static <T> T foo(Supplier<T> fn) {
24+
return fn.get();
25+
}
26+
27+
public static void main(String[] args) {
28+
String unused = foo(() -> "Hello, world!");
29+
}
30+
}

src/tools/android/java/com/google/devtools/build/android/r8/BUILD

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,25 @@ java_library(
8989
"//src/main/java/com/google/devtools/build/lib/worker:work_request_handlers",
9090
],
9191
)
92+
93+
java_binary(
94+
name = "desugar",
95+
jvm_flags = [
96+
# b/71513487
97+
"-XX:+TieredCompilation",
98+
"-XX:TieredStopAtLevel=1",
99+
"-Xms8g",
100+
"-Xmx8g",
101+
# b/172508621
102+
"-Dcom.android.tools.r8.sortMethodsOnCfWriting",
103+
"-Dcom.android.tools.r8.allowAllDesugaredInput",
104+
"-Dcom.android.tools.r8.noCfMarkerForDesugaredCode",
105+
"-Dcom.android.tools.r8.lambdaClassFieldsNotFinal",
106+
"-Dcom.android.tools.r8.createSingletonsForStatelessLambdas",
107+
],
108+
main_class = "com.google.devtools.build.android.r8.Desugar",
109+
visibility = ["//src/test/java/com/google/devtools/build/android/r8:__pkg__"],
110+
runtime_deps = [
111+
":r8",
112+
],
113+
)

src/tools/android/java/com/google/devtools/build/android/r8/CompatDexBuilder.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@
2424
import com.android.tools.r8.D8Command;
2525
import com.android.tools.r8.DexIndexedConsumer;
2626
import com.android.tools.r8.DiagnosticsHandler;
27+
import com.android.tools.r8.SyntheticInfoConsumer;
28+
import com.android.tools.r8.SyntheticInfoConsumerData;
2729
import com.android.tools.r8.origin.ArchiveEntryOrigin;
2830
import com.android.tools.r8.origin.PathOrigin;
31+
import com.android.tools.r8.references.ClassReference;
2932
import com.google.auto.value.AutoValue;
3033
import com.google.common.cache.Cache;
3134
import com.google.common.cache.CacheBuilder;
@@ -67,8 +70,47 @@
6770
public class CompatDexBuilder {
6871
private static final long ONE_MEG = 1024 * 1024;
6972

73+
private static class ContextConsumer implements SyntheticInfoConsumer {
74+
75+
// After compilation this will be non-null iff the compiled class is a D8 synthesized class.
76+
ClassReference sythesizedPrimaryClass = null;
77+
78+
// If the above is non-null then this will be the non-synthesized context class that caused
79+
// D8 to synthesize the above class.
80+
ClassReference contextOfSynthesizedClass = null;
81+
82+
@Nullable
83+
String getContextMapping() {
84+
if (sythesizedPrimaryClass != null) {
85+
return sythesizedPrimaryClass.getBinaryName()
86+
+ ";"
87+
+ contextOfSynthesizedClass.getBinaryName();
88+
}
89+
return null;
90+
}
91+
92+
@Override
93+
public synchronized void acceptSyntheticInfo(SyntheticInfoConsumerData data) {
94+
verify(
95+
sythesizedPrimaryClass == null || sythesizedPrimaryClass.equals(data.getSyntheticClass()),
96+
"The single input classfile should ensure this has one value.");
97+
verify(
98+
contextOfSynthesizedClass == null
99+
|| contextOfSynthesizedClass.equals(data.getSynthesizingContextClass()),
100+
"The single input classfile should ensure this has one value.");
101+
sythesizedPrimaryClass = data.getSyntheticClass();
102+
contextOfSynthesizedClass = data.getSynthesizingContextClass();
103+
}
104+
105+
@Override
106+
public void finished() {
107+
// Do nothing.
108+
}
109+
}
110+
70111
private static class DexConsumer implements DexIndexedConsumer {
71112

113+
final ContextConsumer contextConsumer = new ContextConsumer();
72114
byte[] bytes;
73115

74116
@Override
@@ -86,6 +128,10 @@ void setBytes(byte[] byteCode) {
86128
this.bytes = byteCode;
87129
}
88130

131+
ContextConsumer getContextConsumer() {
132+
return contextConsumer;
133+
}
134+
89135
@Override
90136
public void finished(DiagnosticsHandler handler) {
91137
// Do nothing.
@@ -269,10 +315,23 @@ private void dexEntries(@Nullable Cache<DexingKeyR8, byte[]> dexCache, List<Stri
269315
minSdkVersion,
270316
executor)));
271317
}
318+
StringBuilder contextMappingBuilder = new StringBuilder();
272319
for (int i = 0; i < futures.size(); i++) {
273320
ZipEntry entry = toDex.get(i);
274321
DexConsumer consumer = futures.get(i).get();
275322
ZipUtils.addEntry(entry.getName() + ".dex", consumer.getBytes(), ZipEntry.STORED, out);
323+
String mapping = consumer.getContextConsumer().getContextMapping();
324+
if (mapping != null) {
325+
contextMappingBuilder.append(mapping).append('\n');
326+
}
327+
}
328+
String contextMapping = contextMappingBuilder.toString();
329+
if (!contextMapping.isEmpty()) {
330+
ZipUtils.addEntry(
331+
"META-INF/synthetic-contexts.map",
332+
contextMapping.getBytes(UTF_8),
333+
ZipEntry.STORED,
334+
out);
276335
}
277336
}
278337
} finally {
@@ -292,6 +351,7 @@ private DexConsumer dexEntry(
292351
D8Command.Builder builder = D8Command.builder();
293352
builder
294353
.setProgramConsumer(consumer)
354+
.setSyntheticInfoConsumer(consumer.getContextConsumer())
295355
.setMode(mode)
296356
.setMinApiLevel(minSdkVersion)
297357
.setDisableDesugaring(true)

0 commit comments

Comments
 (0)