/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.jack.comparator; import com.android.jack.dx.dex.file.DebugInfoDecoder; import com.android.jack.dx.dex.file.DebugInfoDecoder.LocalEntry; import com.android.jack.dx.dex.file.DebugInfoDecoder.PositionEntry; import com.android.jack.dx.io.Code; import com.android.jack.dx.io.DexBuffer; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; /** * Debug information for one method. */ public class DebugInfo { /** * The scope of a {@link LocalVar}. */ public static class Interval { private int start; private int end; private int pendingCloseCount; public Interval(int start) { this.start = start; this.end = start; pendingCloseCount = 1; } public Interval(int start, int end) { this.start = start; this.end = end; } /** * @return the end */ public int getEnd() { return end; } /** * @return the start */ public int getStart() { return start; } /** * @param start the start to set */ public void setStart(int start) { augment(start); pendingCloseCount++; } /** * @param end the end to set */ public void setEnd(int end) { augment(end); pendingCloseCount--; assert pendingCloseCount >= 0; } @Override public String toString() { return "[" + start + ", " + end + "]"; } @Override public boolean equals(Object obj) { if (obj instanceof Interval) { Interval other = (Interval) obj; return start == other.start && end == other.end; } return false; } @Override public int hashCode() { return 7529 * start + 11113 * end; } /** * Increase this interval so that it includes the new limit. */ private void augment(int limit) { if (this.end < limit) { this.end = limit; } if (this.start > limit) { this.start = limit; } } private boolean isClosed() { return pendingCloseCount == 0; } } /** * Debugging information for a local variable. */ public static class LocalVar { @Nonnull private final String name; @CheckForNull private final String typeSignature; @Nonnull private final Interval scope; @Nonnull private static final char NON_SOURCE_CONFLICTING_CHAR = '-'; private LocalVar(@Nonnull String name, @CheckForNull String typeSignature, int start) { this.name = name; this.typeSignature = typeSignature; scope = new Interval(start); } @Nonnull public String getName() { return name; } @CheckForNull public String getTypeSignature() { return typeSignature; } @Nonnull public Interval getScope() { return scope; } public boolean isSynthetic() { return name.indexOf(NON_SOURCE_CONFLICTING_CHAR) != -1; } @Nonnull @Override public String toString() { return typeSignature + " " + name + " " + scope; } void addInterval(int start) { scope.setStart(start); } void closeInterval(int end) { scope.setEnd(end); } } private static final int NO_INDEX = -1; private List lines; private final HashMap locals = new HashMap(); private DexBuffer dex; private int sizeInBytes; private int debugInfoOffset; public DebugInfo(DebugInfoDecoder decoder, DexBuffer dex, Code codeItem, int sizeInbytes) { this.dex = dex; this.sizeInBytes = sizeInbytes; debugInfoOffset = codeItem.getDebugInfoOffset(); lines = decoder.getPositionList(); Collections.sort(lines, new Comparator() { @Override public int compare(PositionEntry o1, PositionEntry o2) { return o2.address > o1.address ? -1 : (o2.address == o1.address ? 0 : 1); }}); LocalVar[] regs = new LocalVar[codeItem.getRegistersSize()]; for (LocalEntry localEntry : decoder.getLocals()) { int reg = localEntry.reg; int line = getLine(localEntry.address - 1); if (localEntry.isStart && regs[reg] != null) { regs[reg].closeInterval(line); regs[reg] = null; } if (localEntry.nameIndex != NO_INDEX) { LocalVar localVar = getOrCreate(localEntry); if (localEntry.isStart) { localVar.addInterval(line); regs[reg] = localVar; } else { localVar.closeInterval(line); regs[reg] = null; } } } int endLine = -1; for (PositionEntry line : lines) { endLine = Math.max(line.line, endLine); } for (LocalVar localVar : locals.values()) { if (!localVar.getScope().isClosed()) { localVar.closeInterval(endLine); } } } private LocalVar getOrCreate(LocalEntry localEntry) { String name = dex.strings().get(localEntry.nameIndex); String desc = null; if (localEntry.signatureIndex != NO_INDEX) { desc = dex.strings().get(localEntry.signatureIndex); } else if (localEntry.typeIndex != NO_INDEX) { desc = dex.typeNames().get(localEntry.typeIndex); } String key = name + desc; LocalVar local = locals.get(key); if (local == null) { assert localEntry.isStart; local = new LocalVar(name, desc, getLine(localEntry.address)); locals.put(key, local); } return local; } public int getLine(int address) { int line = -1; for (PositionEntry entry : lines) { if (entry.address > address) { if (line == -1) { line = entry.line; } break; } else { line = entry.line; } } return line; } public Collection getLocals() { return locals.values(); } public LocalVar getLocal(LocalVar other) { return getLocal(other.getName(), other.getTypeSignature()); } public LocalVar getLocal(String name, String desc) { return locals.get(name + desc); } public int getSizeInBytes() { return sizeInBytes; } public int getDebugInfoOffset() { return debugInfoOffset; } }