| // 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.utils.positions; |
| |
| import com.android.tools.r8.graph.AppView; |
| import com.android.tools.r8.graph.DexApplication; |
| import com.android.tools.r8.graph.DexCode; |
| import com.android.tools.r8.graph.DexDebugEvent; |
| import com.android.tools.r8.graph.DexDebugEvent.AdvancePC; |
| import com.android.tools.r8.graph.DexDebugEvent.Default; |
| import com.android.tools.r8.graph.DexDebugEvent.EndLocal; |
| import com.android.tools.r8.graph.DexDebugEvent.RestartLocal; |
| import com.android.tools.r8.graph.DexDebugEvent.SetEpilogueBegin; |
| import com.android.tools.r8.graph.DexDebugEvent.SetFile; |
| import com.android.tools.r8.graph.DexDebugEvent.SetPrologueEnd; |
| import com.android.tools.r8.graph.DexDebugEvent.StartLocal; |
| import com.android.tools.r8.graph.DexDebugEventBuilder; |
| 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; |
| import com.android.tools.r8.graph.DexMethod; |
| import com.android.tools.r8.graph.ProgramMethod; |
| import com.android.tools.r8.ir.code.Position; |
| import com.android.tools.r8.ir.code.Position.SourcePosition; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| public class DexPositionToNoPcMappedRangeMapper { |
| |
| // PositionEventEmitter is a stateful function which converts a Position into series of |
| // position-related DexDebugEvents and puts them into a processedEvents list. |
| private static class PositionEventEmitter { |
| private final DexItemFactory dexItemFactory; |
| private int startLine = -1; |
| private final DexMethod method; |
| private int previousPc = 0; |
| private Position previousPosition = null; |
| private final List<DexDebugEvent> processedEvents; |
| |
| private PositionEventEmitter( |
| DexItemFactory dexItemFactory, DexMethod method, List<DexDebugEvent> processedEvents) { |
| this.dexItemFactory = dexItemFactory; |
| this.method = method; |
| this.processedEvents = processedEvents; |
| } |
| |
| public boolean didEmitLineEvents() { |
| return startLine != -1; |
| } |
| |
| private void emitAdvancePc(int pc) { |
| processedEvents.add(new AdvancePC(pc - previousPc)); |
| previousPc = pc; |
| } |
| |
| private void emitPositionEvents(int currentPc, Position currentPosition) { |
| if (previousPosition == null) { |
| startLine = currentPosition.getLine(); |
| previousPosition = SourcePosition.builder().setLine(startLine).setMethod(method).build(); |
| } |
| DexDebugEventBuilder.emitAdvancementEvents( |
| previousPc, |
| previousPosition, |
| currentPc, |
| currentPosition, |
| processedEvents, |
| dexItemFactory, |
| true); |
| previousPc = currentPc; |
| previousPosition = currentPosition; |
| } |
| |
| private int getStartLine() { |
| assert (startLine >= 0); |
| return startLine; |
| } |
| } |
| |
| private static class DexDebugPositionStateVisitor extends DexDebugPositionState { |
| |
| private final PositionEventEmitter positionEventEmitter; |
| private final List<MappedPosition> mappedPositions; |
| private final PositionRemapper positionRemapper; |
| private final List<DexDebugEvent> processedEvents; |
| |
| // Keep track of what PC has been emitted. |
| private int emittedPc = 0; |
| |
| private boolean inlinedOriginalPosition; |
| |
| public DexDebugPositionStateVisitor( |
| PositionEventEmitter positionEventEmitter, |
| List<MappedPosition> mappedPositions, |
| PositionRemapper positionRemapper, |
| List<DexDebugEvent> processedEvents, |
| DexItemFactory factory, |
| int startLine, |
| DexMethod method, |
| boolean isD8R8Synthesized) { |
| super(startLine, method, isD8R8Synthesized); |
| this.positionEventEmitter = positionEventEmitter; |
| this.mappedPositions = mappedPositions; |
| this.positionRemapper = positionRemapper; |
| this.processedEvents = processedEvents; |
| } |
| |
| // Force the current PC to emitted. |
| private void flushPc() { |
| if (emittedPc != getCurrentPc()) { |
| positionEventEmitter.emitAdvancePc(getCurrentPc()); |
| emittedPc = getCurrentPc(); |
| } |
| } |
| |
| // A default event denotes a line table entry and must always be emitted. Remap its line. |
| @Override |
| @SuppressWarnings("ReferenceEquality") |
| public void visit(Default defaultEvent) { |
| super.visit(defaultEvent); |
| assert getCurrentLine() >= 0; |
| Position position = getPosition(); |
| Position mappedPosition = |
| PositionUtils.remapAndAdd(position, positionRemapper, mappedPositions); |
| positionEventEmitter.emitPositionEvents(getCurrentPc(), mappedPosition); |
| if (mappedPosition != position) { |
| inlinedOriginalPosition = true; |
| } |
| emittedPc = getCurrentPc(); |
| } |
| |
| // Non-materializing events use super, ie, AdvancePC, AdvanceLine and SetInlineFrame. |
| |
| // Materializing events are just amended to the stream. |
| |
| @Override |
| public void visit(SetFile setFile) { |
| processedEvents.add(setFile); |
| } |
| |
| @Override |
| public void visit(SetPrologueEnd setPrologueEnd) { |
| processedEvents.add(setPrologueEnd); |
| } |
| |
| @Override |
| public void visit(SetEpilogueBegin setEpilogueBegin) { |
| processedEvents.add(setEpilogueBegin); |
| } |
| |
| // Local changes must force flush the PC ensuing they pertain to the correct point. |
| |
| @Override |
| public void visit(StartLocal startLocal) { |
| flushPc(); |
| processedEvents.add(startLocal); |
| } |
| |
| @Override |
| public void visit(EndLocal endLocal) { |
| flushPc(); |
| processedEvents.add(endLocal); |
| } |
| |
| @Override |
| public void visit(RestartLocal restartLocal) { |
| flushPc(); |
| processedEvents.add(restartLocal); |
| } |
| } |
| |
| private final AppView<?> appView; |
| private final boolean isIdentityMapping; |
| |
| public DexPositionToNoPcMappedRangeMapper(AppView<?> appView) { |
| this.appView = appView; |
| isIdentityMapping = appView.options().lineNumberOptimization.isOff(); |
| } |
| |
| public List<MappedPosition> optimizeDexCodePositions( |
| ProgramMethod method, PositionRemapper positionRemapper) { |
| List<MappedPosition> mappedPositions = new ArrayList<>(); |
| // Do the actual processing for each method. |
| DexApplication application = appView.appInfo().app(); |
| DexCode dexCode = method.getDefinition().getCode().asDexCode(); |
| EventBasedDebugInfo debugInfo = |
| getEventBasedDebugInfo(method.getDefinition(), dexCode, appView); |
| |
| List<DexDebugEvent> processedEvents = new ArrayList<>(); |
| PositionEventEmitter positionEventEmitter = |
| new PositionEventEmitter( |
| application.dexItemFactory, method.getReference(), processedEvents); |
| |
| DexDebugPositionStateVisitor visitor = |
| new DexDebugPositionStateVisitor( |
| positionEventEmitter, |
| mappedPositions, |
| positionRemapper, |
| processedEvents, |
| appView.dexItemFactory(), |
| debugInfo.startLine, |
| method.getReference(), |
| method.getDefinition().isD8R8Synthesized()); |
| |
| DexDebugEvent[] events = debugInfo.events; |
| for (DexDebugEvent event : events) { |
| event.accept(visitor); |
| } |
| |
| EventBasedDebugInfo optimizedDebugInfo = |
| new EventBasedDebugInfo( |
| positionEventEmitter.didEmitLineEvents() ? positionEventEmitter.getStartLine() : 0, |
| debugInfo.parameters, |
| processedEvents.toArray(DexDebugEvent.EMPTY_ARRAY)); |
| |
| assert !isIdentityMapping |
| || visitor.inlinedOriginalPosition |
| || verifyIdentityMapping(method, debugInfo, optimizedDebugInfo); |
| |
| dexCode.setDebugInfo(optimizedDebugInfo); |
| return mappedPositions; |
| } |
| |
| // This conversion *always* creates an event based debug info encoding as any non-info will |
| // be created as an implicit PC encoding. |
| private static EventBasedDebugInfo getEventBasedDebugInfo( |
| DexEncodedMethod method, DexCode dexCode, AppView<?> appView) { |
| // TODO(b/213411850): Do we need to reconsider conversion here to support pc-based D8 merging? |
| if (dexCode.getDebugInfo() == null) { |
| return DexDebugInfo.createEventBasedInfoForMethodWithoutDebugInfo( |
| method, appView.dexItemFactory()); |
| } |
| assert method.getParameters().size() == dexCode.getDebugInfo().getParameterCount(); |
| EventBasedDebugInfo debugInfo = |
| DexDebugInfo.convertToEventBased(dexCode, appView.dexItemFactory()); |
| assert debugInfo != null; |
| return debugInfo; |
| } |
| |
| private static boolean verifyIdentityMapping( |
| ProgramMethod method, |
| EventBasedDebugInfo originalDebugInfo, |
| EventBasedDebugInfo optimizedDebugInfo) { |
| if (isTrivialSyntheticMethod(method, originalDebugInfo)) { |
| return true; |
| } |
| assert optimizedDebugInfo.startLine == originalDebugInfo.startLine; |
| assert optimizedDebugInfo.events.length == originalDebugInfo.events.length; |
| for (int i = 0; i < originalDebugInfo.events.length; ++i) { |
| assert optimizedDebugInfo.events[i].equals(originalDebugInfo.events[i]); |
| } |
| return true; |
| } |
| |
| private static boolean isTrivialSyntheticMethod( |
| ProgramMethod method, EventBasedDebugInfo originalDebugInfo) { |
| if (!method.getDefinition().isD8R8Synthesized()) { |
| return false; |
| } |
| // A synthetic method may have different debug info but still be trivial if it holds that all |
| // the frames are just trivial frames for the synthetic method itself. |
| for (DexDebugEvent event : originalDebugInfo.events) { |
| if (event.isPositionFrame()) { |
| Position position = event.asSetPositionFrame().getPosition(); |
| if (!method.getReference().isIdenticalTo(position.getMethod()) |
| || !position.isD8R8Synthesized() |
| || position.hasCallerPosition() |
| || position.isOutline() |
| || position.isRemoveInnerFramesIfThrowingNpe()) { |
| // If the frame is not trivial then break and assert the usual identity property. |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| } |