blob: 0aaf7072845fb6926ab602056c05435d83fe5af5 [file] [log] [blame]
/*
* 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<PositionEntry> lines;
private final HashMap<String, LocalVar> locals = new HashMap<String, DebugInfo.LocalVar>();
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<PositionEntry>() {
@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<LocalVar> 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;
}
}