| // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| package com.android.tools.r8.dex; |
| |
| import com.android.tools.r8.ByteDataView; |
| import com.android.tools.r8.DexFilePerClassFileConsumer; |
| import com.android.tools.r8.DiagnosticsHandler; |
| import com.android.tools.r8.code.ConstString; |
| import com.android.tools.r8.code.Instruction; |
| import com.android.tools.r8.code.ReturnVoid; |
| import com.android.tools.r8.graph.AppInfo; |
| import com.android.tools.r8.graph.AppView; |
| import com.android.tools.r8.graph.ClassAccessFlags; |
| import com.android.tools.r8.graph.DexAnnotationSet; |
| import com.android.tools.r8.graph.DexApplication; |
| import com.android.tools.r8.graph.DexCode; |
| import com.android.tools.r8.graph.DexCode.Try; |
| import com.android.tools.r8.graph.DexCode.TryHandler; |
| import com.android.tools.r8.graph.DexEncodedField; |
| import com.android.tools.r8.graph.DexEncodedMethod; |
| import com.android.tools.r8.graph.DexItemFactory; |
| import com.android.tools.r8.graph.DexProgramClass; |
| import com.android.tools.r8.graph.DexString; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.graph.DexTypeList; |
| import com.android.tools.r8.graph.DirectMappedDexApplication; |
| import com.android.tools.r8.graph.GenericSignature.ClassSignature; |
| import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature; |
| import com.android.tools.r8.graph.GraphLens; |
| import com.android.tools.r8.graph.InitClassLens; |
| import com.android.tools.r8.graph.LazyLoadedDexApplication; |
| import com.android.tools.r8.graph.MethodAccessFlags; |
| import com.android.tools.r8.graph.ParameterAnnotationsList; |
| import com.android.tools.r8.naming.NamingLens; |
| import com.android.tools.r8.origin.SynthesizedOrigin; |
| import com.android.tools.r8.utils.DescriptorUtils; |
| import com.android.tools.r8.utils.InternalOptions; |
| import com.android.tools.r8.utils.Reporter; |
| import com.android.tools.r8.utils.StringUtils; |
| import com.android.tools.r8.utils.ThreadUtils; |
| import com.android.tools.r8.utils.Timing; |
| import com.google.common.collect.Sets; |
| import java.io.IOException; |
| import java.nio.ByteBuffer; |
| import java.util.ArrayDeque; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Deque; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.ExecutorService; |
| import org.junit.Assert; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| public class SharedClassWritingTest { |
| |
| private final static String PREFIX = "A"; |
| private final static int NUMBER_OF_FILES = 500; |
| |
| DexItemFactory dexItemFactory = new DexItemFactory(); |
| |
| private DexString[] strings; |
| |
| @Before |
| public void generateStringArray() { |
| strings = new DexString[Constants.MAX_NON_JUMBO_INDEX + 100]; |
| for (int i = 0; i < strings.length; i++) { |
| // Format i as string with common prefix and leading 0's so that they are in the array |
| // in lexicographic order. |
| String string = PREFIX + StringUtils.zeroPrefix(i, 8); |
| strings[i] = dexItemFactory.createString(string); |
| } |
| } |
| |
| private DexEncodedMethod makeMethod(DexType holder, int stringCount, int startOffset) { |
| assert stringCount + startOffset < strings.length; |
| Instruction[] instructions = new Instruction[stringCount + 1]; |
| for (int i = 0; i < stringCount; i++) { |
| instructions[i] = new ConstString(0, strings[startOffset + i]); |
| } |
| instructions[stringCount] = new ReturnVoid(); |
| DexCode code = new DexCode(1, 0, 0, instructions, new Try[0], new TryHandler[0], null); |
| return new DexEncodedMethod( |
| dexItemFactory.createMethod( |
| holder, dexItemFactory.createProto(dexItemFactory.voidType), "theMethod"), |
| MethodAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC, false), |
| MethodTypeSignature.noSignature(), |
| DexAnnotationSet.empty(), |
| ParameterAnnotationsList.empty(), |
| code); |
| } |
| |
| private DexProgramClass makeClass( |
| InternalOptions options, |
| String name, |
| int stringCount, |
| int startOffset, |
| Collection<DexProgramClass> synthesizedFrom) { |
| String desc = DescriptorUtils.javaTypeToDescriptor(name); |
| DexType type = dexItemFactory.createType(desc); |
| DexProgramClass programClass = |
| new DexProgramClass( |
| type, |
| null, |
| new SynthesizedOrigin("test", getClass()), |
| ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC), |
| dexItemFactory.objectType, |
| DexTypeList.empty(), |
| null, |
| null, |
| Collections.emptyList(), |
| null, |
| Collections.emptyList(), |
| ClassSignature.noSignature(), |
| DexAnnotationSet.empty(), |
| DexEncodedField.EMPTY_ARRAY, |
| DexEncodedField.EMPTY_ARRAY, |
| DexEncodedMethod.EMPTY_ARRAY, |
| new DexEncodedMethod[] {makeMethod(type, stringCount, startOffset)}, |
| false, |
| DexProgramClass::invalidChecksumRequest); |
| return programClass; |
| } |
| |
| // TODO(b/181636450): Reconsider this test as it no longer reflects the compiler synthetics. |
| @Test |
| public void manyFilesWithSharedSynthesizedClass() throws ExecutionException, IOException { |
| InternalOptions options = new InternalOptions(dexItemFactory, new Reporter()); |
| |
| // Create classes that all reference enough strings to overflow the index, but are all |
| // at different offsets in the strings array. This ensures we trigger multiple rounds of |
| // rewrites. |
| List<DexProgramClass> classes = new ArrayList<>(); |
| for (int i = 0; i < NUMBER_OF_FILES; i++) { |
| classes.add( |
| makeClass( |
| options, |
| "Class" + i, |
| Constants.MAX_NON_JUMBO_INDEX - 1, |
| i % 100, |
| Collections.emptyList())); |
| } |
| |
| // Create a shared class that references strings above the maximum. |
| DexProgramClass sharedSynthesizedClass = |
| makeClass(options, "SharedSynthesized", 100, Constants.MAX_NON_JUMBO_INDEX - 1, classes); |
| |
| LazyLoadedDexApplication.Builder builder = |
| DirectMappedDexApplication.builder(options, Timing.empty()); |
| builder.addSynthesizedClass(sharedSynthesizedClass); |
| classes.forEach(builder::addProgramClass); |
| DexApplication application = builder.build(); |
| AppView<AppInfo> appView = AppView.createForD8(AppInfo.createInitialAppInfo(application)); |
| classes.forEach( |
| c -> appView.getSyntheticItems().addLegacySyntheticClass(sharedSynthesizedClass, c)); |
| |
| CollectInfoConsumer consumer = new CollectInfoConsumer(); |
| options.programConsumer = consumer; |
| ApplicationWriter writer = |
| new ApplicationWriter( |
| appView, |
| null, |
| GraphLens.getIdentityLens(), |
| InitClassLens.getDefault(), |
| NamingLens.getIdentityLens(), |
| null); |
| ExecutorService executorService = ThreadUtils.getExecutorService(options); |
| writer.write(executorService); |
| List<Set<String>> generatedDescriptors = consumer.getDescriptors(); |
| // Check all files present. |
| Assert.assertEquals(NUMBER_OF_FILES, generatedDescriptors.size()); |
| // And each file contains two classes of which one is the shared one. |
| for (Set<String> classDescriptors : generatedDescriptors) { |
| Assert.assertEquals(2, classDescriptors.size()); |
| Assert |
| .assertTrue(classDescriptors.contains(sharedSynthesizedClass.type.toDescriptorString())); |
| } |
| } |
| |
| private static class CollectInfoConsumer implements DexFilePerClassFileConsumer { |
| |
| private final List<Set<String>> descriptors = new ArrayList<>(); |
| |
| private final Deque<ByteBuffer> freeBuffers = new ArrayDeque<>(); |
| private final Set<ByteBuffer> activeBuffers = Sets.newIdentityHashSet(); |
| |
| @Override |
| public ByteBuffer acquireByteBuffer(int capacity) { |
| synchronized (freeBuffers) { |
| ByteBuffer buffer = freeBuffers.pollFirst(); |
| // Ensure the buffer has sufficient capacity, eg, skip buffers that are too small. |
| if (buffer != null && buffer.capacity() < capacity) { |
| List<ByteBuffer> small = new ArrayList<>(freeBuffers.size()); |
| do { |
| small.add(buffer); |
| buffer = freeBuffers.pollFirst(); |
| } while (buffer != null && buffer.capacity() < capacity); |
| freeBuffers.addAll(small); |
| } |
| if (buffer == null) { |
| buffer = ByteBuffer.allocate(capacity); |
| } |
| assert !activeBuffers.contains(buffer); |
| activeBuffers.add(buffer); |
| return buffer; |
| } |
| } |
| |
| @Override |
| public void releaseByteBuffer(ByteBuffer buffer) { |
| synchronized (freeBuffers) { |
| assert activeBuffers.contains(buffer); |
| activeBuffers.remove(buffer); |
| buffer.position(0); |
| freeBuffers.offerFirst(buffer); |
| } |
| } |
| |
| @Override |
| public void accept( |
| String primaryClassDescriptor, |
| ByteDataView data, |
| Set<String> descriptors, |
| DiagnosticsHandler handler) { |
| addDescriptors(descriptors); |
| } |
| |
| synchronized void addDescriptors(Set<String> descriptors) { |
| this.descriptors.add(descriptors); |
| } |
| |
| public List<Set<String>> getDescriptors() { |
| return descriptors; |
| } |
| |
| @Override |
| public void finished(DiagnosticsHandler handler) {} |
| } |
| } |