Merge commit '3e0a120cf508a856c117d8784cd6477340071181' into dev-release
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index 5e43f29..9d43396 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java
@@ -982,10 +982,10 @@ } private static boolean verifyOriginalMethodInDebugInfo(DexCode code, DexMethod originalMethod) { - if (code.getDebugInfo() == null) { + if (code.getDebugInfo() == null || code.getDebugInfo().isPcBasedInfo()) { return true; } - for (DexDebugEvent event : code.getDebugInfo().events) { + for (DexDebugEvent event : code.getDebugInfo().asEventBasedInfo().events) { assert !event.isSetInlineFrame() || event.asSetInlineFrame().hasOuterPosition(originalMethod); } return true;
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java index f8b10e0..47124c0 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -532,6 +532,27 @@ } public CfFrame map(java.util.function.Function<DexType, DexType> func) { + boolean mapped = false; + for (int var : locals.keySet()) { + CfFrame.FrameType originalType = locals.get(var); + CfFrame.FrameType mappedType = originalType.map(func); + mapped = originalType != mappedType; + if (mapped) { + break; + } + } + if (!mapped) { + for (FrameType frameType : stack) { + CfFrame.FrameType mappedType = frameType.map(func); + mapped = frameType != mappedType; + if (mapped) { + break; + } + } + } + if (!mapped) { + return this; + } Int2ReferenceSortedMap<FrameType> newLocals = new Int2ReferenceAVLTreeMap<>(); for (int var : locals.keySet()) { newLocals.put(var, locals.get(var).map(func));
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java index f99eee5..5d26e0a 100644 --- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java +++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -26,7 +26,7 @@ import com.android.tools.r8.graph.DexAnnotationDirectory; import com.android.tools.r8.graph.DexAnnotationSet; import com.android.tools.r8.graph.DexApplication; -import com.android.tools.r8.graph.DexDebugInfo; +import com.android.tools.r8.graph.DexDebugInfoForWriting; import com.android.tools.r8.graph.DexEncodedArray; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; @@ -132,7 +132,7 @@ } @Override - public boolean add(DexDebugInfo dexDebugInfo) { + public boolean add(DexDebugInfoForWriting dexDebugInfo) { return true; } @@ -488,7 +488,7 @@ timing.end(); timing.begin("Write bytes"); - ByteBufferResult result = writeDexFile(objectMapping, byteBufferProvider); + ByteBufferResult result = writeDexFile(objectMapping, byteBufferProvider, timing); ByteDataView data = new ByteDataView(result.buffer.array(), result.buffer.arrayOffset(), result.length); timing.end(); @@ -789,8 +789,7 @@ } private ByteBufferResult writeDexFile( - ObjectToOffsetMapping objectMapping, - ByteBufferProvider provider) { + ObjectToOffsetMapping objectMapping, ByteBufferProvider provider, Timing timing) { FileWriter fileWriter = new FileWriter( provider, @@ -800,9 +799,9 @@ namingLens, desugaredLibraryCodeToKeep); // Collect the non-fixed sections. - fileWriter.collect(); + timing.scope("collect", () -> fileWriter.collect()); // Generate and write the bytes. - return fileWriter.generate(); + return timing.scope("generate", () -> fileWriter.generate()); } private static String mapMainDexListName(DexType type, NamingLens namingLens) {
diff --git a/src/main/java/com/android/tools/r8/dex/DebugBytecodeWriter.java b/src/main/java/com/android/tools/r8/dex/DebugBytecodeWriter.java index 2c3e18a..5a6a377 100644 --- a/src/main/java/com/android/tools/r8/dex/DebugBytecodeWriter.java +++ b/src/main/java/com/android/tools/r8/dex/DebugBytecodeWriter.java
@@ -3,8 +3,7 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.dex; -import com.android.tools.r8.graph.DexDebugEvent; -import com.android.tools.r8.graph.DexDebugInfo; +import com.android.tools.r8.graph.DexDebugInfoForWriting; import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.GraphLens; @@ -17,32 +16,19 @@ private final ObjectToOffsetMapping mapping; private final GraphLens graphLens; - private final DexDebugInfo info; + private final DexDebugInfoForWriting info; private ByteBuffer buffer; public DebugBytecodeWriter( - DexDebugInfo info, ObjectToOffsetMapping mapping, GraphLens graphLens) { + DexDebugInfoForWriting info, ObjectToOffsetMapping mapping, GraphLens graphLens) { this.info = info; this.mapping = mapping; this.graphLens = graphLens; - // Never allocate a zero-sized buffer, as we need to write the header, and the growth policy - // requires it to have a positive capacity. - this.buffer = ByteBuffer.allocate(info.events.length * 5 + 4); + this.buffer = ByteBuffer.allocate(info.estimatedWriteSize()); } public byte[] generate() { - // Header. - putUleb128(info.startLine); // line_start - putUleb128(info.parameters.length); - for (DexString name : info.parameters) { - putString(name); - } - // Body. - for (DexDebugEvent event : info.events) { - event.writeOn(this, mapping, graphLens); - } - // Tail. - putByte(Constants.DBG_END_SEQUENCE); + info.write(this, mapping, graphLens); return Arrays.copyOf(buffer.array(), buffer.position()); }
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java index 43a9a57..7a3ccf5 100644 --- a/src/main/java/com/android/tools/r8/dex/DexParser.java +++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -25,7 +25,10 @@ import com.android.tools.r8.graph.DexCode.TryHandler; import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair; import com.android.tools.r8.graph.DexDebugEvent; +import com.android.tools.r8.graph.DexDebugEvent.Default; import com.android.tools.r8.graph.DexDebugInfo; +import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo; +import com.android.tools.r8.graph.DexDebugInfo.PcBasedDebugInfo; import com.android.tools.r8.graph.DexEncodedAnnotation; import com.android.tools.r8.graph.DexEncodedArray; import com.android.tools.r8.graph.DexEncodedField; @@ -522,22 +525,28 @@ private DexDebugInfo parseDebugInfo() { int start = dexReader.getUleb128(); + boolean isPcBasedDebugInfo = start == 0; int parametersSize = dexReader.getUleb128(); DexString[] parameters = new DexString[parametersSize]; for (int i = 0; i < parametersSize; i++) { int index = dexReader.getUleb128p1(); if (index != NO_INDEX) { parameters[i] = indexedItems.getString(index); + isPcBasedDebugInfo = false; } } List<DexDebugEvent> events = new ArrayList<>(); - for (int head = dexReader.getUbyte(); head != Constants.DBG_END_SEQUENCE; head = dexReader.getUbyte()) { + for (int head = dexReader.getUbyte(); + head != Constants.DBG_END_SEQUENCE; + head = dexReader.getUbyte()) { switch (head) { case Constants.DBG_ADVANCE_PC: events.add(dexItemFactory.createAdvancePC(dexReader.getUleb128())); + isPcBasedDebugInfo = false; break; case Constants.DBG_ADVANCE_LINE: events.add(dexItemFactory.createAdvanceLine(dexReader.getSleb128())); + isPcBasedDebugInfo = false; break; case Constants.DBG_START_LOCAL: { int registerNum = dexReader.getUleb128(); @@ -548,6 +557,7 @@ nameIdx == NO_INDEX ? null : indexedItems.getString(nameIdx), typeIdx == NO_INDEX ? null : indexedItems.getType(typeIdx), null)); + isPcBasedDebugInfo = false; break; } case Constants.DBG_START_LOCAL_EXTENDED: { @@ -560,22 +570,27 @@ nameIdx == NO_INDEX ? null : indexedItems.getString(nameIdx), typeIdx == NO_INDEX ? null : indexedItems.getType(typeIdx), sigIdx == NO_INDEX ? null : indexedItems.getString(sigIdx))); + isPcBasedDebugInfo = false; break; } case Constants.DBG_END_LOCAL: { events.add(dexItemFactory.createEndLocal(dexReader.getUleb128())); + isPcBasedDebugInfo = false; break; } case Constants.DBG_RESTART_LOCAL: { events.add(dexItemFactory.createRestartLocal(dexReader.getUleb128())); + isPcBasedDebugInfo = false; break; } case Constants.DBG_SET_PROLOGUE_END: { events.add(dexItemFactory.createSetPrologueEnd()); + isPcBasedDebugInfo = false; break; } case Constants.DBG_SET_EPILOGUE_BEGIN: { events.add(dexItemFactory.createSetEpilogueBegin()); + isPcBasedDebugInfo = false; break; } case Constants.DBG_SET_FILE: { @@ -584,15 +599,26 @@ if (options.readDebugSetFileEvent) { events.add(dexItemFactory.createSetFile(sourceFile)); } + isPcBasedDebugInfo = false; break; } default: { assert head >= 0x0a && head <= 0xff; - events.add(dexItemFactory.createDefault(head)); + Default event = dexItemFactory.createDefault(head); + events.add(event); + if (isPcBasedDebugInfo) { + if (events.size() == 1) { + isPcBasedDebugInfo = event.equals(dexItemFactory.zeroChangeDefaultEvent); + } else { + isPcBasedDebugInfo = event.equals(dexItemFactory.oneChangeDefaultEvent); + } + } } } } - return new DexDebugInfo(start, parameters, events.toArray(DexDebugEvent.EMPTY_ARRAY)); + return isPcBasedDebugInfo + ? new PcBasedDebugInfo(parametersSize, events.size() - 1) + : new EventBasedDebugInfo(start, parameters, events.toArray(DexDebugEvent.EMPTY_ARRAY)); } private static class MemberAnnotationIterator<R extends DexMember<?, R>, T extends DexItem> {
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java index 84b245c..ebf21c6 100644 --- a/src/main/java/com/android/tools/r8/dex/FileWriter.java +++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -22,7 +22,6 @@ import com.android.tools.r8.graph.DexCode.Try; import com.android.tools.r8.graph.DexCode.TryHandler; import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair; -import com.android.tools.r8.graph.DexDebugInfo; import com.android.tools.r8.graph.DexDebugInfoForWriting; import com.android.tools.r8.graph.DexEncodedAnnotation; import com.android.tools.r8.graph.DexEncodedArray; @@ -184,7 +183,7 @@ } else { // Ensure deterministic ordering of debug info by sorting consistent with the code objects. layout.setDebugInfosOffset(dest.align(1)); - Set<DexDebugInfo> seen = new HashSet<>(mixedSectionOffsets.getDebugInfos().size()); + Set<DexDebugInfoForWriting> seen = new HashSet<>(mixedSectionOffsets.getDebugInfos().size()); for (ProgramDexCode code : codes) { DexDebugInfoForWriting info = code.getCode().getDebugInfoForWriting(); if (info != null && seen.add(info)) { @@ -480,7 +479,7 @@ dest.putInt(mixedSectionOffsets.getOffsetFor(staticFieldValues.get(clazz))); } - private void writeDebugItem(DexDebugInfo debugInfo, GraphLens graphLens) { + private void writeDebugItem(DexDebugInfoForWriting debugInfo, GraphLens graphLens) { mixedSectionOffsets.setOffsetFor(debugInfo, dest.position()); dest.putBytes(new DebugBytecodeWriter(debugInfo, mapping, graphLens).generate()); } @@ -1070,7 +1069,7 @@ private static final int NOT_KNOWN = -2; private final Reference2IntMap<DexEncodedMethod> codes = createReference2IntMap(); - private final Object2IntMap<DexDebugInfo> debugInfos = createObject2IntMap(); + private final Object2IntMap<DexDebugInfoForWriting> debugInfos = createObject2IntMap(); private final Object2IntMap<DexTypeList> typeLists = createObject2IntMap(); private final Reference2IntMap<DexString> stringData = createReference2IntMap(); private final Object2IntMap<DexAnnotation> annotations = createObject2IntMap(); @@ -1149,7 +1148,7 @@ } @Override - public boolean add(DexDebugInfo debugInfo) { + public boolean add(DexDebugInfoForWriting debugInfo) { return add(debugInfos, debugInfo); } @@ -1190,7 +1189,7 @@ return codes.keySet(); } - public Collection<DexDebugInfo> getDebugInfos() { + public Collection<DexDebugInfoForWriting> getDebugInfos() { return debugInfos.keySet(); } @@ -1263,7 +1262,7 @@ return lookup(encodedArray, encodedArrays); } - public int getOffsetFor(DexDebugInfo debugInfo) { + public int getOffsetFor(DexDebugInfoForWriting debugInfo) { return lookup(debugInfo, debugInfos); } @@ -1311,7 +1310,7 @@ assert old <= NOT_SET; } - void setOffsetFor(DexDebugInfo debugInfo, int offset) { + void setOffsetFor(DexDebugInfoForWriting debugInfo, int offset) { setOffsetFor(debugInfo, offset, debugInfos); }
diff --git a/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java index 990d545..fc3784f 100644 --- a/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java +++ b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
@@ -37,6 +37,7 @@ import com.android.tools.r8.graph.DexDebugEvent.AdvancePC; import com.android.tools.r8.graph.DexDebugEvent.Default; import com.android.tools.r8.graph.DexDebugInfo; +import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexString; @@ -93,8 +94,8 @@ private final DexString firstJumboString; private final DexItemFactory factory; private final Map<Instruction, List<Instruction>> instructionTargets = new IdentityHashMap<>(); - private final Int2ReferenceMap<Instruction> debugEventTargets - = new Int2ReferenceOpenHashMap<>(); + private EventBasedDebugInfo debugEventBasedInfo = null; + private final Int2ReferenceMap<Instruction> debugEventTargets = new Int2ReferenceOpenHashMap<>(); private final Map<Instruction, Instruction> payloadToSwitch = new IdentityHashMap<>(); private final Map<Try, TryTargets> tryTargets = new IdentityHashMap<>(); private final Int2ReferenceMap<Instruction> tryRangeStartAndEndTargets @@ -216,11 +217,12 @@ private DexDebugInfo rewriteDebugInfoOffsets() { DexCode code = method.getCode().asDexCode(); - if (debugEventTargets.size() != 0) { + if (!debugEventTargets.isEmpty()) { + assert debugEventBasedInfo != null; int lastOriginalOffset = 0; int lastNewOffset = 0; List<DexDebugEvent> events = new ArrayList<>(); - for (DexDebugEvent event : code.getDebugInfo().events) { + for (DexDebugEvent event : debugEventBasedInfo.events) { if (event instanceof AdvancePC) { AdvancePC advance = (AdvancePC) event; lastOriginalOffset += advance.delta; @@ -240,9 +242,9 @@ events.add(event); } } - return new DexDebugInfo( - code.getDebugInfo().startLine, - code.getDebugInfo().parameters, + return new EventBasedDebugInfo( + debugEventBasedInfo.startLine, + debugEventBasedInfo.parameters, events.toArray(DexDebugEvent.EMPTY_ARRAY)); } return code.getDebugInfo(); @@ -479,23 +481,30 @@ } private void recordDebugEventTargets(Int2ReferenceMap<Instruction> offsetToInstruction) { - DexDebugInfo debugInfo = method.getCode().asDexCode().getDebugInfo(); - if (debugInfo != null) { - int address = 0; - for (DexDebugEvent event : debugInfo.events) { - if (event instanceof AdvancePC) { - AdvancePC advance = (AdvancePC) event; - address += advance.delta; - Instruction target = offsetToInstruction.get(address); - assert target != null; - debugEventTargets.put(address, target); - } else if (event instanceof Default) { - Default defaultEvent = (Default) event; - address += defaultEvent.getPCDelta(); - Instruction target = offsetToInstruction.get(address); - assert target != null; - debugEventTargets.put(address, target); - } + // TODO(b/213411850): Merging pc based D8 builds will map out of PC for any jumbo processed + // method. Instead we should rather retain the PC encoding by bumping the max-pc and recording + // the line number translation. We actually need to do so to support merging with native PC + // support as in that case we can't reflect the change in the line table, only in the mapping. + EventBasedDebugInfo eventBasedInfo = + DexDebugInfo.convertToEventBased(method.getCode().asDexCode(), factory); + if (eventBasedInfo == null) { + return; + } + debugEventBasedInfo = eventBasedInfo; + int address = 0; + for (DexDebugEvent event : eventBasedInfo.events) { + if (event instanceof AdvancePC) { + AdvancePC advance = (AdvancePC) event; + address += advance.delta; + Instruction target = offsetToInstruction.get(address); + assert target != null; + debugEventTargets.put(address, target); + } else if (event instanceof Default) { + Default defaultEvent = (Default) event; + address += defaultEvent.getPCDelta(); + Instruction target = offsetToInstruction.get(address); + assert target != null; + debugEventTargets.put(address, target); } } }
diff --git a/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java b/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java index e082a31..f0c4c06 100644 --- a/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java +++ b/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java
@@ -6,7 +6,7 @@ import com.android.tools.r8.graph.DexAnnotation; import com.android.tools.r8.graph.DexAnnotationDirectory; import com.android.tools.r8.graph.DexAnnotationSet; -import com.android.tools.r8.graph.DexDebugInfo; +import com.android.tools.r8.graph.DexDebugInfoForWriting; import com.android.tools.r8.graph.DexEncodedArray; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexItem; @@ -74,11 +74,11 @@ /** * Adds the given debug info to the collection. * - * Does not add any dependencies. + * <p>Does not add any dependencies. * * @return true if the item was not added before */ - public abstract boolean add(DexDebugInfo dexDebugInfo); + public abstract boolean add(DexDebugInfoForWriting dexDebugInfo); /** * Adds the given type list to the collection.
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java index 7c7824d..07ebb29 100644 --- a/src/main/java/com/android/tools/r8/graph/CfCode.java +++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -803,7 +803,7 @@ } @Override - public Code getCodeAsInlining(DexMethod caller, DexMethod callee) { + public Code getCodeAsInlining(DexMethod caller, DexMethod callee, DexItemFactory factory) { Position callerPosition = SyntheticPosition.builder().setLine(0).setMethod(caller).build(); List<CfInstruction> newInstructions = new ArrayList<>(instructions.size() + 2); CfLabel firstLabel;
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java index be9507d..e727373 100644 --- a/src/main/java/com/android/tools/r8/graph/Code.java +++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -149,7 +149,7 @@ return true; } - public Code getCodeAsInlining(DexMethod caller, DexMethod callee) { + public Code getCodeAsInlining(DexMethod caller, DexMethod callee, DexItemFactory factory) { throw new Unreachable(); } }
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java index 703977e..11d3e17 100644 --- a/src/main/java/com/android/tools/r8/graph/DexCode.java +++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -16,6 +16,7 @@ import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair; import com.android.tools.r8.graph.DexDebugEvent.SetInlineFrame; import com.android.tools.r8.graph.DexDebugEvent.StartLocal; +import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo; import com.android.tools.r8.graph.bytecodemetadata.BytecodeInstructionMetadata; import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadata; import com.android.tools.r8.ir.code.IRCode; @@ -27,6 +28,7 @@ import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils; import com.android.tools.r8.naming.ClassNameMapper; import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.utils.ArrayUtils; import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.structural.Equatable; import com.android.tools.r8.utils.structural.HashCodeVisitor; @@ -170,7 +172,7 @@ } } - public DexCode withoutThisParameter() { + public DexCode withoutThisParameter(DexItemFactory factory) { // Note that we assume the original code has a register associated with 'this' // argument of the (former) instance method. We also assume (but do not check) // that 'this' register is never used, so when we decrease incoming register size @@ -184,7 +186,7 @@ instructions, tries, handlers, - debugInfoWithoutFirstParameter()); + debugInfoWithoutFirstParameter(factory)); } @Override @@ -229,16 +231,17 @@ } public DexDebugInfo debugInfoWithFakeThisParameter(DexItemFactory factory) { - if (debugInfo == null) { - return null; + EventBasedDebugInfo eventBasedInfo = DexDebugInfo.convertToEventBased(this, factory); + if (eventBasedInfo == null) { + return eventBasedInfo; } // User code may already have variables named '_*this'. Use one more than the largest number of // underscores present as a prefix to 'this'. int largestPrefix = 0; - for (DexString parameter : debugInfo.parameters) { + for (DexString parameter : eventBasedInfo.parameters) { largestPrefix = Integer.max(largestPrefix, getLargestPrefix(factory, parameter)); } - for (DexDebugEvent event : debugInfo.events) { + for (DexDebugEvent event : eventBasedInfo.events) { if (event instanceof DexDebugEvent.StartLocal) { DexString name = ((StartLocal) event).name; largestPrefix = Integer.max(largestPrefix, getLargestPrefix(factory, name)); @@ -246,15 +249,15 @@ } String fakeThisName = Strings.repeat(FAKE_THIS_PREFIX, largestPrefix + 1) + FAKE_THIS_SUFFIX; - DexString[] parameters = debugInfo.parameters; + DexString[] parameters = eventBasedInfo.parameters; DexString[] newParameters = new DexString[parameters.length + 1]; newParameters[0] = factory.createString(fakeThisName); System.arraycopy(parameters, 0, newParameters, 1, parameters.length); - return new DexDebugInfo(debugInfo.startLine, newParameters, debugInfo.events); + return new EventBasedDebugInfo(eventBasedInfo.startLine, newParameters, eventBasedInfo.events); } @Override - public Code getCodeAsInlining(DexMethod caller, DexMethod callee) { + public Code getCodeAsInlining(DexMethod caller, DexMethod callee, DexItemFactory factory) { return new DexCode( registerSize, incomingRegisterSize, @@ -262,24 +265,25 @@ instructions, tries, handlers, - debugInfoAsInlining(caller, callee)); + debugInfoAsInlining(caller, callee, factory)); } - private DexDebugInfo debugInfoAsInlining(DexMethod caller, DexMethod callee) { + private DexDebugInfo debugInfoAsInlining( + DexMethod caller, DexMethod callee, DexItemFactory factory) { Position callerPosition = SyntheticPosition.builder().setLine(0).setMethod(caller).build(); - if (debugInfo == null) { + EventBasedDebugInfo eventBasedInfo = DexDebugInfo.convertToEventBased(this, factory); + if (eventBasedInfo == null) { // If the method has no debug info we generate a preamble position to denote the inlining. // This is consistent with the building IR for inlining which will always ensure the method // has a position. - return new DexDebugInfo( + return new EventBasedDebugInfo( 0, new DexString[callee.getArity()], new DexDebugEvent[] { - new DexDebugEvent.SetInlineFrame(callee, callerPosition), - DexDebugEvent.ZERO_CHANGE_DEFAULT_EVENT + new DexDebugEvent.SetInlineFrame(callee, callerPosition), factory.zeroChangeDefaultEvent }); } - DexDebugEvent[] oldEvents = debugInfo.events; + DexDebugEvent[] oldEvents = eventBasedInfo.events; DexDebugEvent[] newEvents = new DexDebugEvent[oldEvents.length + 1]; int i = 0; newEvents[i++] = new DexDebugEvent.SetInlineFrame(callee, callerPosition); @@ -296,7 +300,7 @@ newEvents[i++] = event; } } - return new DexDebugInfo(debugInfo.startLine, debugInfo.parameters, newEvents); + return new EventBasedDebugInfo(eventBasedInfo.startLine, eventBasedInfo.parameters, newEvents); } public static int getLargestPrefix(DexItemFactory factory, DexString name) { @@ -311,17 +315,18 @@ return 0; } - public DexDebugInfo debugInfoWithoutFirstParameter() { - if (debugInfo == null) { - return null; + public DexDebugInfo debugInfoWithoutFirstParameter(DexItemFactory factory) { + EventBasedDebugInfo eventBasedInfo = DexDebugInfo.convertToEventBased(this, factory); + if (eventBasedInfo == null) { + return eventBasedInfo; } - DexString[] parameters = debugInfo.parameters; + DexString[] parameters = eventBasedInfo.parameters; if(parameters.length == 0) { - return debugInfo; + return eventBasedInfo; } DexString[] newParameters = new DexString[parameters.length - 1]; System.arraycopy(parameters, 1, newParameters, 0, parameters.length - 1); - return new DexDebugInfo(debugInfo.startLine, newParameters, debugInfo.events); + return new EventBasedDebugInfo(eventBasedInfo.startLine, newParameters, eventBasedInfo.events); } @Override @@ -362,7 +367,8 @@ this, method, appView.graphLens().getOriginalMethodSignature(method.getReference()), - null); + null, + appView.dexItemFactory()); return IRBuilder.create(method, appView, source, origin).build(method); } @@ -381,7 +387,8 @@ this, method, appView.graphLens().getOriginalMethodSignature(method.getReference()), - callerPosition); + callerPosition, + appView.dexItemFactory()); return IRBuilder.createForInlining( method, appView, codeLens, source, origin, valueNumberGenerator, protoChanges) .build(context); @@ -443,13 +450,15 @@ DexDebugEntry debugInfo = null; Iterator<DexDebugEntry> debugInfoIterator = Collections.emptyIterator(); - if (getDebugInfo() != null && method != null) { + boolean isPcBasedInfo = getDebugInfo() != null && getDebugInfo().isPcBasedInfo(); + if (!isPcBasedInfo && getDebugInfo() != null && method != null) { debugInfoIterator = new DexDebugEntryBuilder(method, new DexItemFactory()).build().iterator(); debugInfo = debugInfoIterator.hasNext() ? debugInfoIterator.next() : null; } int instructionNumber = 0; Map<Integer, DebugLocalInfo> locals = Collections.emptyMap(); for (Instruction insn : instructions) { + debugInfo = advanceToOffset(insn.getOffset() - 1, debugInfo, debugInfoIterator); while (debugInfo != null && debugInfo.address == insn.getOffset()) { if (debugInfo.lineEntry || !locals.equals(debugInfo.locals)) { builder.append(" ").append(debugInfo.toString(false)).append("\n"); @@ -467,8 +476,16 @@ } builder.append('\n'); } - if (debugInfoIterator.hasNext()) { - throw new Unreachable("Could not print all debug information."); + if (isPcBasedInfo) { + builder.append(getDebugInfo()).append("\n"); + } else if (debugInfoIterator.hasNext()) { + Instruction lastInstruction = ArrayUtils.last(instructions); + debugInfo = advanceToOffset(lastInstruction.getOffset(), debugInfo, debugInfoIterator); + if (debugInfo != null) { + throw new Unreachable("Could not print all debug information."); + } else { + builder.append("(has debug events past last pc)\n"); + } } if (tries.length > 0) { builder.append("Tries (numbers are offsets)\n"); @@ -488,6 +505,14 @@ return builder.toString(); } + DexDebugEntry advanceToOffset( + int offset, DexDebugEntry current, Iterator<DexDebugEntry> iterator) { + while (current != null && current.address <= offset) { + current = iterator.hasNext() ? iterator.next() : null; + } + return current; + } + public String toSmaliString(ClassNameMapper naming) { StringBuilder builder = new StringBuilder(); // Find labeled targets. @@ -566,18 +591,15 @@ if (debugInfo != null) { getDebugInfoForWriting().collectIndexedItems(indexedItems, graphLens); } - for (TryHandler handler : handlers) { - handler.collectIndexedItems(indexedItems, graphLens); - } + for (TryHandler handler : handlers) { + handler.collectIndexedItems(indexedItems, graphLens); + } } @Override public DexDebugInfoForWriting getDebugInfoForWriting() { - if (debugInfo == null) { - return null; - } if (debugInfoForWriting == null) { - debugInfoForWriting = DexDebugInfoForWriting.create(debugInfo); + debugInfoForWriting = DexDebugInfo.convertToWritable(debugInfo); } return debugInfoForWriting; } @@ -619,10 +641,6 @@ } } - public boolean usesExceptionHandling() { - return tries.length != 0; - } - @Override public void collectMixedSectionItems(MixedSectionCollection mixedItems) { if (debugInfo != null) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java index ce338d6..d8b6a9b 100644 --- a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java +++ b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
@@ -5,6 +5,7 @@ import com.android.tools.r8.graph.DexDebugEvent.SetOutlineCallerFrame; import com.android.tools.r8.graph.DexDebugEvent.SetOutlineFrame; +import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo; import com.android.tools.r8.ir.code.ValueType; import com.google.common.collect.ImmutableMap; import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap; @@ -67,11 +68,11 @@ public DexDebugEntryBuilder(DexEncodedMethod method, DexItemFactory factory) { assert method != null && method.getReference() != null; this.method = method.getReference(); - positionState = - new DexDebugPositionState( - method.getCode().asDexCode().getDebugInfo().startLine, method.getReference()); DexCode code = method.getCode().asDexCode(); - DexDebugInfo info = code.getDebugInfo(); + EventBasedDebugInfo info = code.getDebugInfo().asEventBasedInfo(); + // Only event based debug info supports conversion to entries. + assert info != null; + positionState = new DexDebugPositionState(info.startLine, method.getReference()); int argumentRegister = code.registerSize - code.incomingRegisterSize; if (!method.accessFlags.isStatic()) { DexString name = factory.thisName;
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java index e4994c0..60cc65a 100644 --- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java +++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -26,8 +26,6 @@ public static final DexDebugEvent[] EMPTY_ARRAY = {}; - public static final DexDebugEvent.Default ZERO_CHANGE_DEFAULT_EVENT = Default.create(0, 0); - public void collectIndexedItems(IndexedItemCollection collection, GraphLens graphLens) { // Empty by default. } @@ -746,12 +744,14 @@ this.value = value; } - public static int computeSpecialOpcode(int lineDelta, int pcDelta) { + // Use DexDebugEventBuilder.addDefaultEventWithAdvancePcIfNecessary instead. + static int computeSpecialOpcode(int lineDelta, int pcDelta) { return Constants.DBG_FIRST_SPECIAL + (lineDelta - Constants.DBG_LINE_BASE) + Constants.DBG_LINE_RANGE * pcDelta; } + // Use DexDebugEventBuilder.addDefaultEventWithAdvancePcIfNecessary instead. public static Default create(int lineDelta, int pcDelta) { return new Default(computeSpecialOpcode(lineDelta, pcDelta)); }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java index 71822f6..dc90361 100644 --- a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java +++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.dex.Constants; import com.android.tools.r8.graph.DexDebugEvent.Default; import com.android.tools.r8.graph.DexDebugEvent.StartLocal; +import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo; import com.android.tools.r8.ir.code.Argument; import com.android.tools.r8.ir.code.DebugLocalsChange; import com.android.tools.r8.ir.code.IRCode; @@ -120,7 +121,7 @@ params[i] = (local == null || local.signature != null) ? null : local.name; } } - return new DexDebugInfo(startLine, params, events.toArray(DexDebugEvent.EMPTY_ARRAY)); + return new EventBasedDebugInfo(startLine, params, events.toArray(DexDebugEvent.EMPTY_ARRAY)); } private void updateBlockEntry(Instruction instruction) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java index 75588f6..0f27bda 100644 --- a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java +++ b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
@@ -3,92 +3,368 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.graph; +import com.android.tools.r8.code.Instruction; +import com.android.tools.r8.dex.Constants; +import com.android.tools.r8.dex.DebugBytecodeWriter; import com.android.tools.r8.dex.IndexedItemCollection; import com.android.tools.r8.dex.MixedSectionCollection; +import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.utils.ArrayUtils; +import com.android.tools.r8.utils.LebUtils; +import com.android.tools.r8.utils.StringUtils; +import com.android.tools.r8.utils.structural.CompareToVisitor; import com.android.tools.r8.utils.structural.Equatable; +import com.android.tools.r8.utils.structural.HashingVisitor; import com.android.tools.r8.utils.structural.StructuralItem; import com.android.tools.r8.utils.structural.StructuralMapping; import com.android.tools.r8.utils.structural.StructuralSpecification; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; -public class DexDebugInfo extends CachedHashValueDexItem implements StructuralItem<DexDebugInfo> { +public abstract class DexDebugInfo extends CachedHashValueDexItem + implements StructuralItem<DexDebugInfo> { - public final int startLine; - public final DexString[] parameters; - public DexDebugEvent[] events; - - private static void specify(StructuralSpecification<DexDebugInfo, ?> spec) { - spec.withInt(d -> d.startLine) - .withItemArrayAllowingNullMembers(d -> d.parameters) - .withItemArray(d -> d.events); + private enum DebugInfoKind { + EVENT_BASED, + PC_BASED } - public DexDebugInfo(int startLine, DexString[] parameters, DexDebugEvent[] events) { - assert startLine >= 0; - this.startLine = startLine; - this.parameters = parameters; - this.events = events; - // This call to hashCode is just an optimization to speedup equality when - // canonicalizing DexDebugInfo objects inside a synchronize method. - hashCode(); + abstract DebugInfoKind getKind(); + + abstract int internalAcceptCompareTo(DexDebugInfo other, CompareToVisitor visitor); + + public abstract int getStartLine(); + + public abstract int getParameterCount(); + + public boolean isEventBasedInfo() { + return getKind() == DebugInfoKind.EVENT_BASED; + } + + public boolean isPcBasedInfo() { + return getKind() == DebugInfoKind.PC_BASED; + } + + public EventBasedDebugInfo asEventBasedInfo() { + return null; + } + + public PcBasedDebugInfo asPcBasedInfo() { + return null; } @Override - public DexDebugInfo self() { - return this; - } + abstract void collectMixedSectionItems(MixedSectionCollection collection); + + @Override + public abstract DexDebugInfo self(); @Override public StructuralMapping<DexDebugInfo> getStructuralMapping() { - return DexDebugInfo::specify; + throw new Unreachable(); } - public List<DexDebugEntry> computeEntries(DexMethod method) { - DexDebugEntryBuilder builder = new DexDebugEntryBuilder(startLine, method); - for (DexDebugEvent event : events) { - event.accept(builder); + @Override + public abstract void acceptHashing(HashingVisitor visitor); + + @Override + public int acceptCompareTo(DexDebugInfo other, CompareToVisitor visitor) { + int diff = visitor.visitInt(getKind().ordinal(), other.getKind().ordinal()); + if (diff != 0) { + return diff; } - return builder.build(); + return internalAcceptCompareTo(other, visitor); } @Override - public int computeHashCode() { - return startLine - + Arrays.hashCode(parameters) * 7 - + Arrays.hashCode(events) * 13; - } - - @Override - public final boolean computeEquals(Object other) { + protected final boolean computeEquals(Object other) { return Equatable.equalsImpl(this, other); } - public void collectIndexedItems(IndexedItemCollection indexedItems, GraphLens graphLens) { - for (DexString parameter : parameters) { - if (parameter != null) { - parameter.collectIndexedItems(indexedItems); + public static class PcBasedDebugInfo extends DexDebugInfo implements DexDebugInfoForWriting { + private static final int START_LINE = 0; + private final int parameterCount; + private final int maxPc; + + private static void specify(StructuralSpecification<PcBasedDebugInfo, ?> spec) { + spec.withInt(d -> d.parameterCount).withInt(d -> d.maxPc); + } + + public PcBasedDebugInfo(int parameterCount, int maxPc) { + this.parameterCount = parameterCount; + this.maxPc = maxPc; + } + + @Override + public int getStartLine() { + return START_LINE; + } + + @Override + public int getParameterCount() { + return parameterCount; + } + + @Override + public DexDebugInfo self() { + return this; + } + + @Override + public PcBasedDebugInfo asPcBasedInfo() { + return this; + } + + @Override + DebugInfoKind getKind() { + return DebugInfoKind.PC_BASED; + } + + @Override + protected int computeHashCode() { + return Objects.hash(parameterCount, maxPc); + } + + @Override + public void acceptHashing(HashingVisitor visitor) { + visitor.visit(this, PcBasedDebugInfo::specify); + } + + @Override + int internalAcceptCompareTo(DexDebugInfo other, CompareToVisitor visitor) { + assert other.isPcBasedInfo(); + return visitor.visit(this, other.asPcBasedInfo(), PcBasedDebugInfo::specify); + } + + @Override + public void collectMixedSectionItems(MixedSectionCollection collection) { + collection.add(this); + } + + @Override + public void collectIndexedItems(IndexedItemCollection indexedItems, GraphLens graphLens) { + // No indexed items to collect. + } + + @Override + public int estimatedWriteSize() { + return LebUtils.sizeAsUleb128(START_LINE) + + LebUtils.sizeAsUleb128(parameterCount) + + parameterCount * LebUtils.sizeAsUleb128(0) + + 1 + + maxPc + + 1; + } + + @Override + public void write( + DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) { + writer.putUleb128(START_LINE); + writer.putUleb128(parameterCount); + for (int i = 0; i < parameterCount; i++) { + writer.putString(null); + } + mapping.dexItemFactory().zeroChangeDefaultEvent.writeOn(writer, mapping, graphLens); + for (int i = 0; i < maxPc; i++) { + mapping.dexItemFactory().oneChangeDefaultEvent.writeOn(writer, mapping, graphLens); + } + writer.putByte(Constants.DBG_END_SEQUENCE); + } + + @Override + public String toString() { + return "PcBasedDebugInfo (params: " + + parameterCount + + ", max-pc: " + + StringUtils.hexString(maxPc, 2) + + ")"; + } + } + + public static class EventBasedDebugInfo extends DexDebugInfo { + + public final int startLine; + public final DexString[] parameters; + public DexDebugEvent[] events; + + private static void specify(StructuralSpecification<EventBasedDebugInfo, ?> spec) { + spec.withInt(d -> d.startLine) + .withItemArrayAllowingNullMembers(d -> d.parameters) + .withItemArray(d -> d.events); + } + + public EventBasedDebugInfo(int startLine, DexString[] parameters, DexDebugEvent[] events) { + assert startLine >= 0; + this.startLine = startLine; + this.parameters = parameters; + this.events = events; + } + + @Override + public DexDebugInfo self() { + return this; + } + + @Override + public EventBasedDebugInfo asEventBasedInfo() { + return this; + } + + @Override + DebugInfoKind getKind() { + return DebugInfoKind.EVENT_BASED; + } + + @Override + public int getStartLine() { + return startLine; + } + + @Override + public int getParameterCount() { + return parameters.length; + } + + public List<DexDebugEntry> computeEntries(DexMethod method) { + DexDebugEntryBuilder builder = new DexDebugEntryBuilder(startLine, method); + for (DexDebugEvent event : events) { + event.accept(builder); + } + return builder.build(); + } + + @Override + public int computeHashCode() { + return startLine + Arrays.hashCode(parameters) * 7 + Arrays.hashCode(events) * 13; + } + + @Override + public void acceptHashing(HashingVisitor visitor) { + visitor.visit(this, EventBasedDebugInfo::specify); + } + + @Override + int internalAcceptCompareTo(DexDebugInfo other, CompareToVisitor visitor) { + assert other.isEventBasedInfo(); + return visitor.visit(this, other.asEventBasedInfo(), EventBasedDebugInfo::specify); + } + + public void collectIndexedItems(IndexedItemCollection indexedItems, GraphLens graphLens) { + for (DexString parameter : parameters) { + if (parameter != null) { + parameter.collectIndexedItems(indexedItems); + } + } + for (DexDebugEvent event : events) { + event.collectIndexedItems(indexedItems, graphLens); } } - for (DexDebugEvent event : events) { - event.collectIndexedItems(indexedItems, graphLens); + + @Override + void collectMixedSectionItems(MixedSectionCollection collection) { + // Only writable info should be iterated for collection. + throw new Unreachable(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("DebugInfo (line " + startLine + ") events: [\n"); + for (DexDebugEvent event : events) { + builder.append(" ").append(event).append("\n"); + } + builder.append(" END_SEQUENCE\n"); + builder.append("]\n"); + return builder.toString(); } } - @Override - void collectMixedSectionItems(MixedSectionCollection collection) { - collection.add(this); + public static EventBasedDebugInfo convertToEventBased(DexCode code, DexItemFactory factory) { + if (code.getDebugInfo() == null) { + return null; + } + if (code.getDebugInfo().isEventBasedInfo()) { + return code.getDebugInfo().asEventBasedInfo(); + } + assert code.getDebugInfo().isPcBasedInfo(); + PcBasedDebugInfo pcBasedDebugInfo = code.getDebugInfo().asPcBasedInfo(); + // Generate a line event at each throwing instruction. + List<DexDebugEvent> events = new ArrayList<>(code.instructions.length + 1); + events.add(factory.zeroChangeDefaultEvent); + int pc = 0; + int delta = 0; + for (Instruction instruction : code.instructions) { + delta += instruction.getSize(); + if (instruction.canThrow()) { + DexDebugEventBuilder.addDefaultEventWithAdvancePcIfNecessary(delta, delta, events, factory); + pc += delta; + delta = 0; + } + } + assert pc + delta - ArrayUtils.last(code.instructions).getSize() <= pcBasedDebugInfo.maxPc; + return new EventBasedDebugInfo( + PcBasedDebugInfo.START_LINE, + new DexString[pcBasedDebugInfo.getParameterCount()], + events.toArray(DexDebugEvent.EMPTY_ARRAY)); } - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("DebugInfo (line " + startLine + ") events: [\n"); - for (DexDebugEvent event : events) { - builder.append(" ").append(event).append("\n"); + public static DexDebugInfoForWriting convertToWritable(DexDebugInfo debugInfo) { + if (debugInfo == null) { + return null; } - builder.append(" END_SEQUENCE\n"); - builder.append("]\n"); - return builder.toString(); + if (debugInfo.isPcBasedInfo()) { + return debugInfo.asPcBasedInfo(); + } + EventBasedDebugInfo eventBasedInfo = debugInfo.asEventBasedInfo(); + DexDebugEvent[] writableEvents = + ArrayUtils.filter( + eventBasedInfo.events, DexDebugEvent::isWritableEvent, DexDebugEvent.EMPTY_ARRAY); + return new WritableEventBasedDebugInfo( + eventBasedInfo.startLine, eventBasedInfo.parameters, writableEvents); + } + + private static class WritableEventBasedDebugInfo extends EventBasedDebugInfo + implements DexDebugInfoForWriting { + + private WritableEventBasedDebugInfo( + int startLine, DexString[] parameters, DexDebugEvent[] writableEvents) { + super(startLine, parameters, writableEvents); + } + + @Override + public void collectIndexedItems(IndexedItemCollection indexedItems, GraphLens graphLens) { + super.collectIndexedItems(indexedItems, graphLens); + } + + @Override + public void collectMixedSectionItems(MixedSectionCollection collection) { + collection.add(this); + } + + @Override + public int estimatedWriteSize() { + return LebUtils.sizeAsUleb128(startLine) + + LebUtils.sizeAsUleb128(parameters.length) + // Estimate 4 bytes per parameter pointer. + + parameters.length * 4 + + events.length + + 1; + } + + @Override + public void write( + DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) { + writer.putUleb128(startLine); + writer.putUleb128(parameters.length); + for (DexString name : parameters) { + writer.putString(name); + } + for (DexDebugEvent event : events) { + event.writeOn(writer, mapping, graphLens); + } + writer.putByte(Constants.DBG_END_SEQUENCE); + } } }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugInfoForSingleLineMethod.java b/src/main/java/com/android/tools/r8/graph/DexDebugInfoForSingleLineMethod.java index 32d3126..8d41561 100644 --- a/src/main/java/com/android/tools/r8/graph/DexDebugInfoForSingleLineMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexDebugInfoForSingleLineMethod.java
@@ -4,7 +4,9 @@ package com.android.tools.r8.graph; -public class DexDebugInfoForSingleLineMethod extends DexDebugInfo { +import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo; + +public class DexDebugInfoForSingleLineMethod extends EventBasedDebugInfo { private static final DexDebugInfoForSingleLineMethod INSTANCE = new DexDebugInfoForSingleLineMethod(0, DexString.EMPTY_ARRAY, DexDebugEvent.EMPTY_ARRAY);
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugInfoForWriting.java b/src/main/java/com/android/tools/r8/graph/DexDebugInfoForWriting.java index ddb4efc..5f94db9 100644 --- a/src/main/java/com/android/tools/r8/graph/DexDebugInfoForWriting.java +++ b/src/main/java/com/android/tools/r8/graph/DexDebugInfoForWriting.java
@@ -4,36 +4,18 @@ package com.android.tools.r8.graph; -/** - * Wraps DexDebugInfo to make comparison and hashcode not consider - * the SetInlineFrames - */ -public class DexDebugInfoForWriting extends DexDebugInfo { +import com.android.tools.r8.dex.DebugBytecodeWriter; +import com.android.tools.r8.dex.IndexedItemCollection; +import com.android.tools.r8.dex.MixedSectionCollection; - private DexDebugInfoForWriting(int startLine, DexString[] parameters, DexDebugEvent[] events) { - super(startLine, parameters, events); - } +/** Interface to guarantee that the info only contains writable events. */ +public interface DexDebugInfoForWriting { - public static DexDebugInfoForWriting create(DexDebugInfo debugInfo) { - assert debugInfo != null; - int nonWritableEvents = 0; - for (DexDebugEvent event : debugInfo.events) { - if (!event.isWritableEvent()) { - nonWritableEvents++; - } - } - DexDebugEvent[] writableEvents; - if (nonWritableEvents == 0) { - writableEvents = debugInfo.events; - } else { - writableEvents = new DexDebugEvent[debugInfo.events.length - nonWritableEvents]; - int i = 0; - for (DexDebugEvent event : debugInfo.events) { - if (event.isWritableEvent()) { - writableEvents[i++] = event; - } - } - } - return new DexDebugInfoForWriting(debugInfo.startLine, debugInfo.parameters, writableEvents); - } + void collectMixedSectionItems(MixedSectionCollection collection); + + void collectIndexedItems(IndexedItemCollection indexedItems, GraphLens graphLens); + + int estimatedWriteSize(); + + void write(DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens); }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java index 289f255..865a536 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -992,9 +992,9 @@ public static void setDebugInfoWithFakeThisParameter(Code code, int arity, AppView<?> appView) { if (code.isDexCode()) { DexCode dexCode = code.asDexCode(); - dexCode.setDebugInfo(dexCode.debugInfoWithFakeThisParameter(appView.dexItemFactory())); - assert (dexCode.getDebugInfo() == null) - || (arity == dexCode.getDebugInfo().parameters.length); + DexDebugInfo newDebugInfo = dexCode.debugInfoWithFakeThisParameter(appView.dexItemFactory()); + assert (newDebugInfo == null) || (arity == newDebugInfo.getParameterCount()); + dexCode.setDebugInfo(newDebugInfo); } else { assert code.isCfCode(); CfCode cfCode = code.asCfCode(); @@ -1222,7 +1222,7 @@ Builder builder = builder(this) .promoteToStatic() - .withoutThisParameter() + .withoutThisParameter(appView.dexItemFactory()) .fixupOptimizationInfo(appView, prototypeChanges.createMethodOptimizationInfoFixer()) .setGenericSignature(MethodTypeSignature.noSignature()); DexEncodedMethod method = builder.build(); @@ -1524,10 +1524,10 @@ return this; } - public Builder withoutThisParameter() { + public Builder withoutThisParameter(DexItemFactory factory) { assert code != null; if (code.isDexCode()) { - code = code.asDexCode().withoutThisParameter(); + code = code.asDexCode().withoutThisParameter(factory); } else { throw new Unreachable("Code " + code.getClass().getSimpleName() + " is not supported."); }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java index c064992..5421ba6 100644 --- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java +++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -99,6 +99,8 @@ private final Map<DexString, SetFile> setFiles = new HashMap<>(); private final SetOutlineFrame setOutlineFrame = new SetOutlineFrame(); private final Map<SetInlineFrame, SetInlineFrame> setInlineFrames = new HashMap<>(); + public final DexDebugEvent.Default zeroChangeDefaultEvent = createDefault(0, 0); + public final DexDebugEvent.Default oneChangeDefaultEvent = createDefault(1, 1); // ReferenceTypeElement canonicalization. private final ConcurrentHashMap<DexType, ReferenceTypeElement> referenceTypes = @@ -2744,6 +2746,10 @@ } } + public Default createDefault(int lineDelta, int pcDelta) { + return createDefault(Default.create(lineDelta, pcDelta).value); + } + public EndLocal createEndLocal(int registerNum) { synchronized (endLocals) { return endLocals.computeIfAbsent(registerNum, EndLocal::new);
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java index ae16c5a..457a6f7 100644 --- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java +++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -170,8 +170,8 @@ } @Override - public Code getCodeAsInlining(DexMethod caller, DexMethod callee) { - return asCfCode().getCodeAsInlining(caller, callee); + public Code getCodeAsInlining(DexMethod caller, DexMethod callee, DexItemFactory factory) { + return asCfCode().getCodeAsInlining(caller, callee, factory); } public static class DebugParsingOptions {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java index f2c1d8d..6dec27c 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -36,6 +36,7 @@ import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair; import com.android.tools.r8.graph.DexDebugEntry; import com.android.tools.r8.graph.DexDebugInfo; +import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexType; @@ -77,11 +78,15 @@ private final DexMethod originalMethod; public DexSourceCode( - DexCode code, ProgramMethod method, DexMethod originalMethod, Position callerPosition) { + DexCode code, + ProgramMethod method, + DexMethod originalMethod, + Position callerPosition, + DexItemFactory factory) { this.code = code; this.method = method; this.originalMethod = originalMethod; - DexDebugInfo info = code.getDebugInfo(); + EventBasedDebugInfo info = DexDebugInfo.convertToEventBased(code, factory); if (info != null) { debugEntries = info.computeEntries(originalMethod); }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java index de940f8..71db541 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -245,6 +245,7 @@ private void finalizeConstantDynamicDesugaring(Consumer<ProgramMethod> needsProcessing) { for (ConstantDynamicClass constantDynamicClass : synthesizedConstantDynamicClasses) { + constantDynamicClass.rewriteBootstrapMethodSignatureIfNeeded(); constantDynamicClass.getConstantDynamicProgramClass().forEachProgramMethod(needsProcessing); } synthesizedConstantDynamicClasses.clear(); @@ -408,6 +409,7 @@ public void finalizeDesugaring() { finalizeInvokeSpecialDesugaring(); finalizeLambdaDesugaring(); + // TODO(b/210485236): Finalize constant dynamic desugaring by rewriting signature if needed. } private void finalizeInvokeSpecialDesugaring() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java index fb848df..a332828 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
@@ -34,6 +34,7 @@ import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; +import com.android.tools.r8.graph.Code; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexField; @@ -41,6 +42,7 @@ import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexMethodHandle; import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.FieldAccessFlags; import com.android.tools.r8.graph.MethodAccessFlags; import com.android.tools.r8.graph.MethodResolutionResult; @@ -51,10 +53,13 @@ import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.ir.desugar.FreshLocalProvider; import com.android.tools.r8.ir.desugar.LocalStackAllocator; +import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper; import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations; import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.MethodSynthesizerConsumer; import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations; import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.ListUtils; import com.android.tools.r8.utils.collections.ImmutableDeque; import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap; import com.google.common.collect.ImmutableList; @@ -80,6 +85,9 @@ public final DexField constantValueField; private final DexMethod getConstMethod; private final Behaviour behaviour; + private DexMethod bootstrapMethodReference; + private DexMethod finalBootstrapMethodReference; + private boolean isFinalBootstrapMethodReferenceOnInterface; // Considered final but is set after due to circularity in allocation. private DexProgramClass clazz = null; @@ -108,19 +116,49 @@ factory.createString("get")); DexMethodHandle bootstrapMethodHandle = reference.getBootstrapMethod(); - DexMethod bootstrapMethodReference = bootstrapMethodHandle.asMethod(); + bootstrapMethodReference = bootstrapMethodHandle.asMethod(); MethodResolutionResult resolution = appView .appInfoForDesugaring() .resolveMethod(bootstrapMethodReference, bootstrapMethodHandle.isInterface); if (resolution.isSingleResolution() && resolution.asSingleResolution().getResolvedMethod().isStatic()) { - // Ensure that the bootstrap method is accessible from the generated class. SingleResolutionResult result = resolution.asSingleResolution(); - MethodAccessFlags flags = result.getResolvedMethod().getAccessFlags(); - flags.unsetPrivate(); - flags.setPublic(); + if (bootstrapMethodHandle.isInterface + && appView.options().isInterfaceMethodDesugaringEnabled()) { + bootstrapMethodReference = + bootstrapMethodReference.withHolder( + InterfaceDesugaringSyntheticHelper.getCompanionClassType( + bootstrapMethodReference.getHolderType(), factory), + factory); + isFinalBootstrapMethodReferenceOnInterface = false; + } else { + assert bootstrapMethodReference.getHolderType() == resolution.getResolvedHolder().getType(); + isFinalBootstrapMethodReferenceOnInterface = bootstrapMethodHandle.isInterface; + } + if (shouldRewriteBootstrapMethodSignature()) { + // The bootstrap method will have its signature modified to have type Object as its first + // argument. + this.finalBootstrapMethodReference = + factory.createMethod( + bootstrapMethodReference.getHolderType(), + factory.createProto( + bootstrapMethodReference.getReturnType(), + factory.objectType, + factory.stringType, + factory.classType), + bootstrapMethodReference.getName()); + } else { + this.finalBootstrapMethodReference = bootstrapMethodReference; + // Ensure that the bootstrap method is accessible from the generated class. + DexEncodedMethod bootstrapMethodImpl = result.getResolvedMethod(); + MethodAccessFlags flags = bootstrapMethodImpl.getAccessFlags(); + flags.unsetPrivate(); + flags.setPublic(); + } + behaviour = CACHE_CONSTANT; + synthesizeConstantDynamicClass(builder); } else { // Unconditionally throw as the RI. @@ -128,6 +166,12 @@ } } + private boolean shouldRewriteBootstrapMethodSignature() { + // TODO(b/210485236): Check for references to the bootstrap method outside of dynamic constant. + return !appView.enableWholeProgramOptimizations() + && appView.options().getMinApiLevel().isLessThan(AndroidApiLevel.O); + } + public Collection<CfInstruction> desugarConstDynamicInstruction( CfConstDynamic invoke, FreshLocalProvider freshLocalProvider, @@ -214,13 +258,15 @@ private void invokeBootstrapMethod(ImmutableList.Builder<CfInstruction> instructions) { assert reference.getBootstrapMethod().type.isInvokeStatic(); - DexMethodHandle bootstrapMethodHandle = reference.getBootstrapMethod(); - DexMethod bootstrapMethodReference = bootstrapMethodHandle.asMethod(); // TODO(b/178172809): Use MethodHandle.invokeWithArguments if supported. instructions.add(new CfConstNull()); instructions.add(new CfConstString(reference.getName())); instructions.add(new CfConstClass(reference.getType())); - instructions.add(new CfInvoke(INVOKESTATIC, bootstrapMethodReference, false)); + instructions.add( + new CfInvoke( + INVOKESTATIC, + finalBootstrapMethodReference, + isFinalBootstrapMethodReferenceOnInterface)); instructions.add(new CfCheckCast(reference.getType())); } @@ -317,4 +363,76 @@ assert clazz != null; this.clazz = clazz; } + + public void rewriteBootstrapMethodSignatureIfNeeded() { + if (!shouldRewriteBootstrapMethodSignature() || behaviour != CACHE_CONSTANT) { + return; + } + DexProgramClass bootstrapMethodHolder = + appView.definitionFor(bootstrapMethodReference.getHolderType()).asProgramClass(); + DexEncodedMethod replacement = + bootstrapMethodHolder + .getMethodCollection() + .replaceDirectMethod( + bootstrapMethodReference, + encodedMethod -> { + MethodAccessFlags newAccessFlags = encodedMethod.accessFlags.copy(); + // Ensure that the bootstrap method is accessible from the generated class. + newAccessFlags.unsetPrivate(); + newAccessFlags.setPublic(); + DexEncodedMethod newMethod = + DexEncodedMethod.syntheticBuilder() + .setMethod(finalBootstrapMethodReference) + .setAccessFlags(newAccessFlags) + .setGenericSignature(encodedMethod.getGenericSignature()) + .setAnnotations(encodedMethod.annotations()) + .setParameterAnnotations(encodedMethod.parameterAnnotationsList) + .setCode(adaptCode(encodedMethod)) + .setApiLevelForDefinition(encodedMethod.getApiLevelForDefinition()) + .setApiLevelForCode(encodedMethod.getApiLevelForCode()) + .build(); + newMethod.copyMetadata(appView, encodedMethod); + return newMethod; + }); + if (replacement != null) { + // Since we've copied the code object from an existing method, the code should already be + // processed, and thus we don't need to schedule it for processing in D8. + assert !appView.options().isGeneratingClassFiles() || replacement.getCode().isCfCode(); + assert !appView.options().isGeneratingDex() || replacement.getCode().isDexCode(); + } + // The method might already have been moved by another dynamic constant targeting it. + // If so, it must be defined on the holder. + ProgramMethod modified = + bootstrapMethodHolder.lookupProgramMethod(finalBootstrapMethodReference); + assert modified != null; + assert modified.getDefinition().isPublicMethod(); + } + + private DexType mapLookupTypeToObject(DexType type) { + return type == appView.dexItemFactory().lookupType ? appView.dexItemFactory().objectType : type; + } + + private Code adaptCode(DexEncodedMethod method) { + assert behaviour == CACHE_CONSTANT; + if (method.getCode().isDexCode()) { + return method.getCode(); + } + CfCode code = method.getCode().asCfCode(); + List<CfInstruction> newInstructions = + ListUtils.mapOrElse( + code.getInstructions(), + instruction -> + instruction.isFrame() + ? instruction.asFrame().map(this::mapLookupTypeToObject) + : instruction); + return code.getInstructions() != newInstructions + ? new CfCode( + method.getHolderType(), + code.getMaxStack(), + code.getMaxLocals(), + newInstructions, + code.getTryCatchRanges(), + code.getLocalVariables()) + : code; + } }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java index 616cc07..77d99e5 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
@@ -155,7 +155,7 @@ } // Gets the companion class for the interface `type`. - static DexType getCompanionClassType(DexType type, DexItemFactory factory) { + public static DexType getCompanionClassType(DexType type, DexItemFactory factory) { assert type.isClassType(); String descriptor = type.descriptor.toString(); String ccTypeDescriptor = getCompanionClassDescriptor(descriptor);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java index c89f90c..82f80e8 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
@@ -168,7 +168,10 @@ companion.getDefinition().downgradeClassFileVersion(definition.getClassFileVersion()); } Code code = - definition.getCode().getCodeAsInlining(companion.getReference(), method.getReference()); + definition + .getCode() + .getCodeAsInlining( + companion.getReference(), method.getReference(), appView.dexItemFactory()); if (!definition.isStatic()) { DexEncodedMethod.setDebugInfoWithFakeThisParameter( code, companion.getReference().getArity(), appView);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java index 3875ddf..d35c7b7 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -247,7 +247,7 @@ } } if (outValue.getType().isNullType()) { - addNullDependencies(code, outValue.uniqueUsers(), eligibleEnums); + addNullDependencies(code, outValue, eligibleEnums); } } else { if (instruction.isInvokeMethod()) { @@ -290,7 +290,7 @@ } } if (phi.getType().isNullType()) { - addNullDependencies(code, phi.uniqueUsers(), eligibleEnums); + addNullDependencies(code, phi, eligibleEnums); } } } @@ -520,8 +520,8 @@ || method == factory.classMethods.getSimpleName; } - private void addNullDependencies(IRCode code, Set<Instruction> uses, Set<DexType> eligibleEnums) { - for (Instruction use : uses) { + private void addNullDependencies(IRCode code, Value nullValue, Set<DexType> eligibleEnums) { + for (Instruction use : nullValue.uniqueUsers()) { if (use.isInvokeMethod()) { InvokeMethod invokeMethod = use.asInvokeMethod(); DexMethod invokedMethod = invokeMethod.getInvokedMethod(); @@ -530,7 +530,8 @@ eligibleEnums.add(paramType); } } - if (invokeMethod.isInvokeMethodWithReceiver()) { + if (invokeMethod.isInvokeMethodWithReceiver() + && invokeMethod.asInvokeMethodWithReceiver().getReceiver() == nullValue) { DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(invokedMethod.holder); if (enumClass != null) { markEnumAsUnboxable(Reason.ENUM_METHOD_CALLED_WITH_NULL_RECEIVER, enumClass);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java index 33f4ba5..dca8e31 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
@@ -356,6 +356,10 @@ if (!candidateBuilders.contains(builder)) { continue; } + if (builder.hasPhiUsers()) { + candidateBuilders.remove(builder); + continue; + } Map<Instruction, BuilderState> perInstrState = createBuilderState(builder); perInstrState.put(instr, BuilderState.createRoot()); continue; @@ -396,6 +400,10 @@ if (!candidateBuilders.contains(builder)) { continue; } + if (invoke.hasUsedOutValue()) { + candidateBuilders.remove(builder); + continue; + } Value arg = invoke.inValues().get(1).getAliasedValue(); DexType argType = invoke.getInvokedMethod().proto.parameters.values[0]; String addition = extractConstantArgument(arg, argType);
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java index bec8073..f948457 100644 --- a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java +++ b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
@@ -180,6 +180,7 @@ ReservedFieldNamingState reservedNames = getOrCreateReservedFieldNamingState(clazz.type); + // TODO(b/213041051): This could avoid duplication of strings downwards. FieldNamingState state = parentState.createChildState(reservedNames); if (clazz.isProgramClass()) { clazz.asProgramClass().forEachProgramField(field -> renameField(field, state)); @@ -191,8 +192,8 @@ } private void renameFieldsInInterfaces(Collection<DexClass> interfaces) { - InterfacePartitioning partioning = new InterfacePartitioning(this); - for (Set<DexClass> partition : partioning.sortedPartitions(interfaces)) { + InterfacePartitioning partitioning = new InterfacePartitioning(this); + for (Set<DexClass> partition : partitioning.sortedPartitions(interfaces)) { renameFieldsInInterfacePartition(partition); } } @@ -231,11 +232,10 @@ if (!visited.add(implementationType)) { continue; } - DexClass implementation = appView.definitionFor(implementationType); if (implementation != null) { getOrCreateReservedFieldNamingState(implementationType) - .includeReservations(namesToBeReservedInImplementsSubclasses); + .setInterfaceMinificationState(namesToBeReservedInImplementsSubclasses); } } }
diff --git a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java index 3ba2467..8067817 100644 --- a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java +++ b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
@@ -654,9 +654,8 @@ DexType frontierType = minifierState.getFrontier(clazz.type); iState.addReservationType(frontierType); // The reservation state should already be added, but if a class is - // extending - // an interface, we will not visit the class during the sub-type - // traversel + // extending an interface, we will not visit the class during the + // sub-type traversel. if (minifierState.getReservationState(clazz.type) == null) { minifierState.allocateReservationStateAndReserve( clazz.type, frontierType);
diff --git a/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java b/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java index 4e3e98c..258646e 100644 --- a/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java +++ b/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java
@@ -14,16 +14,32 @@ class ReservedFieldNamingState extends FieldNamingStateBase<InternalState> { + private ReservedFieldNamingState interfaceMinificationState = null; + ReservedFieldNamingState(AppView<? extends AppInfoWithClassHierarchy> appView) { super(appView, new IdentityHashMap<>()); } boolean isReserved(DexString name, DexType type) { - return getReservedByName(name, type) != null; + return getReservedByName(name, type) != null + || getReservedByNameInInterfaces(name, type) != null; } - DexString getReservedByName(DexString name, DexType type) { - InternalState internalState = getInternalState(type); + private DexString getReservedByName(DexString name, DexType type) { + DexString reservedByNameInState = getReservedByNameInState(getInternalState(type), name); + if (reservedByNameInState != null) { + return reservedByNameInState; + } + return getReservedByNameInInterfaces(name, type); + } + + private DexString getReservedByNameInInterfaces(DexString name, DexType type) { + return interfaceMinificationState == null + ? null + : getReservedByNameInState(interfaceMinificationState.getInternalState(type), name); + } + + private static DexString getReservedByNameInState(InternalState internalState, DexString name) { return internalState == null ? null : internalState.getReservedByName(name); } @@ -35,12 +51,28 @@ for (Map.Entry<DexType, InternalState> entry : reservedNames.internalStates.entrySet()) { getOrCreateInternalState(entry.getKey()).includeReservations(entry.getValue()); } + includeInterfaceReservationState(reservedNames); } void includeReservationsFromBelow(ReservedFieldNamingState reservedNames) { for (Map.Entry<DexType, InternalState> entry : reservedNames.internalStates.entrySet()) { getOrCreateInternalState(entry.getKey()).includeReservationsFromBelow(entry.getValue()); } + includeInterfaceReservationState(reservedNames); + } + + private void includeInterfaceReservationState(ReservedFieldNamingState reservedNames) { + if (reservedNames.interfaceMinificationState != null) { + assert interfaceMinificationState == null + || interfaceMinificationState == reservedNames.interfaceMinificationState; + interfaceMinificationState = reservedNames.interfaceMinificationState; + } + } + + void setInterfaceMinificationState(ReservedFieldNamingState namingState) { + assert namingState != null; + assert interfaceMinificationState == null; + this.interfaceMinificationState = namingState; } @Override
diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java index 24c6a60..06fdc4c 100644 --- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java +++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -131,6 +131,10 @@ return results != null ? results.toArray(emptyArray) : original; } + public static <T> T[] filter(T[] original, Predicate<T> test, T[] emptyArray) { + return map(original, e -> test.test(e) ? e : null, emptyArray); + } + public static int[] createIdentityArray(int size) { int[] array = new int[size]; for (int i = 0; i < size; i++) {
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java index 0bfc079..1000a75 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1869,8 +1869,12 @@ return sourceFileProvider != null && sourceFileProvider.allowDiscardingSourceFile(); } - public boolean canUseDexPcAsDebugInformation() { - return lineNumberOptimization == LineNumberOptimization.ON + public boolean canUseDexPc2PcAsDebugInformation() { + return lineNumberOptimization == LineNumberOptimization.ON; + } + + public boolean canUseNativeDexPcInsteadOfDebugInfo() { + return canUseDexPc2PcAsDebugInformation() && hasMinApi(AndroidApiLevel.O) && allowDiscardingResidualDebugInfo(); }
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java index 71da5cc..50eeb68 100644 --- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java +++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -27,6 +27,7 @@ import com.android.tools.r8.graph.DexDebugEventBuilder; import com.android.tools.r8.graph.DexDebugEventVisitor; import com.android.tools.r8.graph.DexDebugInfo; +import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo; import com.android.tools.r8.graph.DexDebugInfoForSingleLineMethod; import com.android.tools.r8.graph.DexDebugPositionState; import com.android.tools.r8.graph.DexEncodedMethod; @@ -73,7 +74,10 @@ import it.unimi.dsi.fastutil.ints.Int2IntArrayMap; import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2IntSortedMap; +import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; +import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; import java.util.ArrayList; import java.util.IdentityHashMap; import java.util.List; @@ -345,6 +349,117 @@ return mapId; } + private interface PcBasedDebugInfoRecorder { + /** Callback to record a code object with a given max instruction PC and parameter count. */ + void recordPcMappingFor(DexCode code, int lastInstructionPc, int parameterCount); + + /** Callback to record a code object with only a single "line". */ + void recordSingleLineFor(DexCode code, int parameterCount); + + void recordSingleLineFor(DexCode code, int parameterCount, int lastInstructionPc); + + /** + * Install the correct debug info objects. + * + * <p>Must be called after all recordings have been given to allow computing the debug info + * items to be installed. + */ + void updateDebugInfoInCodeObjects(); + } + + private static class Pc2PcMappingSupport implements PcBasedDebugInfoRecorder { + + // Some DEX VMs require matching parameter count in methods and debug info. + // Record the max pc for each parameter count so we can share the param count objects. + private Int2IntMap paramToMaxPc = new Int2IntOpenHashMap(); + + private final List<Pair<Integer, DexCode>> codesToUpdate = new ArrayList<>(); + + // We can only drop single-line debug info if it is OK to lose the source-file info. + // This list is null if we must retain single-line entries. + private final List<DexCode> singleLineCodesToClear; + + public Pc2PcMappingSupport(boolean allowDiscardingSourceFile) { + singleLineCodesToClear = allowDiscardingSourceFile ? new ArrayList<>() : null; + } + + @Override + public void recordPcMappingFor(DexCode code, int lastInstructionPc, int parameterCount) { + codesToUpdate.add(new Pair<>(parameterCount, code)); + int existing = paramToMaxPc.getOrDefault(parameterCount, -1); + if (existing < lastInstructionPc) { + paramToMaxPc.put(parameterCount, lastInstructionPc); + } + } + + @Override + public void recordSingleLineFor(DexCode code, int parameterCount) { + if (singleLineCodesToClear != null) { + singleLineCodesToClear.add(code); + return; + } + int lastInstructionPc = ArrayUtils.last(code.instructions).getOffset(); + recordPcMappingFor(code, lastInstructionPc, parameterCount); + } + + @Override + public void recordSingleLineFor(DexCode code, int parameterCount, int lastInstructionPc) { + if (singleLineCodesToClear != null) { + singleLineCodesToClear.add(code); + return; + } + recordPcMappingFor(code, lastInstructionPc, parameterCount); + } + + @Override + public void updateDebugInfoInCodeObjects() { + Int2ReferenceMap<DexDebugInfo> debugInfos = + new Int2ReferenceOpenHashMap<>(paramToMaxPc.size()); + codesToUpdate.forEach( + entry -> { + int parameterCount = entry.getFirst(); + DexCode code = entry.getSecond(); + DexDebugInfo debugInfo = + debugInfos.computeIfAbsent( + parameterCount, + key -> buildPc2PcDebugInfo(paramToMaxPc.get(key), parameterCount)); + code.setDebugInfo(debugInfo); + }); + if (singleLineCodesToClear != null) { + singleLineCodesToClear.forEach(c -> c.setDebugInfo(null)); + } + } + + private static DexDebugInfo buildPc2PcDebugInfo(int lastInstructionPc, int parameterCount) { + return new DexDebugInfo.PcBasedDebugInfo(parameterCount, lastInstructionPc); + } + } + + private static class NativePcSupport implements PcBasedDebugInfoRecorder { + + @Override + public void recordPcMappingFor(DexCode code, int lastInstructionPc, int length) { + // Strip the info in full as the runtime will emit the PC directly. + code.setDebugInfo(null); + } + + @Override + public void recordSingleLineFor(DexCode code, int parameterCount) { + // Strip the info at once as it does not conflict with any PC mapping update. + code.setDebugInfo(null); + } + + @Override + public void recordSingleLineFor(DexCode code, int parameterCount, int lastInstructionPc) { + recordSingleLineFor(code, parameterCount); + } + + @Override + public void updateDebugInfoInCodeObjects() { + // Already null out the info so nothing to do. + } + } + public static ClassNameMapper run( AppView<?> appView, DexApplication application, @@ -358,6 +473,11 @@ Map<DexMethod, OutlineFixupBuilder> outlinesToFix = new IdentityHashMap<>(); + PcBasedDebugInfoRecorder pcBasedDebugInfo = + appView.options().canUseNativeDexPcInsteadOfDebugInfo() + ? new NativePcSupport() + : new Pc2PcMappingSupport(appView.options().allowDiscardingResidualDebugInfo()); + // Collect which files contain which classes that need to have their line numbers optimized. for (DexProgramClass clazz : application.classes()) { boolean isSyntheticClass = appView.getSyntheticItems().isSyntheticClass(clazz); @@ -440,11 +560,13 @@ List<MappedPosition> mappedPositions; Code code = method.getCode(); boolean canUseDexPc = - appView.options().canUseDexPcAsDebugInformation() && methods.size() == 1; + appView.options().canUseDexPc2PcAsDebugInformation() && methods.size() == 1; if (code != null) { if (code.isDexCode() && doesContainPositions(code.asDexCode())) { if (canUseDexPc) { - mappedPositions = optimizeDexCodePositionsForPc(method, appView, kotlinRemapper); + mappedPositions = + optimizeDexCodePositionsForPc( + method, appView, kotlinRemapper, pcBasedDebugInfo); } else { mappedPositions = optimizeDexCodePositions( @@ -615,8 +737,8 @@ if (method.getCode().isDexCode() && method.getCode().asDexCode().getDebugInfo() == DexDebugInfoForSingleLineMethod.getInstance()) { - assert appView.options().allowDiscardingResidualDebugInfo(); - method.getCode().asDexCode().setDebugInfo(null); + pcBasedDebugInfo.recordSingleLineFor( + method.getCode().asDexCode(), method.getParameters().size()); } } // for each method of the group } // for each method group, grouped by name @@ -625,6 +747,9 @@ // Fixup all outline positions outlinesToFix.values().forEach(OutlineFixupBuilder::fixup); + // Update all the debug-info objects. + pcBasedDebugInfo.updateDebugInfoInCodeObjects(); + return classNameMapperBuilder.build(); } @@ -727,7 +852,7 @@ } if (code.isDexCode()) { DexDebugInfo dexDebugInfo = code.asDexCode().getDebugInfo(); - return dexDebugInfo == null ? 0 : dexDebugInfo.startLine; + return dexDebugInfo == null ? 0 : dexDebugInfo.getStartLine(); } else if (code.isCfCode()) { List<CfInstruction> instructions = code.asCfCode().getInstructions(); for (CfInstruction instruction : instructions) { @@ -823,7 +948,10 @@ if (debugInfo == null) { return false; } - for (DexDebugEvent event : debugInfo.events) { + if (debugInfo.isPcBasedInfo()) { + return true; + } + for (DexDebugEvent event : debugInfo.asEventBasedInfo().events) { if (event instanceof DexDebugEvent.Default) { return true; } @@ -851,7 +979,10 @@ // Do the actual processing for each method. DexApplication application = appView.appInfo().app(); DexCode dexCode = method.getCode().asDexCode(); - DexDebugInfo debugInfo = dexCode.getDebugInfo(); + // TODO(b/213411850): Do we need to reconsider conversion here to support pc-based D8 merging? + EventBasedDebugInfo debugInfo = + DexDebugInfo.convertToEventBased(dexCode, appView.dexItemFactory()); + assert debugInfo != null; List<DexDebugEvent> processedEvents = new ArrayList<>(); PositionEventEmitter positionEventEmitter = @@ -949,8 +1080,8 @@ return mappedPositions; } - DexDebugInfo optimizedDebugInfo = - new DexDebugInfo( + EventBasedDebugInfo optimizedDebugInfo = + new EventBasedDebugInfo( positionEventEmitter.getStartLine(), debugInfo.parameters, processedEvents.toArray(DexDebugEvent.EMPTY_ARRAY)); @@ -985,14 +1116,19 @@ } private static List<MappedPosition> optimizeDexCodePositionsForPc( - DexEncodedMethod method, AppView<?> appView, PositionRemapper positionRemapper) { + DexEncodedMethod method, + AppView<?> appView, + PositionRemapper positionRemapper, + PcBasedDebugInfoRecorder debugInfoProvider) { List<MappedPosition> mappedPositions = new ArrayList<>(); // Do the actual processing for each method. DexCode dexCode = method.getCode().asDexCode(); - DexDebugInfo debugInfo = dexCode.getDebugInfo(); - + // TODO(b/213411850): Do we need to reconsider conversion here to support pc-based D8 merging? + EventBasedDebugInfo debugInfo = + DexDebugInfo.convertToEventBased(dexCode, appView.dexItemFactory()); + assert debugInfo != null; + BooleanBox singleOriginalLine = new BooleanBox(true); Pair<Integer, Position> lastPosition = new Pair<>(); - DexDebugEventVisitor visitor = new DexDebugPositionState( debugInfo.startLine, @@ -1001,7 +1137,12 @@ public void visit(Default defaultEvent) { super.visit(defaultEvent); assert getCurrentLine() >= 0; + Position currentPosition = getPositionFromPositionState(this); if (lastPosition.getSecond() != null) { + if (singleOriginalLine.isTrue() + && !currentPosition.equals(lastPosition.getSecond())) { + singleOriginalLine.set(false); + } remapAndAddForPc( lastPosition.getFirst(), getCurrentPc(), @@ -1010,7 +1151,7 @@ mappedPositions); } lastPosition.setFirst(getCurrentPc()); - lastPosition.setSecond(getPositionFromPositionState(this)); + lastPosition.setSecond(currentPosition); resetOutlineInformation(); } }; @@ -1019,22 +1160,32 @@ event.accept(visitor); } + int lastInstructionPc = ArrayUtils.last(dexCode.instructions).getOffset(); if (lastPosition.getSecond() != null) { - int lastPc = dexCode.instructions[dexCode.instructions.length - 1].getOffset(); remapAndAddForPc( lastPosition.getFirst(), - lastPc + 1, + lastInstructionPc + 1, lastPosition.getSecond(), positionRemapper, mappedPositions); } - dexCode.setDebugInfo(null); + // If we only have one original line we can always retrace back uniquely. + assert !mappedPositions.isEmpty(); + if (singleOriginalLine.isTrue() + && lastPosition.getSecond() != null + && !mappedPositions.get(0).isOutlineCaller()) { + dexCode.setDebugInfo(DexDebugInfoForSingleLineMethod.getInstance()); + debugInfoProvider.recordSingleLineFor( + dexCode, method.getParameters().size(), lastInstructionPc); + } else { + debugInfoProvider.recordPcMappingFor(dexCode, lastInstructionPc, debugInfo.parameters.length); + } return mappedPositions; } private static boolean verifyIdentityMapping( - DexDebugInfo originalDebugInfo, DexDebugInfo optimizedDebugInfo) { + EventBasedDebugInfo originalDebugInfo, EventBasedDebugInfo optimizedDebugInfo) { assert optimizedDebugInfo.startLine == originalDebugInfo.startLine; assert optimizedDebugInfo.events.length == originalDebugInfo.events.length; for (int i = 0; i < originalDebugInfo.events.length; ++i) {
diff --git a/src/main/java/com/android/tools/r8/utils/Timing.java b/src/main/java/com/android/tools/r8/utils/Timing.java index 04d801c..be77df56 100644 --- a/src/main/java/com/android/tools/r8/utils/Timing.java +++ b/src/main/java/com/android/tools/r8/utils/Timing.java
@@ -59,8 +59,13 @@ } @Override - public void scope(String title, TimingScope fn) { - // Ignore. + public <T> T scope(String title, TimingScope<T> fn) { + return fn.apply(); + } + + @Override + public void vscope(String title, VoidTimingScope fn) { + fn.apply(); } }; @@ -402,7 +407,7 @@ top.report(0, top); } - public void scope(String title, TimingScope fn) { + public void vscope(String title, VoidTimingScope fn) { begin(title); try { fn.apply(); @@ -411,7 +416,20 @@ } } - public interface TimingScope { + public <T> T scope(String title, TimingScope<T> fn) { + begin(title); + try { + return fn.apply(); + } finally { + end(); + } + } + + public interface TimingScope<T> { + T apply(); + } + + public interface VoidTimingScope { void apply(); }
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java index aec53db..ae8a263 100644 --- a/src/test/java/com/android/tools/r8/TestBuilder.java +++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -61,6 +61,23 @@ return self; } + public T applyIf( + boolean value, + ThrowableConsumer<T> trueConsumer, + boolean value2, + ThrowableConsumer<T> trueConsumer2, + ThrowableConsumer<T> falseConsumer) { + T self = self(); + if (value) { + trueConsumer.acceptWithRuntimeException(self); + } else if (value2) { + trueConsumer2.acceptWithRuntimeException(self); + } else { + falseConsumer.acceptWithRuntimeException(self); + } + return self; + } + @Deprecated public RR run(String mainClass) throws CompilationFailedException, ExecutionException, IOException {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java index c41c5e2..4b94e1c 100644 --- a/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java
@@ -48,6 +48,7 @@ .apply(ApiModelingTestHelper::enableApiCallerIdentification) .apply(ApiModelingTestHelper::disableCheckAllApiReferencesAreNotUnknown) .apply(setMockApiLevelForClass(Api.class, AndroidApiLevel.L_MR1)) + .apply(ApiModelingTestHelper::disableOutliningAndStubbing) .compile() .addRunClasspathClasses(Api.class) .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldTypeReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldTypeReferenceTest.java index 302cca5..f946a84 100644 --- a/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldTypeReferenceTest.java +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldTypeReferenceTest.java
@@ -47,6 +47,7 @@ .apply(setMockApiLevelForClass(Api.class, AndroidApiLevel.L_MR1)) .apply(setMockApiLevelForDefaultInstanceInitializer(Api.class, AndroidApiLevel.L_MR1)) .apply(ApiModelingTestHelper::enableApiCallerIdentification) + .apply(ApiModelingTestHelper::disableOutliningAndStubbing) .apply( addTracedApiReferenceLevelCallBack( (method, apiLevel) -> {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineInSameClassDifferentApiLevelTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineInSameClassDifferentApiLevelTest.java index 911731b..fc29914 100644 --- a/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineInSameClassDifferentApiLevelTest.java +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineInSameClassDifferentApiLevelTest.java
@@ -45,6 +45,7 @@ .addKeepMainRule(Main.class) .apply(setMockApiLevelForMethod(apiLevel22, AndroidApiLevel.L_MR1)) .apply(ApiModelingTestHelper::enableApiCallerIdentification) + .apply(ApiModelingTestHelper::disableOutliningAndStubbing) .enableInliningAnnotations() .compile() .inspect(inspector -> verifyThat(inspector, parameters, callApi).inlinedInto(callCallApi))
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java index 9d667c1..73132ae 100644 --- a/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java
@@ -48,6 +48,7 @@ .enableNoHorizontalClassMergingAnnotations() .apply(setMockApiLevelForClass(ApiType.class, AndroidApiLevel.L_MR1)) .apply(ApiModelingTestHelper::enableApiCallerIdentification) + .apply(ApiModelingTestHelper::disableOutliningAndStubbing) .compile() .addRunClasspathClasses(ApiType.class) .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMethodTypeReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMethodTypeReferenceTest.java index c4308ed..53b4942 100644 --- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMethodTypeReferenceTest.java +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMethodTypeReferenceTest.java
@@ -47,6 +47,7 @@ .apply(setMockApiLevelForClass(Api.class, AndroidApiLevel.L_MR1)) .apply(setMockApiLevelForDefaultInstanceInitializer(Api.class, AndroidApiLevel.L_MR1)) .apply(ApiModelingTestHelper::enableApiCallerIdentification) + .apply(ApiModelingTestHelper::disableOutliningAndStubbing) .apply( addTracedApiReferenceLevelCallBack( (method, apiLevel) -> {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelSuperTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelSuperTest.java index 4979620..9a239f8 100644 --- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelSuperTest.java +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelSuperTest.java
@@ -55,6 +55,7 @@ .apply(setMockApiLevelForMethod(apiMethod, AndroidApiLevel.L_MR1)) .apply(setMockApiLevelForDefaultInstanceInitializer(Api.class, AndroidApiLevel.L_MR1)) .apply(ApiModelingTestHelper::enableApiCallerIdentification) + .apply(ApiModelingTestHelper::disableOutliningAndStubbing) .apply( addTracedApiReferenceLevelCallBack( (method, apiLevel) -> {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelVerticalMergingOfSuperClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelVerticalMergingOfSuperClassTest.java index db7dc06..71a742e 100644 --- a/src/test/java/com/android/tools/r8/apimodel/ApiModelVerticalMergingOfSuperClassTest.java +++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelVerticalMergingOfSuperClassTest.java
@@ -52,6 +52,7 @@ .apply(setMockApiLevelForClass(Api.class, AndroidApiLevel.L_MR1)) .apply(setMockApiLevelForDefaultInstanceInitializer(Api.class, AndroidApiLevel.L_MR1)) .apply(ApiModelingTestHelper::enableApiCallerIdentification) + .apply(ApiModelingTestHelper::disableOutliningAndStubbing) .apply( addTracedApiReferenceLevelCallBack( (methodReference, apiLevel) -> {
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java index 3ccf46d..605865f 100644 --- a/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java +++ b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
@@ -11,11 +11,10 @@ import com.android.tools.r8.graph.DebugLocalInfo; import com.android.tools.r8.graph.DexDebugEntry; import com.android.tools.r8.graph.DexDebugEntryBuilder; -import com.android.tools.r8.graph.DexDebugEvent; import com.android.tools.r8.graph.DexDebugInfo; +import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexItemFactory; -import com.android.tools.r8.graph.DexString; import com.android.tools.r8.naming.MemberNaming.MethodSignature; import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.DescriptorUtils; @@ -36,15 +35,18 @@ private final DexEncodedMethod method; private final List<DexDebugEntry> entries; - final DexDebugInfo info; + final EventBasedDebugInfo info; public DebugInfoInspector(DexEncodedMethod method, DexItemFactory factory) { this.method = method; - info = method.getCode().asDexCode().getDebugInfo(); - if (info != null) { + EventBasedDebugInfo debugInfo = + DexDebugInfo.convertToEventBased(method.getCode().asDexCode(), factory); + if (debugInfo != null) { + info = debugInfo; entries = new DexDebugEntryBuilder(method, factory).build(); checkConsistentEntries(); } else { + info = null; entries = Collections.emptyList(); } } @@ -58,24 +60,6 @@ this(new CodeInspector(app), clazz, method); } - public boolean hasLocalsInfo() { - DexDebugInfo dexInfo = method.getCode().asDexCode().getDebugInfo(); - if (info == null || dexInfo == null) { - return false; - } - for (DexString parameter : dexInfo.parameters) { - if (parameter != null) { - return true; - } - } - for (DexDebugEvent event : dexInfo.events) { - if (event instanceof DexDebugEvent.StartLocal) { - return true; - } - } - return false; - } - public void checkStartLine(int i) { assertEquals(i, info.startLine); }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DebugSetFileSmaliTest.java b/src/test/java/com/android/tools/r8/debuginfo/DebugSetFileSmaliTest.java index e64b7d7..008d3cf 100644 --- a/src/test/java/com/android/tools/r8/debuginfo/DebugSetFileSmaliTest.java +++ b/src/test/java/com/android/tools/r8/debuginfo/DebugSetFileSmaliTest.java
@@ -71,6 +71,7 @@ .getCode() .asDexCode() .getDebugInfo() + .asEventBasedInfo() .events) .anyMatch(e -> e instanceof SetFile)); })
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java index a2f71b4..c31af39 100644 --- a/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java +++ b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
@@ -23,7 +23,6 @@ import com.android.tools.r8.ToolHelper.DexVm.Version; import com.android.tools.r8.references.Reference; import com.android.tools.r8.retrace.RetraceFrameResult; -import com.android.tools.r8.shaking.ProguardKeepAttributes; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; @@ -68,7 +67,6 @@ .addKeepMainRule(MAIN) .addKeepMethodRules(MAIN, "void overloaded(...)") .addKeepAttributeLineNumberTable() - .addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE) .enableAlwaysInliningAnnotations() .setMinApi(parameters.getApiLevel()) .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicGetDeclaredMethodsTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicGetDeclaredMethodsTest.java index de9685e..4810b1d 100644 --- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicGetDeclaredMethodsTest.java +++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicGetDeclaredMethodsTest.java
@@ -34,7 +34,7 @@ getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build()); } - private static final String EXPECTED_OUTPUT = + private static final String EXPECTED_OUTPUT_WITH_METHOD_HANDLES = StringUtils.lines( "Hello, world!", "myConstant", @@ -42,6 +42,14 @@ "java.lang.invoke.MethodHandles$Lookup", "java.lang.String", "java.lang.Class"); + private static final String EXPECTED_OUTPUT_WITHOUT_METHOD_HANDLES = + StringUtils.lines( + "Hello, world!", + "myConstant", + "3", + "java.lang.Object", + "java.lang.String", + "java.lang.Class"); private static final String EXPECTED_OUTPUT_R8 = StringUtils.lines("Hello, world!", "No myConstant method"); @@ -56,7 +64,7 @@ testForJvm() .addProgramClassFileData(getTransformedClasses()) .run(parameters.getRuntime(), MAIN_CLASS) - .assertSuccessWithOutput(EXPECTED_OUTPUT); + .assertSuccessWithOutput(EXPECTED_OUTPUT_WITH_METHOD_HANDLES); } @Test @@ -64,24 +72,22 @@ testForDesugaring(parameters) .addProgramClassFileData(getTransformedClasses()) .run(parameters.getRuntime(), MAIN_CLASS) - // TODO(b/210485236): This should never fail. .applyIf( // When not desugaring the CF code requires JDK 11. DesugarTestConfiguration::isNotDesugared, r -> { if (parameters.isCfRuntime() && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)) { - r.assertSuccessWithOutput(EXPECTED_OUTPUT); + r.assertSuccessWithOutput(EXPECTED_OUTPUT_WITH_METHOD_HANDLES); } else { r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class); } }, c -> DesugarTestConfiguration.isDesugared(c) - && parameters.isDexRuntime() - && parameters.asDexRuntime().getVersion().isOlderThan(Version.V8_1_0), - r -> r.assertFailureWithErrorThatThrows(ClassNotFoundException.class), - r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT)); + && parameters.getApiLevel().isLessThan(AndroidApiLevel.O), + r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT_WITHOUT_METHOD_HANDLES), + r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT_WITH_METHOD_HANDLES)); } @Test @@ -110,11 +116,10 @@ parameters.getApiLevel().isLessThan(AndroidApiLevel.O), b -> b.addDontWarn(MethodHandles.Lookup.class)) .run(parameters.getRuntime(), MAIN_CLASS) - // TODO(b/210485236): This should never fail. .applyIf( parameters.getDexRuntimeVersion().isOlderThan(Version.V8_1_0), b -> b.assertFailureWithErrorThatThrows(ClassNotFoundException.class), - b -> b.assertSuccessWithOutput(EXPECTED_OUTPUT)); + b -> b.assertSuccessWithOutput(EXPECTED_OUTPUT_WITH_METHOD_HANDLES)); } private byte[] getTransformedClasses() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicInDefaultInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicInDefaultInterfaceMethodTest.java new file mode 100644 index 0000000..a09ae6a --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicInDefaultInterfaceMethodTest.java
@@ -0,0 +1,151 @@ +// Copyright (c) 2022, 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.desugar.constantdynamic; + +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertThrows; +import static org.junit.Assume.assumeTrue; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.DesugarTestConfiguration; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestRuntime.CfVm; +import com.android.tools.r8.cf.CfVersion; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.StringUtils; +import java.lang.invoke.MethodHandles; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class ConstantDynamicInDefaultInterfaceMethodTest extends TestBase { + + @Parameter() public TestParameters parameters; + + @Parameters(name = "{0}") + public static List<Object[]> data() { + return buildParameters( + getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build()); + } + + private static final String EXPECTED_OUTPUT = StringUtils.lines("true", "true"); + private static final Class<?> MAIN_CLASS = A.class; + + @Test + public void testReference() throws Exception { + assumeTrue(parameters.isCfRuntime()); + assumeTrue(parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)); + assumeTrue(parameters.getApiLevel().isEqualTo(AndroidApiLevel.B)); + + testForJvm() + .addProgramClasses(MAIN_CLASS) + .addProgramClassFileData(getTransformedClasses()) + .run(parameters.getRuntime(), MAIN_CLASS) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + + @Test + public void testDesugaring() throws Exception { + testForDesugaring(parameters) + .addProgramClasses(MAIN_CLASS) + .addProgramClassFileData(getTransformedClasses()) + .run(parameters.getRuntime(), MAIN_CLASS) + .applyIf( + // When not desugaring the CF code requires JDK 11. + DesugarTestConfiguration::isNotDesugared, + r -> { + if (parameters.isCfRuntime() + && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)) { + r.assertSuccessWithOutput(EXPECTED_OUTPUT); + } else { + r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class); + } + }) + .applyIf( + DesugarTestConfiguration::isDesugared, r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT)); + } + + @Test + public void testR8() throws Exception { + assumeTrue(parameters.isDexRuntime() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B)); + + testForR8(parameters.getBackend()) + .addProgramClasses(MAIN_CLASS) + .addProgramClassFileData(getTransformedClasses()) + .setMinApi(parameters.getApiLevel()) + .addKeepMainRule(MAIN_CLASS) + // TODO(b/198142613): There should not be a warnings on class references which are + // desugared away. + .applyIf( + parameters.getApiLevel().isLessThan(AndroidApiLevel.O), + b -> b.addDontWarn("java.lang.invoke.MethodHandles$Lookup")) + // TODO(b/198142625): Support CONSTANT_Dynamic output for class files. + .applyIf( + parameters.isCfRuntime(), + b -> { + assertThrows( + CompilationFailedException.class, + () -> + b.compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertOnlyErrors(); + diagnostics.assertErrorsMatch( + diagnosticMessage( + containsString( + "Unsupported dynamic constant (not desugaring)"))); + })); + }, + // TODO(b/210485236): This should not fail for R8. + !parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring(), + b -> + b.run(parameters.getRuntime(), MAIN_CLASS) + .assertFailureWithErrorThatThrows(NoSuchMethodError.class), + b -> + b.run(parameters.getRuntime(), MAIN_CLASS) + .assertSuccessWithOutput(EXPECTED_OUTPUT)); + } + + private byte[] getTransformedClasses() throws Exception { + return transformer(I.class) + .setVersion(CfVersion.V11) + .transformConstStringToConstantDynamic( + "condy1", I.class, "myConstant", "constantName", Object.class) + .transformConstStringToConstantDynamic( + "condy2", I.class, "myConstant", "constantName", Object.class) + .setPrivate( + I.class.getDeclaredMethod( + "myConstant", MethodHandles.Lookup.class, String.class, Class.class)) + .transform(); + } + + public interface I { + + default Object f() { + return "condy1"; // Will be transformed to Constant_DYNAMIC. + } + + default Object g() { + return "condy2"; // Will be transformed to Constant_DYNAMIC. + } + + /* private */ static Object myConstant( + MethodHandles.Lookup lookup, String name, Class<?> type) { + return new Object(); + } + } + + public static class A implements I { + public static void main(String[] args) { + A a = new A(); + System.out.println(a.f() != null); + System.out.println(a.f() == a.g()); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicGetDeclaredMethods.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicGetDeclaredMethods.java index 79fc834..8524f5d 100644 --- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicGetDeclaredMethods.java +++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicGetDeclaredMethods.java
@@ -49,13 +49,20 @@ public static JacocoClasses testClasses; private static final String MAIN_CLASS = TestRunner.class.getTypeName(); - private static final String EXPECTED_OUTPUT = + private static final String EXPECTED_OUTPUT_WITH_METHOD_HANDLES = StringUtils.lines( jacocoBootstrapMethodName, "3", "java.lang.invoke.MethodHandles$Lookup", "java.lang.String", "java.lang.Class"); + private static final String EXPECTED_OUTPUT_WITHOUT_METHOD_HANDLES = + StringUtils.lines( + jacocoBootstrapMethodName, + "3", + "java.lang.Object", + "java.lang.String", + "java.lang.Class"); @BeforeClass public static void setUpInput() throws IOException { @@ -85,7 +92,7 @@ .addProgramFiles(testClasses.getOriginal()) .enableJaCoCoAgent(ToolHelper.JACOCO_AGENT, agentOutputOnTheFly) .run(parameters.getRuntime(), MAIN_CLASS) - .assertSuccessWithOutput(EXPECTED_OUTPUT); + .assertSuccessWithOutput(EXPECTED_OUTPUT_WITH_METHOD_HANDLES); checkJacocoReport(agentOutputOnTheFly); // Run the instrumented code. @@ -94,7 +101,7 @@ .addProgramFiles(testClasses.getInstrumented()) .configureJaCoCoAgentForOfflineInstrumentedCode(ToolHelper.JACOCO_AGENT, agentOutputOffline) .run(parameters.getRuntime(), MAIN_CLASS) - .assertSuccessWithOutput(EXPECTED_OUTPUT); + .assertSuccessWithOutput(EXPECTED_OUTPUT_WITH_METHOD_HANDLES); checkJacocoReport(agentOutputOffline); } @@ -108,11 +115,10 @@ .setMinApi(parameters.getApiLevel()) .compile() .runWithJaCoCo(agentOutput, parameters.getRuntime(), MAIN_CLASS) - // TODO(b/210485236): This should never fail. .applyIf( - parameters.getDexRuntimeVersion().isOlderThan(Version.V8_1_0), - b -> b.assertFailureWithErrorThatThrows(ClassNotFoundException.class), - b -> b.assertSuccessWithOutput(EXPECTED_OUTPUT)); + parameters.getApiLevel().isLessThan(AndroidApiLevel.O), + b -> b.assertSuccessWithOutput(EXPECTED_OUTPUT_WITHOUT_METHOD_HANDLES), + b -> b.assertSuccessWithOutput(EXPECTED_OUTPUT_WITH_METHOD_HANDLES)); checkJacocoReport(agentOutput); } @@ -160,11 +166,10 @@ "javax.management.**") .compile() .runWithJaCoCo(agentOutput, parameters.getRuntime(), MAIN_CLASS) - // TODO(b/210485236): This should never fail. .applyIf( parameters.getDexRuntimeVersion().isOlderThan(Version.V8_1_0), b -> b.assertFailureWithErrorThatThrows(ClassNotFoundException.class), - b -> b.assertSuccessWithOutput(EXPECTED_OUTPUT)); + b -> b.assertSuccessWithOutput(EXPECTED_OUTPUT_WITH_METHOD_HANDLES)); checkJacocoReport(agentOutput); }
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java index 9c33f50..46261e5 100644 --- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java +++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java
@@ -16,6 +16,7 @@ import com.android.tools.r8.jacoco.JacocoClasses; import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.StringUtils; +import com.google.common.collect.ImmutableList; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -50,7 +51,7 @@ public JacocoClasses testClasses; private static final String MAIN_CLASS = TestRunner.class.getTypeName(); - private static final String EXPECTED_OUTPUT = StringUtils.lines("Hello, world!"); + private static final String EXPECTED_OUTPUT = StringUtils.lines("Hello, world!", "Hello from I!"); @BeforeClass public static void setUpInput() throws IOException { @@ -85,7 +86,7 @@ .run(parameters.getRuntime(), MAIN_CLASS) .assertSuccessWithOutput(EXPECTED_OUTPUT); List<String> onTheFlyReport = testClasses.generateReport(agentOutputOnTheFly); - assertEquals(2, onTheFlyReport.size()); + assertEquals(3, onTheFlyReport.size()); // Run the instrumented code. Path agentOutputOffline = output.resolve("offline"); @@ -114,7 +115,7 @@ // TODO(sgjesse): Need to figure out why there is no instrumentation output for newer VMs. if (parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_HOST)) { List<String> report = testClasses.generateReport(agentOutput); - assertEquals(2, report.size()); + assertEquals(3, report.size()); } else { assertFalse(Files.exists(agentOutput)); } @@ -130,16 +131,23 @@ private static JacocoClasses testClasses(TemporaryFolder temp, CfVersion version) throws IOException { return new JacocoClasses( - transformer(TestRunner.class) - .setVersion(version) /*.setClassDescriptor("LTestRunner;")*/ - .transform(), + ImmutableList.of( + transformer(TestRunner.class).setVersion(version).transform(), + transformer(I.class).setVersion(version).transform()), temp); } - static class TestRunner { + interface I { + default void m() { + System.out.println("Hello from I!"); + } + } + + static class TestRunner implements I { public static void main(String[] args) { System.out.println("Hello, world!"); + new TestRunner().m(); } } }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DateTimeFormatterTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DateTimeFormatterTest.java new file mode 100644 index 0000000..2d4640a --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DateTimeFormatterTest.java
@@ -0,0 +1,110 @@ +// Copyright (c) 2022, 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.desugar.desugaredlibrary; + +import com.android.tools.r8.D8TestRunResult; +import com.android.tools.r8.R8TestRunResult; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.utils.BooleanUtils; +import com.android.tools.r8.utils.StringUtils; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.FormatStyle; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class DateTimeFormatterTest extends DesugaredLibraryTestBase { + + private final TestParameters parameters; + private final boolean shrinkDesugaredLibrary; + private static final String expectedOutputDesugaredLib = + StringUtils.lines("2/3/01 4:05 AM - Feb 3, 1 4:05 AM"); + private static final String expectedOutput = + StringUtils.lines("2/3/01, 4:05 AM - Feb 3, 1, 4:05 AM"); + + @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}") + public static List<Object[]> data() { + return buildParameters( + BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build()); + } + + public DateTimeFormatterTest(boolean shrinkDesugaredLibrary, TestParameters parameters) { + this.shrinkDesugaredLibrary = shrinkDesugaredLibrary; + this.parameters = parameters; + } + + @Test + public void testD8() throws Exception { + KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters); + D8TestRunResult run = + testForD8() + .addLibraryFiles(getLibraryFile()) + .addInnerClasses(DateTimeFormatterTest.class) + .setMinApi(parameters.getApiLevel()) + .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer) + .setIncludeClassesChecksum(true) + .compile() + .addDesugaredCoreLibraryRunClassPath( + this::buildDesugaredLibrary, + parameters.getApiLevel(), + keepRuleConsumer.get(), + shrinkDesugaredLibrary) + .run(parameters.getRuntime(), TestClass.class); + if (requiresTimeDesugaring(parameters)) { + run.assertSuccessWithOutput(expectedOutputDesugaredLib); + } else { + run.assertSuccessWithOutput(expectedOutput); + } + } + + @Test + public void testR8() throws Exception { + KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters); + R8TestRunResult run = + testForR8(parameters.getBackend()) + .addLibraryFiles(getLibraryFile()) + .noMinification() + .addKeepMainRule(TestClass.class) + .addInnerClasses(DateTimeFormatterTest.class) + .setMinApi(parameters.getApiLevel()) + .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer) + .compile() + .addDesugaredCoreLibraryRunClassPath( + this::buildDesugaredLibrary, + parameters.getApiLevel(), + keepRuleConsumer.get(), + shrinkDesugaredLibrary) + .run(parameters.getRuntime(), TestClass.class); + if (requiresTimeDesugaring(parameters)) { + run.assertSuccessWithOutput(expectedOutputDesugaredLib); + } else { + run.assertSuccessWithOutput(expectedOutput); + } + } + + static class TestClass { + + public static void main(String[] args) { + // See b/210885987: ANRS reported on Android 12 on the methods: + // - j$.time.format.DateTimeFormatterBuilder.append (DateTimeFormatterBuilder.java) + // - j$.time.format.DateTimeFormatter.<clinit> (DateTimeFormatter.java) + // - j$.time.format.DateTimeFormatter.ofLocalizedDateTime (DateTimeFormatter.java) + DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder(); + DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT); + DateTimeFormatter formatter2 = + DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT); + DateTimeFormatter formatter = + builder.append(formatter1).appendLiteral(" - ").append(formatter2).toFormatter(); + LocalDateTime dateTime = LocalDateTime.of(1, 2, 3, 4, 5); + String str = dateTime.format(formatter); + System.out.println(str); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java b/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java index 5aeaddc..9c4c113 100644 --- a/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java +++ b/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.DexDebugEvent; import com.android.tools.r8.graph.DexDebugInfo; +import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.GraphLens; @@ -68,10 +69,13 @@ @Test public void testEmptyDebugInfo() { - DexDebugInfo debugInfo = new DexDebugInfo(1, DexString.EMPTY_ARRAY, new DexDebugEvent[]{}); + DexDebugInfo debugInfo = + new EventBasedDebugInfo(1, DexString.EMPTY_ARRAY, new DexDebugEvent[] {}); DebugBytecodeWriter writer = new DebugBytecodeWriter( - debugInfo, emptyObjectTObjectMapping(), GraphLens.getIdentityLens()); + DexDebugInfo.convertToWritable(debugInfo), + emptyObjectTObjectMapping(), + GraphLens.getIdentityLens()); Assert.assertEquals(3, writer.generate().length); } }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisSmaliTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisSmaliTest.java index 8552e48..cc582ea 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisSmaliTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisSmaliTest.java
@@ -158,29 +158,33 @@ public void testPhiWithDifferentNewInstance() { buildAndCheckIR( "phiWithDifferentNewInstance", - checkOptimizerStates(appView, optimizer -> { - assertEquals(2, optimizer.analysis.builderStates.size()); - for (Value builder : optimizer.analysis.builderStates.keySet()) { - Map<Instruction, BuilderState> perBuilderState = - optimizer.analysis.builderStates.get(builder); - checkBuilderState(optimizer, perBuilderState, null, false); - } - assertEquals(0, optimizer.analysis.simplifiedBuilders.size()); - })); + checkOptimizerStates( + appView, + optimizer -> { + assertEquals(0, optimizer.analysis.builderStates.size()); + for (Value builder : optimizer.analysis.builderStates.keySet()) { + Map<Instruction, BuilderState> perBuilderState = + optimizer.analysis.builderStates.get(builder); + checkBuilderState(optimizer, perBuilderState, null, false); + } + assertEquals(0, optimizer.analysis.simplifiedBuilders.size()); + })); } @Test public void testPhiAtInit() { buildAndCheckIR( "phiAtInit", - checkOptimizerStates(appView, optimizer -> { - assertEquals(2, optimizer.analysis.builderStates.size()); - for (Value builder : optimizer.analysis.builderStates.keySet()) { - Map<Instruction, BuilderState> perBuilderState = - optimizer.analysis.builderStates.get(builder); - checkBuilderState(optimizer, perBuilderState, null, false); - } - assertEquals(0, optimizer.analysis.simplifiedBuilders.size()); - })); + checkOptimizerStates( + appView, + optimizer -> { + assertEquals(0, optimizer.analysis.builderStates.size()); + for (Value builder : optimizer.analysis.builderStates.keySet()) { + Map<Instruction, BuilderState> perBuilderState = + optimizer.analysis.builderStates.get(builder); + checkBuilderState(optimizer, perBuilderState, null, false); + } + assertEquals(0, optimizer.analysis.simplifiedBuilders.size()); + })); } }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java index e18c8ea..2a81ef0 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java
@@ -265,7 +265,7 @@ @Test public void testPhiAtInit() { - int expectedNumOfNewBuilder = 2; + int expectedNumOfNewBuilder = 0; boolean expectToMeetToString = false; if (parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.M)) { expectedNumOfNewBuilder = 1; @@ -290,15 +290,17 @@ public void testPhiWithDifferentInits() { buildAndCheckIR( "phiWithDifferentInits", - checkOptimizerStates(appView, optimizer -> { - assertEquals(2, optimizer.analysis.builderStates.size()); - for (Value builder : optimizer.analysis.builderStates.keySet()) { - Map<Instruction, BuilderState> perBuilderState = - optimizer.analysis.builderStates.get(builder); - checkBuilderState(optimizer, perBuilderState, null, false); - } - assertEquals(0, optimizer.analysis.simplifiedBuilders.size()); - })); + checkOptimizerStates( + appView, + optimizer -> { + assertEquals(0, optimizer.analysis.builderStates.size()); + for (Value builder : optimizer.analysis.builderStates.keySet()) { + Map<Instruction, BuilderState> perBuilderState = + optimizer.analysis.builderStates.get(builder); + checkBuilderState(optimizer, perBuilderState, null, false); + } + assertEquals(0, optimizer.analysis.simplifiedBuilders.size()); + })); } @Test
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithLoopTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithLoopTest.java new file mode 100644 index 0000000..8cc98c4 --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithLoopTest.java
@@ -0,0 +1,65 @@ +// Copyright (c) 2022, 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.ir.optimize.string; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class StringBuilderWithLoopTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .addKeepClassAndMembersRules(Utils.class) + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("1", "2"); + } + + static class Main { + + public static void main(String[] args) { + StringBuilder sb1 = new StringBuilder(); + StringBuilder sb2 = new StringBuilder(); + int i = 1; + String line; + while ((line = Utils.readLine(i)) != null) { + StringBuilder sb = i == 1 ? sb1 : sb2; + sb.append(line); + i++; + } + System.out.println(sb1.toString()); + System.out.println(sb2.toString()); + } + } + + static class Utils { + + // @Keep + static String readLine(int i) { + if (i <= 2) { + return Integer.toString(i); + } + return null; + } + } +}
diff --git a/src/test/java/com/android/tools/r8/jacoco/JacocoClasses.java b/src/test/java/com/android/tools/r8/jacoco/JacocoClasses.java index b98cc63..dd2700f 100644 --- a/src/test/java/com/android/tools/r8/jacoco/JacocoClasses.java +++ b/src/test/java/com/android/tools/r8/jacoco/JacocoClasses.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.ToolHelper; import com.android.tools.r8.ToolHelper.ProcessResult; import com.android.tools.r8.utils.ZipUtils; +import com.google.common.collect.ImmutableList; import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -28,22 +29,29 @@ private final Path originalJar; private final Path instrumentedJar; - // Create JacocoClasses with just one class provided as bytes. + // Create JacocoClasses with just one class provided as class file bytes. public JacocoClasses(byte[] clazz, TemporaryFolder temp) throws IOException { + this(ImmutableList.of(clazz), temp); + } + + // Create JacocoClasses with multiple classes provided as class file bytes. + public JacocoClasses(List<byte[]> classes, TemporaryFolder temp) throws IOException { this.temp = temp; dir = temp.newFolder().toPath(); // Write the class to a .class file with package sub-directories. - String typeName = TestBase.extractClassName(clazz); - int lastDotIndex = typeName.lastIndexOf('.'); - String pkg = typeName.substring(0, lastDotIndex); - String baseFileName = typeName.substring(lastDotIndex + 1) + CLASS_EXTENSION; Path original = dir.resolve("original"); - Files.createDirectories(original); - Path packageDir = original.resolve(pkg.replace(JAVA_PACKAGE_SEPARATOR, File.separatorChar)); - Files.createDirectories(packageDir); - Path classFile = packageDir.resolve(baseFileName); - Files.write(classFile, clazz); + for (byte[] clazz : classes) { + String typeName = TestBase.extractClassName(clazz); + int lastDotIndex = typeName.lastIndexOf('.'); + String pkg = typeName.substring(0, lastDotIndex); + String baseFileName = typeName.substring(lastDotIndex + 1) + CLASS_EXTENSION; + Files.createDirectories(original); + Path packageDir = original.resolve(pkg.replace(JAVA_PACKAGE_SEPARATOR, File.separatorChar)); + Files.createDirectories(packageDir); + Path classFile = packageDir.resolve(baseFileName); + Files.write(classFile, clazz); + } // Run offline instrumentation. Path instrumented = dir.resolve("instrumented");
diff --git a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java index a6505c8..3531c36 100644 --- a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java +++ b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
@@ -8,7 +8,7 @@ import com.android.tools.r8.ToolHelper; import com.android.tools.r8.debuginfo.DebugInfoInspector; import com.android.tools.r8.graph.DexCode; -import com.android.tools.r8.graph.DexDebugInfo; +import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo; import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion; import com.android.tools.r8.naming.MemberNaming.MethodSignature; import com.android.tools.r8.utils.AndroidApp; @@ -24,65 +24,70 @@ public void testSwap() throws Exception { JasminBuilder builder = new JasminBuilder(); JasminBuilder.ClassBuilder clazz = builder.addClass("Test"); - MethodSignature foo = clazz.addVirtualMethod("foo", ImmutableList.of("Ljava/lang/String;"), "V", - // The first three vars are out-of-order to verify that the order is not relied on. - ".var 5 is t I from L4 to L6", - ".var 1 is bar Ljava/lang/String; from L0 to L9", - ".var 0 is this LTest; from L0 to L9", - ".var 2 is x I from L1 to L9", - ".var 3 is y I from L2 to L9", - ".var 4 is z I from L3 to L9", - ".var 5 is foobar Ljava/lang/String; from L7 to L9", - ".limit locals 6", - ".limit stack 2", - "L0:", - ".line 23", - " iconst_1", - " istore 2", - "L1:", - ".line 24", - " iconst_2", - " istore 3", - "L2:", - ".line 25", - " iconst_3", - " istore 4", - "L3:", - " .line 27", - " iload 3", - " istore 5", - "L4:", - " .line 28", - " iload 2", - " istore 3", - "L5:", - " .line 29", - " iload 5", - " istore 2", - "L6:", - " .line 32", - " new java/lang/StringBuilder", - " dup", - " invokespecial java/lang/StringBuilder/<init>()V", - " ldc \"And the value of y is: \"", - " invokevirtual java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;", - " iload 2", - " invokevirtual java/lang/StringBuilder/append(I)Ljava/lang/StringBuilder;", - " iload 3", - " invokevirtual java/lang/StringBuilder/append(I)Ljava/lang/StringBuilder;", - " iload 4", - " invokevirtual java/lang/StringBuilder/append(I)Ljava/lang/StringBuilder;", - " invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String;", - " astore 5", - "L7:", - " .line 34", - " getstatic java/lang/System/out Ljava/io/PrintStream;", - " aload 5", - " invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V", - "L8:", - " .line 35", - " return", - "L9:"); + MethodSignature foo = + clazz.addVirtualMethod( + "foo", + ImmutableList.of("Ljava/lang/String;"), + "V", + // The first three vars are out-of-order to verify that the order is not relied on. + ".var 5 is t I from L4 to L6", + ".var 1 is bar Ljava/lang/String; from L0 to L9", + ".var 0 is this LTest; from L0 to L9", + ".var 2 is x I from L1 to L9", + ".var 3 is y I from L2 to L9", + ".var 4 is z I from L3 to L9", + ".var 5 is foobar Ljava/lang/String; from L7 to L9", + ".limit locals 6", + ".limit stack 2", + "L0:", + ".line 23", + " iconst_1", + " istore 2", + "L1:", + ".line 24", + " iconst_2", + " istore 3", + "L2:", + ".line 25", + " iconst_3", + " istore 4", + "L3:", + " .line 27", + " iload 3", + " istore 5", + "L4:", + " .line 28", + " iload 2", + " istore 3", + "L5:", + " .line 29", + " iload 5", + " istore 2", + "L6:", + " .line 32", + " new java/lang/StringBuilder", + " dup", + " invokespecial java/lang/StringBuilder/<init>()V", + " ldc \"And the value of y is: \"", + " invokevirtual" + + " java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;", + " iload 2", + " invokevirtual java/lang/StringBuilder/append(I)Ljava/lang/StringBuilder;", + " iload 3", + " invokevirtual java/lang/StringBuilder/append(I)Ljava/lang/StringBuilder;", + " iload 4", + " invokevirtual java/lang/StringBuilder/append(I)Ljava/lang/StringBuilder;", + " invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String;", + " astore 5", + "L7:", + " .line 34", + " getstatic java/lang/System/out Ljava/io/PrintStream;", + " aload 5", + " invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V", + "L8:", + " .line 35", + " return", + "L9:"); clazz.addMainMethod( ".limit stack 3", @@ -105,7 +110,7 @@ ClassSubject classSubject = inspector.clazz("Test"); MethodSubject methodSubject = classSubject.method(foo); DexCode code = methodSubject.getMethod().getCode().asDexCode(); - DexDebugInfo info = code.getDebugInfo(); + EventBasedDebugInfo info = code.getDebugInfo().asEventBasedInfo(); assertEquals(23, info.startLine); assertEquals(1, info.parameters.length); assertEquals("bar", info.parameters[0].toString());
diff --git a/src/test/java/com/android/tools/r8/naming/FieldSharedParentMinificationTest.java b/src/test/java/com/android/tools/r8/naming/FieldSharedParentMinificationTest.java new file mode 100644 index 0000000..e3a8047 --- /dev/null +++ b/src/test/java/com/android/tools/r8/naming/FieldSharedParentMinificationTest.java
@@ -0,0 +1,83 @@ +// Copyright (c) 2022, 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.naming; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.codeinspector.FieldSubject; +import com.google.common.collect.ImmutableSet; +import java.util.Set; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class FieldSharedParentMinificationTest extends TestBase { + + @Parameter() public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .setMinApi(parameters.getApiLevel()) + .addKeepMainRule(Main.class) + .addKeepClassAndMembersRulesWithAllowObfuscation( + I.class, J.class, A.class, B.class, C.class) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("true", "42", "Hello World!") + .inspect( + inspector -> { + FieldSubject foo = inspector.clazz(I.class).uniqueFieldWithName("foo"); + FieldSubject bar = inspector.clazz(J.class).uniqueFieldWithName("bar"); + FieldSubject baz = inspector.clazz(A.class).uniqueFieldWithName("baz"); + assertThat(foo, isPresentAndRenamed()); + assertThat(bar, isPresentAndRenamed()); + assertThat(baz, isPresentAndRenamed()); + Set<String> seenNames = + ImmutableSet.of(foo.getFinalName(), bar.getFinalName(), baz.getFinalName()); + assertEquals(ImmutableSet.of("a", "b", "c"), seenNames); + }); + } + + public interface I { + + int foo = 42; + } + + public interface J { + String bar = "Hello World!"; + } + + public static class A { + + boolean baz = System.currentTimeMillis() > 0; + } + + public static class B extends A implements I {} + + public static class C extends A implements J {} + + public static class Main { + + public static void main(String[] args) { + System.out.println(new A().baz); + System.out.println(new B().foo); + System.out.println(new C().bar); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/naming/InterfaceFieldMinificationTest.java b/src/test/java/com/android/tools/r8/naming/InterfaceFieldMinificationTest.java index ca294fa..f72ffa2 100644 --- a/src/test/java/com/android/tools/r8/naming/InterfaceFieldMinificationTest.java +++ b/src/test/java/com/android/tools/r8/naming/InterfaceFieldMinificationTest.java
@@ -8,13 +8,27 @@ import com.android.tools.r8.NeverInline; import com.android.tools.r8.NoVerticalClassMerging; import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.naming.testclasses.Greeting; import com.android.tools.r8.utils.StringUtils; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; /** Regression test for b/128600647. */ +@RunWith(Parameterized.class) public class InterfaceFieldMinificationTest extends TestBase { + @Parameter() public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withDexRuntimes().withAllApiLevels().build(); + } + @Test public void test() throws Exception { String expectedOutput = StringUtils.lines("Greeter: Hello world!"); @@ -26,7 +40,8 @@ .enableNeverClassInliningAnnotations() .enableInliningAnnotations() .enableNoVerticalClassMergingAnnotations() - .run(TestClass.class) + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), TestClass.class) .assertSuccessWithOutput(expectedOutput); }
diff --git a/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java b/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java index 5ed7de6..c09e451 100644 --- a/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java +++ b/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
@@ -138,7 +138,7 @@ // as this is a release build. assertTrue( (code.getDebugInfo() == null) - || Arrays.stream(code.getDebugInfo().events) + || Arrays.stream(code.getDebugInfo().asEventBasedInfo().events) .allMatch(event -> !(event instanceof StartLocal))); }
diff --git a/src/test/java/com/android/tools/r8/regress/b147865212/Regress147865212.java b/src/test/java/com/android/tools/r8/regress/b147865212/Regress147865212.java index d7133e7..fa3661e 100644 --- a/src/test/java/com/android/tools/r8/regress/b147865212/Regress147865212.java +++ b/src/test/java/com/android/tools/r8/regress/b147865212/Regress147865212.java
@@ -35,7 +35,8 @@ } private boolean hasLocal(MethodSubject method) { - return Arrays.stream(method.getMethod().getCode().asDexCode().getDebugInfo().events) + return Arrays.stream( + method.getMethod().getCode().asDexCode().getDebugInfo().asEventBasedInfo().events) .anyMatch(event -> event instanceof StartLocal); }
diff --git a/src/test/java/com/android/tools/r8/regress/b150400371/DebuginfoForInlineFrameRegressionTest.java b/src/test/java/com/android/tools/r8/regress/b150400371/DebuginfoForInlineFrameRegressionTest.java index cc7900e..5405ecb 100644 --- a/src/test/java/com/android/tools/r8/regress/b150400371/DebuginfoForInlineFrameRegressionTest.java +++ b/src/test/java/com/android/tools/r8/regress/b150400371/DebuginfoForInlineFrameRegressionTest.java
@@ -4,8 +4,8 @@ package com.android.tools.r8.regress.b150400371; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; @@ -45,8 +45,9 @@ MethodSubject main = inspector.method(InlineInto.class.getDeclaredMethod("main", String[].class)); if (parameters.getApiLevel().isLessThan(apiLevelWithPcAsLineNumberSupport())) { + // Method has 14 actual lines, the PC mapping table will have about 50. IntSet lines = new IntArraySet(main.getLineNumberTable().getLines()); - assertEquals(2, lines.size()); + assertTrue(lines.size() > 20); } else { assertNull(main.getLineNumberTable()); }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnTargetedMethodTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnTargetedMethodTest.java index 153bf38..02947f7 100644 --- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnTargetedMethodTest.java +++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnTargetedMethodTest.java
@@ -8,14 +8,29 @@ import static org.hamcrest.MatcherAssert.assertThat; import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; import java.lang.reflect.Proxy; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; +@RunWith(Parameterized.class) public class IfOnTargetedMethodTest extends TestBase { + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimes().withApiLevel(AndroidApiLevel.B).build(); + } + + @Parameter public TestParameters parameters; + @Test public void test() throws Exception { String expectedOutput = StringUtils.lines("Hello world!"); @@ -23,13 +38,14 @@ testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput); CodeInspector inspector = - testForR8(Backend.DEX) + testForR8(parameters.getBackend()) + .setMinApi(parameters.getApiLevel()) .addInnerClasses(IfOnTargetedMethodTest.class) .addKeepMainRule(TestClass.class) .addKeepRules( "-if interface * { @" + MyAnnotation.class.getTypeName() + " <methods>; }", "-keep,allowobfuscation interface <1>") - .run(TestClass.class) + .run(parameters.getRuntime(), TestClass.class) .assertSuccessWithOutput(expectedOutput) .inspector();
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java index 296004d..8644e9a 100644 --- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java +++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -996,7 +996,7 @@ DescriptorUtils.getClassBinaryName(bootstrapMethodHolder), bootstrapMethodName, bootstrapMethodSignature, - false), + bootstrapMethodHolder.isInterface()), new Object[] {})); } else { super.visitLdcInsn(value);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java index 813739b..a41ab17 100644 --- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java +++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -20,6 +20,7 @@ import com.android.tools.r8.graph.DexCode; import com.android.tools.r8.graph.DexDebugEvent; import com.android.tools.r8.graph.DexDebugInfo; +import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo; import com.android.tools.r8.graph.DexDebugPositionState; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexItemFactory; @@ -217,16 +218,21 @@ } if (code.isDexCode()) { DexCode dexCode = code.asDexCode(); - if (dexCode.getDebugInfo() != null) { - for (DexString parameter : dexCode.getDebugInfo().parameters) { - if (parameter != null) { - return true; - } + if (dexCode.getDebugInfo() == null) { + return false; + } + EventBasedDebugInfo eventBasedInfo = dexCode.getDebugInfo().asEventBasedInfo(); + if (eventBasedInfo == null) { + return false; + } + for (DexString parameter : eventBasedInfo.parameters) { + if (parameter != null) { + return true; } - for (DexDebugEvent event : dexCode.getDebugInfo().events) { - if (event instanceof DexDebugEvent.StartLocal) { - return true; - } + } + for (DexDebugEvent event : eventBasedInfo.events) { + if (event instanceof DexDebugEvent.StartLocal) { + return true; } } return false; @@ -268,14 +274,14 @@ } private LineNumberTable getDexLineNumberTable(DexCode code) { - DexDebugInfo debugInfo = code.getDebugInfo(); - if (debugInfo == null) { + EventBasedDebugInfo info = DexDebugInfo.convertToEventBased(code, codeInspector.getFactory()); + if (info == null) { return null; } Object2IntMap<InstructionSubject> lineNumberTable = new Object2IntOpenHashMap<>(); DexDebugPositionState state = - new DexDebugPositionState(debugInfo.startLine, getMethod().getReference()); - Iterator<DexDebugEvent> iterator = Arrays.asList(debugInfo.events).iterator(); + new DexDebugPositionState(info.startLine, getMethod().getReference()); + Iterator<DexDebugEvent> iterator = Arrays.asList(info.events).iterator(); for (Instruction insn : code.instructions) { int offset = insn.getOffset(); while (state.getCurrentPc() < offset && iterator.hasNext()) {