Refactoring of debug information

This commit is contained in:
konsoletyper 2014-08-28 17:54:02 +04:00
parent 88c47095a6
commit 45c336ebb8
21 changed files with 669 additions and 474 deletions

View File

@ -57,6 +57,18 @@ public class RecordArray {
return arraysPerRecord;
}
public int[] cut(int index) {
if (index < 0 || index >= recordSize) {
throw new IndexOutOfBoundsException("Index " + index + " is outside of [0; " + recordSize + ")");
}
int[] result = new int[size];
for (int i = 0; i < size; ++i) {
result[i] = data[index];
index += recordSize;
}
return result;
}
public class Record {
int offset;
int arrayOffset;

View File

@ -110,6 +110,13 @@ public class RecordArrayBuilder {
return data.get(index + offset);
}
public void set(int index, int value) {
if (index >= recordSize) {
throw new IndexOutOfBoundsException("Index out of bounds: " + index + " of " + recordSize);
}
data.set(index + offset, value);
}
public int size() {
return recordSize;
}

View File

@ -1,3 +1,18 @@
/*
* Copyright 2014 Alexey Andreev.
*
* 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 org.teavm.debugging;
/**
@ -5,5 +20,40 @@ package org.teavm.debugging;
* @author Alexey Andreev <konsoletyper@gmail.com>
*/
public class ClassNameIterator {
private DebugInformation debugInformation;
private int index;
ClassNameIterator(DebugInformation debugInformation) {
this.debugInformation = debugInformation;
}
public boolean isEndReached() {
return index < debugInformation.classMapping.size();
}
public GeneratedLocation getLocation() {
if (isEndReached()) {
throw new IllegalStateException("End already reached");
}
return DebugInformation.key(debugInformation.classMapping.get(index));
}
public int getClassNameId() {
if (isEndReached()) {
throw new IllegalStateException("End already reached");
}
return debugInformation.classMapping.get(index).get(2);
}
public String getClassName() {
int classNameId = getClassNameId();
return classNameId >= 0 ? debugInformation.getClassName(classNameId) : null;
}
public void next() {
if (isEndReached()) {
throw new IllegalStateException("End already reached");
}
++index;
}
}

View File

@ -46,7 +46,7 @@ public class DebugInformation {
RecordArray methodMapping;
RecordArray lineMapping;
RecordArray callSiteMapping;
MultiMapping[] variableMappings;
RecordArray[] variableMappings;
RecordArray[] lineCallSites;
CFG[] controlFlowGraphs;
List<ClassMetadata> classesMetadata;
@ -73,6 +73,71 @@ public class DebugInformation {
return fileNames[fileNameId];
}
public String[] getClassNames() {
return classNames.clone();
}
public String getClassName(int classNameId) {
return classNames[classNameId];
}
public MethodDescriptor[] getMethods() {
MethodDescriptor[] descriptors = new MethodDescriptor[methods.length];
for (int i = 0; i < descriptors.length; ++i) {
descriptors[i] = MethodDescriptor.parse(methods[i]);
}
return descriptors;
}
public MethodDescriptor getMethod(int methodId) {
return MethodDescriptor.parse(methods[methodId]);
}
public MethodReference[] getExactMethods() {
MethodReference[] result = new MethodReference[exactMethods.length];
for (int i = 0; i < result.length; ++i) {
result[i] = getExactMethod(i);
}
return result;
}
private Integer getExactMethodIndex(MethodReference methodRef) {
Integer classIndex = classNameMap.get(methodRef.getClassName());
if (classIndex == null) {
return null;
}
Integer methodIndex = methodMap.get(methodRef.getDescriptor().toString());
if (methodIndex == null) {
return null;
}
return getExactMethodIndex(classIndex, methodIndex);
}
public MethodReference getExactMethod(int index) {
long item = exactMethods[index];
int classIndex = (int)(item >>> 32);
int methodIndex = (int)item;
return new MethodReference(classNames[classIndex], MethodDescriptor.parse(methods[methodIndex]));
}
public int getExactMethodId(int classNameId, int methodId) {
long full = ((long)classNameId << 32) | methodId;
Integer id = exactMethodMap.get(full);
return id != null ? id : -1;
}
public ClassNameIterator iterateOverClassNames() {
return new ClassNameIterator(this);
}
public MethodIterator iterateOverMethods() {
return new MethodIterator(this);
}
public ExactMethodIterator iterateOverExactMethods() {
return new ExactMethodIterator(this);
}
public Collection<GeneratedLocation> getGeneratedLocations(String fileName, int line) {
Integer fileIndex = fileNameMap.get(fileName);
if (fileIndex == null) {
@ -139,11 +204,11 @@ public class DebugInformation {
if (varIndex == null) {
return new String[0];
}
MultiMapping mapping = variableMappings[varIndex];
RecordArray mapping = variableMappings[varIndex];
if (mapping == null) {
return new String[0];
}
return componentByKey(mapping, variableNames, location);
return componentSetByKey(mapping, variableNames, location);
}
public SourceLocation[] getFollowingLines(SourceLocation location) {
@ -212,7 +277,7 @@ public class DebugInformation {
case DebuggerCallSite.STATIC:
return new DebuggerStaticCallSite(getExactMethod(data[0]));
case DebuggerCallSite.VIRTUAL:
return new DebuggerVirtualCallSite(getExactMethod(data[0]), data[1], variableNames[data[1]]);
return new DebuggerVirtualCallSite(getExactMethod(data[0]));
default:
throw new AssertionError("Unrecognized call site type: " + type);
}
@ -227,26 +292,12 @@ public class DebugInformation {
if (index == null) {
return new GeneratedLocation[0];
}
return methodEntrances.getEntrances(index);
}
private Integer getExactMethodIndex(MethodReference methodRef) {
Integer classIndex = classNameMap.get(methodRef.getClassName());
if (classIndex == null) {
return null;
int[] data = methodEntrances.get(0).getArray(0);
GeneratedLocation[] entrances = new GeneratedLocation[data.length / 2];
for (int i = 0; i < entrances.length; ++i) {
entrances[i] = new GeneratedLocation(data[i * 2], data[i * 2 + 1]);
}
Integer methodIndex = methodMap.get(methodRef.getDescriptor().toString());
if (methodIndex == null) {
return null;
}
return getExactMethodIndex(classIndex, methodIndex);
}
public MethodReference getExactMethod(int index) {
long item = exactMethods[index];
int classIndex = (int)(item >>> 32);
int methodIndex = (int)item;
return new MethodReference(classNames[classIndex], MethodDescriptor.parse(methods[methodIndex]));
return entrances;
}
public MethodReference[] getDirectOverridingMethods(MethodReference methodRef) {
@ -300,16 +351,15 @@ public class DebugInformation {
return valueIndex >= 0 ? values[valueIndex] : null;
}
private String[] componentByKey(MultiMapping mapping, String[] values, GeneratedLocation location) {
private String[] componentSetByKey(RecordArray mapping, String[] values, GeneratedLocation location) {
int keyIndex = indexByKey(mapping, location);
if (keyIndex < 0) {
return new String[0];
}
int start = mapping.offsets[keyIndex];
int end = mapping.offsets[keyIndex + 1];
String[] result = new String[end - start];
int[] valueIndexes = mapping.get(keyIndex).getArray(0);
String[] result = new String[valueIndexes.length];
for (int i = 0; i < result.length; ++i) {
result[i] = values[mapping.data[i + start]];
result[i] = values[valueIndexes[i]];
}
return result;
}
@ -324,11 +374,6 @@ public class DebugInformation {
return index >= 0 ? mapping.get(index).get(2) : -1;
}
private int indexByKey(MultiMapping mapping, GeneratedLocation location) {
int index = Collections.binarySearch(mapping.keyList(), location);
return index >= 0 ? index : -index - 2;
}
public void write(OutputStream output) throws IOException {
DebugInformationWriter writer = new DebugInformationWriter(new DataOutputStream(output));
writer.write(this);
@ -372,48 +417,56 @@ public class DebugInformation {
}
void rebuildFileDescriptions() {
FileDescriptionBuilder builder = new FileDescriptionBuilder(fileNames.length);
int fileIndex = 0;
int lineIndex = 0;
int currentFile = -1;
int currentLine = -1;
while (fileIndex < fileMapping.size() && lineIndex < lineMapping.size()) {
RecordArray.Record fileRec = fileMapping.get(fileIndex);
RecordArray.Record lineRec = lineMapping.get(lineIndex);
GeneratedLocation fileLoc = key(fileRec);
GeneratedLocation lineLoc = key(lineRec);
int cmp = fileLoc.compareTo(lineLoc);
if (cmp < 0) {
currentFile = fileRec.get(2);
fileIndex++;
} else if (cmp > 0){
currentLine = lineRec.get(2);
lineIndex++;
} else {
currentFile = fileRec.get(2);
currentLine = lineRec.get(2);
fileIndex++;
lineIndex++;
RecordArrayBuilder[] builders = new RecordArrayBuilder[fileNames.length];
for (int i = 0; i < builders.length; ++i) {
builders[i] = new RecordArrayBuilder(0, 1);
}
for (SourceLocationIterator iter = iterateOverSourceLocations(); !iter.isEndReached(); iter.next()) {
if (iter.getFileNameId() >= 0 && iter.getLine() >= 0) {
RecordArrayBuilder builder = builders[iter.getFileNameId()];
while (builder.size() <= iter.getLine()) {
builder.add();
}
GeneratedLocation loc = iter.getLocation();
RecordArrayBuilder.RecordSubArray array = builder.get(iter.getLine()).getArray(0);
array.add(loc.getLine());
array.add(loc.getColumn());
}
builder.emit(fileLoc.getLine(), fileLoc.getColumn(), currentFile, currentLine);
}
while (fileIndex < fileMapping.size()) {
RecordArray.Record fileRec = fileMapping.get(fileIndex++);
builder.emit(fileRec.get(0), fileRec.get(1), fileRec.get(2), currentLine);
fileDescriptions = new RecordArray[builders.length];
for (int i = 0; i < fileDescriptions.length; ++i) {
fileDescriptions[i] = builders[i].build();
}
while (lineIndex < lineMapping.size()) {
RecordArray.Record lineRec = lineMapping.get(lineIndex++);
builder.emit(lineRec.get(0), lineRec.get(1), currentFile, lineRec.get(2));
}
fileDescriptions = builder.build();
}
void rebuildEntrances() {
RecordArrayBuilder builder = new RecordArrayBuilder(0, 1);
for (SourceLocationIterator iter = iterateOverSourceLocations(); !iter.isEndReached(); iter.next()) {
iter.getLocation();
for (int i = 0; i < exactMethods.length; ++i) {
builder.add();
}
GeneratedLocation prevLocation = new GeneratedLocation(0, 0);
MethodReference prevMethod = null;
for (ExactMethodIterator iter = iterateOverExactMethods(); !iter.isEndReached(); iter.next()) {
int id = iter.getExactMethodId();
if (prevMethod != null) {
int lineIndex = Math.max(0, indexByKey(lineMapping, prevLocation));
while (lineIndex < 0) {
if (key(lineMapping.get(lineIndex)).compareTo(iter.getLocation()) >= 0) {
break;
}
int line = lineMapping.get(0).get(2);
if (line >= 0) {
GeneratedLocation firstLineLoc = key(lineMapping.get(lineIndex));
RecordArrayBuilder.RecordSubArray array = builder.get(id).getArray(0);
array.add(firstLineLoc.getLine());
array.add(firstLineLoc.getColumn());
break;
}
}
}
prevMethod = iter.getExactMethod();
prevLocation = iter.getLocation();
}
methodEntrances = new MethodEntrancesBuilder().build();
}
void rebuildMethodTree() {
@ -516,188 +569,10 @@ public class DebugInformation {
}
}
class MethodEntrancesBuilder {
int[] start;
IntegerArray data;
IntegerArray next;
int methodIndex;
int classIndex;
public MethodEntrances build() {
methodIndex = -1;
classIndex = -1;
start = new int[exactMethods.length];
Arrays.fill(start, -1);
data = new IntegerArray(0);
next = new IntegerArray(0);
int methodMappingIndex = 0;
int classMappingIndex = 0;
GeneratedLocation previousLocation = new GeneratedLocation(0, 0);
while (methodMappingIndex < methodMapping.lines.length &&
classMappingIndex < classMapping.lines.length) {
GeneratedLocation methodLoc = new GeneratedLocation(methodMapping.lines[methodMappingIndex],
methodMapping.columns[methodMappingIndex]);
GeneratedLocation classLoc = new GeneratedLocation(classMapping.lines[classMappingIndex],
classMapping.columns[classMappingIndex]);
int cmp = methodLoc.compareTo(classLoc);
if (cmp < 0) {
addMethodEntrance(previousLocation, methodLoc);
previousLocation = methodLoc;
methodIndex = methodMapping.values[methodMappingIndex++];
} else if (cmp > 0) {
addMethodEntrance(previousLocation, classLoc);
previousLocation = classLoc;
classIndex = classMapping.values[classMappingIndex++];
} else {
addMethodEntrance(previousLocation, classLoc);
previousLocation = classLoc;
methodIndex = methodMapping.values[methodMappingIndex++];
classIndex = classMapping.values[classMappingIndex++];
}
}
while (methodMappingIndex < methodMapping.lines.length) {
GeneratedLocation methodLoc = new GeneratedLocation(methodMapping.lines[methodMappingIndex],
methodMapping.columns[methodMappingIndex]);
addMethodEntrance(previousLocation, methodLoc);
previousLocation = methodLoc;
methodIndex = methodMapping.values[methodMappingIndex++];
}
while (classMappingIndex < classMapping.lines.length) {
GeneratedLocation classLoc = new GeneratedLocation(classMapping.lines[classMappingIndex],
classMapping.columns[classMappingIndex]);
addMethodEntrance(previousLocation, classLoc);
previousLocation = classLoc;
classIndex = classMapping.values[classMappingIndex++];
}
return assemble();
}
private void addMethodEntrance(GeneratedLocation location, GeneratedLocation limit) {
if (classIndex < 0 || methodIndex < 0) {
return;
}
long exactMethod = ((long)classIndex << 32) | methodIndex;
Integer exactMethodIndex = exactMethodMap.get(exactMethod);
if (exactMethodIndex == null) {
return;
}
int lineIndex = indexByKey(lineMapping, location);
if (lineIndex < 0) {
lineIndex = 0;
}
int line = -1;
while (lineIndex < lineMapping.values.length) {
location = new GeneratedLocation(lineMapping.lines[lineIndex], lineMapping.columns[lineIndex]);
if (location.compareTo(limit) >= 0) {
break;
}
if (lineMapping.values[lineIndex] >= 0) {
line = lineMapping.values[lineIndex];
break;
}
++lineIndex;
}
if (line == -1) {
return;
}
int ptr = start[exactMethodIndex];
start[exactMethodIndex] = data.size();
next.add(ptr);
ptr = data.size();
data.add(location.getColumn());
next.add(ptr);
start[exactMethodIndex] = data.size();
data.add(location.getLine());
}
private MethodEntrances assemble() {
MethodEntrances entrances = new MethodEntrances();
entrances.offsets = new int[start.length + 1];
entrances.data = new int[data.size()];
int index = 0;
for (int i = 0; i < start.length; ++i) {
int ptr = start[i];
while (ptr != -1) {
entrances.data[index++] = data.get(ptr);
ptr = next.get(ptr);
}
entrances.offsets[i + 1] = index;
}
return entrances;
}
}
static class FileDescriptionBuilder {
RecordArrayBuilder[] files;
int lastFileIndex = -1;
int lastSourceLine = -1;
public FileDescriptionBuilder(int size) {
files = new RecordArrayBuilder[size];
for (int i = 0; i < size; ++i) {
files[i] = new RecordArrayBuilder(0, 1);
}
}
void emit(int line, int column, int fileIndex, int sourceLine) {
if (sourceLine == -1 || fileIndex == -1) {
return;
}
if (lastFileIndex == fileIndex && lastSourceLine == sourceLine) {
return;
}
lastFileIndex = fileIndex;
lastSourceLine = sourceLine;
RecordArrayBuilder proto = files[fileIndex];
while (proto.size() <= sourceLine) {
proto.add();
}
RecordArrayBuilder.RecordSubArray array = proto.get(sourceLine).getArray(0);
array.add(line);
array.add(column);
}
public RecordArray[] build() {
RecordArray[] descriptions = new RecordArray[files.length];
for (int i = 0; i < files.length; ++i) {
descriptions[i] = files[i].build();
}
return descriptions;
}
}
static GeneratedLocation key(RecordArray.Record record) {
return new GeneratedLocation(record.get(0), record.get(1));
}
static class MultiMapping {
int[] lines;
int[] columns;
int[] offsets;
int[] data;
public MultiMapping(int[] lines, int[] columns, int[] offsets, int[] data) {
this.lines = lines;
this.columns = columns;
this.offsets = offsets;
this.data = data;
}
public LocationList keyList() {
return new LocationList(lines, columns);
}
public int size() {
return lines.length;
}
public GeneratedLocation key(int index) {
return new GeneratedLocation(lines[index], columns[index]);
}
}
static class LocationList extends AbstractList<GeneratedLocation> {
private RecordArray recordArray;

View File

@ -18,6 +18,8 @@ package org.teavm.debugging;
import java.util.*;
import org.teavm.codegen.LocationProvider;
import org.teavm.common.IntegerArray;
import org.teavm.common.RecordArray;
import org.teavm.common.RecordArrayBuilder;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReference;
@ -35,12 +37,12 @@ public class DebugInformationBuilder implements DebugInformationEmitter {
private MappedList variableNames = new MappedList();
private List<Long> exactMethods = new ArrayList<>();
private Map<Long, Integer> exactMethodMap = new HashMap<>();
private Mapping fileMapping = new Mapping();
private Mapping lineMapping = new Mapping();
private Mapping classMapping = new Mapping();
private Mapping methodMapping = new Mapping();
private Mapping callSiteMapping = new Mapping();
private Map<Integer, MultiMapping> variableMappings = new HashMap<>();
private RecordArrayBuilder fileMapping = new RecordArrayBuilder(3, 0);
private RecordArrayBuilder lineMapping = new RecordArrayBuilder(3, 0);
private RecordArrayBuilder classMapping = new RecordArrayBuilder(3, 0);
private RecordArrayBuilder methodMapping = new RecordArrayBuilder(3, 0);
private RecordArrayBuilder callSiteMapping = new RecordArrayBuilder(3, 1);
private Map<Integer, RecordArrayBuilder> variableMappings = new HashMap<>();
private MethodDescriptor currentMethod;
private String currentClass;
private String currentFileName;
@ -63,21 +65,40 @@ public class DebugInformationBuilder implements DebugInformationEmitter {
debugInformation = null;
int fileIndex = files.index(fileName);
if (!Objects.equals(currentFileName, fileName)) {
fileMapping.add(locationProvider, fileIndex, true);
add(fileMapping, fileIndex);
currentFileName = fileName;
}
if (currentLine != line) {
lineMapping.add(locationProvider, line, true);
add(lineMapping, line);
currentLine = line;
}
}
private RecordArrayBuilder.Record add(RecordArrayBuilder builder) {
if (builder.size() > 1) {
RecordArrayBuilder.Record lastRecord = builder.get(builder.size() - 1);
if (lastRecord.get(0) == locationProvider.getLine() && lastRecord.get(1) == locationProvider.getColumn()) {
return lastRecord;
}
}
RecordArrayBuilder.Record record = builder.add();
record.set(0, locationProvider.getLine());
record.set(1, locationProvider.getColumn());
return record;
}
private RecordArrayBuilder.Record add(RecordArrayBuilder builder, int value) {
RecordArrayBuilder.Record record = add(builder);
record.set(2, value);
return record;
}
@Override
public void emitClass(String className) {
debugInformation = null;
int classIndex = classes.index(className);
if (!Objects.equals(className, currentClass)) {
classMapping.add(locationProvider, classIndex, true);
add(classMapping, classIndex);
currentClass = className;
}
}
@ -87,7 +108,7 @@ public class DebugInformationBuilder implements DebugInformationEmitter {
debugInformation = null;
int methodIndex = methods.index(method != null ? method.toString() : null);
if (!Objects.equals(method, currentMethod)) {
methodMapping.add(locationProvider, methodIndex, true);
add(methodMapping, methodIndex);
currentMethod = method;
}
if (currentClass != null) {
@ -108,19 +129,42 @@ public class DebugInformationBuilder implements DebugInformationEmitter {
}
Arrays.sort(sourceIndexes);
int generatedIndex = variableNames.index(generatedName);
MultiMapping mapping = variableMappings.get(generatedIndex);
RecordArrayBuilder mapping = variableMappings.get(generatedIndex);
if (mapping == null) {
mapping = new MultiMapping();
mapping = new RecordArrayBuilder(2, 1);
variableMappings.put(generatedIndex, mapping);
}
mapping.add(locationProvider, sourceIndexes);
RecordArrayBuilder.Record record = add(mapping);
RecordArrayBuilder.RecordSubArray array = record.getArray(0);
for (int sourceIndex : sourceIndexes) {
array.add(sourceIndex);
}
}
@Override
public DeferredCallSite emitCallSite() {
final RecordArrayBuilder.Record record = add(callSiteMapping, DebuggerCallSite.NONE);
DeferredCallSite callSite = new DeferredCallSite() {
int index = callSiteMapping.values.size();
@Override public void setMethod(MethodReference method) {
@Override
public void setVirtualMethod(MethodReference method) {
record.set(2, DebuggerCallSite.VIRTUAL);
RecordArrayBuilder.RecordSubArray array = record.getArray(0);
array.clear();
array.add(getExactMethodIndex(method));
}
@Override
public void setStaticMethod(MethodReference method) {
record.set(2, DebuggerCallSite.STATIC);
RecordArrayBuilder.RecordSubArray array = record.getArray(0);
array.clear();
array.add(getExactMethodIndex(method));
}
@Override
public void clean() {
record.set(2, DebuggerCallSite.NONE);
}
private int getExactMethodIndex(MethodReference method) {
int methodIndex = methods.index(method.getDescriptor().toString());
int classIndex = classes.index(method.getClassName());
long fullIndex = ((long)classIndex << 32) | methodIndex;
@ -130,18 +174,12 @@ public class DebugInformationBuilder implements DebugInformationEmitter {
exactMethodMap.put(fullIndex, exactMethodIndex);
exactMethods.add(fullIndex);
}
callSiteMapping.values.set(index, exactMethodIndex);
return exactMethodIndex;
}
};
callSiteMapping.add(locationProvider, -1, false);
return callSite;
}
@Override
public void emitEmptyCallSite() {
callSiteMapping.add(locationProvider, -1, false);
}
@Override
public void addClass(String className, String parentName) {
int classIndex = classes.index(className);
@ -181,6 +219,43 @@ public class DebugInformationBuilder implements DebugInformationEmitter {
}
}
private RecordArrayBuilder compress(RecordArrayBuilder builder) {
int lastValue = 0;
RecordArrayBuilder compressed = new RecordArrayBuilder(builder.getRecordSize(), builder.getArraysPerRecord());
for (int i = 0; i < builder.size(); ++i) {
RecordArrayBuilder.Record record = builder.get(i);
if (i == 0 || lastValue != record.get(2)) {
RecordArrayBuilder.Record compressedRecord = compressed.add();
for (int j = 0; j < builder.getRecordSize(); ++j) {
compressedRecord.set(j, record.get(j));
}
}
}
return compressed;
}
private void compressAndSortArrays(RecordArrayBuilder builder) {
for (int i = 0; i < builder.size(); ++i) {
RecordArrayBuilder.Record record = builder.get(i);
for (int j = 0; j < builder.getArraysPerRecord(); ++j) {
RecordArrayBuilder.RecordSubArray array = record.getArray(j);
int[] data = array.getData();
Arrays.sort(data);
array.clear();
if (data.length > 0) {
int last = data[0];
array.add(last);
for (int k = 1; k < data.length; ++k) {
if (data[k] != last) {
last = data[k];
array.add(last);
}
}
}
}
}
}
public DebugInformation getDebugInformation() {
if (debugInformation == null) {
debugInformation = new DebugInformation();
@ -196,14 +271,15 @@ public class DebugInformationBuilder implements DebugInformationEmitter {
}
debugInformation.exactMethodMap = new HashMap<>(exactMethodMap);
debugInformation.fileMapping = fileMapping.build();
debugInformation.lineMapping = lineMapping.build();
debugInformation.classMapping = classMapping.build();
debugInformation.methodMapping = methodMapping.build();
debugInformation.fileMapping = compress(fileMapping).build();
debugInformation.lineMapping = compress(lineMapping).build();
debugInformation.classMapping = compress(classMapping).build();
debugInformation.methodMapping = compress(methodMapping).build();
debugInformation.callSiteMapping = callSiteMapping.build();
debugInformation.variableMappings = new DebugInformation.MultiMapping[variableNames.list.size()];
debugInformation.variableMappings = new RecordArray[variableNames.list.size()];
for (int var : variableMappings.keySet()) {
MultiMapping mapping = variableMappings.get(var);
RecordArrayBuilder mapping = variableMappings.get(var);
compressAndSortArrays(mapping);
debugInformation.variableMappings[var] = mapping.build();
}
@ -233,107 +309,6 @@ public class DebugInformationBuilder implements DebugInformationEmitter {
return debugInformation;
}
static class Mapping {
IntegerArray lines = new IntegerArray(1);
IntegerArray columns = new IntegerArray(1);
IntegerArray values = new IntegerArray(1);
public void add(LocationProvider location, int value, boolean merge) {
if (merge && lines.size() > 1) {
int last = lines.size() - 1;
if (lines.get(last) == location.getLine() && columns.get(last) == location.getColumn()) {
values.set(last, value);
// TODO: check why this gives an invalid result
/*if (values.get(last) == values.get(last - 1)) {
values.remove(last);
lines.remove(last);
columns.remove(last);
}*/
return;
}
}
lines.add(location.getLine());
columns.add(location.getColumn());
values.add(value);
}
DebugInformation.Mapping build() {
return new DebugInformation.Mapping(lines.getAll(), columns.getAll(), values.getAll());
}
}
static class MultiMapping {
IntegerArray lines = new IntegerArray(1);
IntegerArray columns = new IntegerArray(1);
IntegerArray offsets = new IntegerArray(1);
IntegerArray data = new IntegerArray(1);
public MultiMapping() {
offsets.add(0);
}
public void add(LocationProvider location, int[] values) {
if (lines.size() > 1) {
int last = lines.size() - 1;
if (lines.get(last) == location.getLine() && columns.get(last) == location.getColumn()) {
addToLast(values);
return;
}
}
lines.add(location.getLine());
columns.add(location.getColumn());
data.addAll(values);
offsets.add(data.size());
}
private void addToLast(int[] values) {
int start = offsets.get(offsets.size() - 2);
int end = offsets.get(offsets.size() - 1);
int[] existing = data.getRange(start, end);
values = merge(existing, values);
if (values.length == existing.length) {
return;
}
data.remove(start, end - start);
data.addAll(values);
offsets.set(offsets.size() - 1, data.size());
}
private int[] merge(int[] a, int[] b) {
int[] result = new int[a.length + b.length];
int i = 0;
int j = 0;
int k = 0;
while (i < a.length && j < b.length) {
int p = a[i];
int q = b[j];
if (p == q) {
result[k++] = p;
++i;
++j;
} else if (p < q) {
result[k++] = p;
++i;
} else {
result[k++] = q;
++j;
}
}
while (i < a.length) {
result[k++] = a[i++];
}
while (j < b.length) {
result[k++] = b[j++];
}
return k < result.length ? Arrays.copyOf(result, k) : result;
}
public DebugInformation.MultiMapping build() {
return new DebugInformation.MultiMapping(lines.getAll(), columns.getAll(), offsets.getAll(),
data.getAll());
}
}
static class MappedList {
private List<String> list = new ArrayList<>();
private Map<String, Integer> map = new HashMap<>();

View File

@ -35,8 +35,6 @@ public interface DebugInformationEmitter {
DeferredCallSite emitCallSite();
void emitEmptyCallSite();
void addClass(String className, String parentName);
void addField(String fieldName, String jsName);

View File

@ -21,6 +21,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.teavm.common.RecordArray;
import org.teavm.debugging.DebugInformation.ClassMetadata;
/**
@ -57,13 +58,13 @@ class DebugInformationWriter {
int lastVar = 0;
writeUnsignedNumber(nonNullVariableMappings(debugInfo));
for (int i = 0; i < debugInfo.variableMappings.length; ++i) {
DebugInformation.MultiMapping mapping = debugInfo.variableMappings[i];
RecordArray mapping = debugInfo.variableMappings[i];
if (mapping == null) {
continue;
}
writeUnsignedNumber(i - lastVar);
lastVar = i;
writeMapping(mapping);
writeMultiMapping(mapping);
}
}
@ -114,8 +115,8 @@ class DebugInformationWriter {
}
}
private void writeMapping(DebugInformation.MultiMapping mapping) throws IOException {
int[] lines = mapping.lines.clone();
private void writeLinesAndColumns(RecordArray mapping) throws IOException {
int[] lines = mapping.cut(0);
int last = 0;
for (int i = 0; i < lines.length; ++i) {
int next = lines[i];
@ -124,36 +125,36 @@ class DebugInformationWriter {
}
writeRle(lines);
resetRelativeNumber();
for (int i = 0; i < mapping.columns.length; ++i) {
writeRelativeNumber(mapping.columns[i]);
}
int lastOffset = 0;
for (int i = 1; i < mapping.offsets.length; ++i) {
writeUnsignedNumber(mapping.offsets[i] - lastOffset);
lastOffset = mapping.offsets[i];
}
resetRelativeNumber();
for (int i = 0; i < mapping.data.length; ++i) {
writeRelativeNumber(mapping.data[i]);
int[] columns = mapping.cut(1);
int lastLine = -1;
for (int i = 0; i < columns.length; ++i) {
if (lastLine != mapping.get(i).get(0)) {
resetRelativeNumber();
lastLine = mapping.get(i).get(0);
}
writeRelativeNumber(columns[i]);
}
}
private void writeMapping(DebugInformation.Mapping mapping) throws IOException {
int[] lines = mapping.lines.clone();
int last = 0;
for (int i = 0; i < lines.length; ++i) {
int next = lines[i];
lines[i] -= last;
last = next;
private void writeMultiMapping(RecordArray mapping) throws IOException {
writeLinesAndColumns(mapping);
for (int i = 0; i < mapping.size(); ++i) {
int[] array = mapping.get(0).getArray(0);
writeUnsignedNumber(array.length);
int lastNumber = 0;
for (int elem : array) {
writeUnsignedNumber(elem - lastNumber);
lastNumber = elem;
}
}
writeRle(lines);
}
private void writeMapping(RecordArray mapping) throws IOException {
writeLinesAndColumns(mapping);
resetRelativeNumber();
for (int i = 0; i < mapping.columns.length; ++i) {
writeRelativeNumber(mapping.columns[i]);
}
resetRelativeNumber();
for (int i = 0; i < mapping.values.length; ++i) {
writeRelativeNumber(mapping.values[i]);
int[] values = mapping.cut(2);
for (int i = 0; i < values.length; ++i) {
writeRelativeNumber(values[i]);
}
}

View File

@ -101,16 +101,14 @@ public class Debugger {
DebugInformation debugInfo = debugInformationMap.get(script);
if (frame.getLocation() != null && frame.getLocation().getFileName() != null &&
frame.getLocation().getLine() >= 0 && debugInfo != null) {
MethodReference[] callMethods = debugInfo.getCallSites(frame.getLocation());
exits = addFollowing(debugInfo, frame.getLocation(), script, new HashSet<SourceLocation>(),
successors);
if (enterMethod) {
for (MethodReference callMethod : callMethods) {
for (MethodReference potentialMethod : debugInfo.getOverridingMethods(callMethod)) {
for (GeneratedLocation loc : debugInfo.getMethodEntrances(potentialMethod)) {
successors.add(new JavaScriptLocation(script, loc.getLine(), loc.getColumn()));
}
}
CallSiteSuccessorFinder successorFinder = new CallSiteSuccessorFinder(debugInfo, script,
successors);
DebuggerCallSite[] callSites = debugInfo.getCallSites(frame.getLocation());
for (DebuggerCallSite callSite : callSites) {
callSite.acceptVisitor(successorFinder);
}
}
} else {
@ -126,6 +124,34 @@ public class Debugger {
javaScriptDebugger.resume();
}
private static class CallSiteSuccessorFinder implements DebuggerCallSiteVisitor {
private DebugInformation debugInfo;
private String script;
Set<JavaScriptLocation> locations;
public CallSiteSuccessorFinder(DebugInformation debugInfo, String script, Set<JavaScriptLocation> locations) {
this.debugInfo = debugInfo;
this.script = script;
this.locations = locations;
}
@Override
public void visit(DebuggerVirtualCallSite callSite) {
for (MethodReference potentialMethod : debugInfo.getOverridingMethods(callSite.getMethod())) {
for (GeneratedLocation loc : debugInfo.getMethodEntrances(potentialMethod)) {
locations.add(new JavaScriptLocation(script, loc.getLine(), loc.getColumn()));
}
}
}
@Override
public void visit(DebuggerStaticCallSite callSite) {
for (GeneratedLocation loc : debugInfo.getMethodEntrances(callSite.getMethod())) {
locations.add(new JavaScriptLocation(script, loc.getLine(), loc.getColumn()));
}
}
}
private boolean addFollowing(DebugInformation debugInfo, SourceLocation location, String script,
Set<SourceLocation> visited, Set<JavaScriptLocation> successors) {
if (!visited.add(location)) {

View File

@ -1,3 +1,18 @@
/*
* Copyright 2014 Alexey Andreev.
*
* 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 org.teavm.debugging;
/**

View File

@ -1,3 +1,18 @@
/*
* Copyright 2014 Alexey Andreev.
*
* 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 org.teavm.debugging;
/**

View File

@ -1,3 +1,18 @@
/*
* Copyright 2014 Alexey Andreev.
*
* 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 org.teavm.debugging;
import org.teavm.model.MethodReference;

View File

@ -1,3 +1,18 @@
/*
* Copyright 2014 Alexey Andreev.
*
* 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 org.teavm.debugging;
import org.teavm.model.MethodReference;
@ -8,13 +23,9 @@ import org.teavm.model.MethodReference;
*/
public class DebuggerVirtualCallSite extends DebuggerCallSite {
private MethodReference method;
private int variableId;
private String variableName;
DebuggerVirtualCallSite(MethodReference method, int variableId, String variableName) {
DebuggerVirtualCallSite(MethodReference method) {
this.method = method;
this.variableId = variableId;
this.variableName = variableName;
}
public MethodReference getMethod() {
@ -25,22 +36,6 @@ public class DebuggerVirtualCallSite extends DebuggerCallSite {
this.method = method;
}
public int getVariableId() {
return variableId;
}
public void setVariableId(int variableId) {
this.variableId = variableId;
}
public String getVariableName() {
return variableName;
}
public void setVariableName(String variableName) {
this.variableName = variableName;
}
@Override
public void acceptVisitor(DebuggerCallSiteVisitor visitor) {
visitor.visit(this);

View File

@ -22,5 +22,9 @@ import org.teavm.model.MethodReference;
* @author Alexey Andreev
*/
public interface DeferredCallSite {
void setMethod(MethodReference method);
void setVirtualMethod(MethodReference method);
void setStaticMethod(MethodReference method);
void clean();
}

View File

@ -44,15 +44,12 @@ public class DummyDebugInformationEmitter implements DebugInformationEmitter {
@Override
public DeferredCallSite emitCallSite() {
return new DeferredCallSite() {
@Override public void setMethod(MethodReference method) {
}
@Override public void setVirtualMethod(MethodReference method) { }
@Override public void setStaticMethod(MethodReference method) { }
@Override public void clean() { }
};
}
@Override
public void emitEmptyCallSite() {
}
@Override
public void setLocationProvider(LocationProvider locationProvider) {
}

View File

@ -0,0 +1,123 @@
/*
* Copyright 2014 Alexey Andreev.
*
* 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 org.teavm.debugging;
import org.teavm.common.RecordArray;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReference;
/**
*
* @author Alexey Andreev
*/
public class ExactMethodIterator {
private DebugInformation debugInformation;
private GeneratedLocation location;
private int classIndex;
private int methodIndex;
private int classId = -1;
private int methodId = -1;
private boolean endReached;
ExactMethodIterator(DebugInformation debugInformation) {
this.debugInformation = debugInformation;
read();
}
public boolean isEndReached() {
return endReached;
}
private void read() {
if (classIndex >= debugInformation.classMapping.size()) {
nextClassRecord();
} else if (methodIndex >= debugInformation.methodMapping.size()) {
nextMethodRecord();
} else if (classIndex < debugInformation.classMapping.size() &&
methodIndex < debugInformation.methodMapping.size()) {
RecordArray.Record classRecord = debugInformation.classMapping.get(classIndex++);
RecordArray.Record methodRecord = debugInformation.methodMapping.get(methodIndex++);
GeneratedLocation classLoc = DebugInformation.key(classRecord);
GeneratedLocation methodLoc = DebugInformation.key(methodRecord);
int cmp = classLoc.compareTo(methodLoc);
if (cmp < 0) {
nextClassRecord();
} else if (cmp > 0) {
nextMethodRecord();
} else {
nextClassRecord();
nextMethodRecord();
}
} else {
endReached = true;
}
}
private void nextClassRecord() {
RecordArray.Record record = debugInformation.classMapping.get(classIndex++);
classId = record.get(2);
location = DebugInformation.key(record);
}
private void nextMethodRecord() {
RecordArray.Record record = debugInformation.methodMapping.get(methodIndex++);
methodId = record.get(2);
location = DebugInformation.key(record);
}
public void next() {
if (isEndReached()) {
throw new IllegalStateException("End already reached");
}
read();
}
public int getClassNameId() {
if (isEndReached()) {
throw new IllegalStateException("End already reached");
}
return classId;
}
public String getClassName() {
int classId = getClassNameId();
return classId >= 0 ? debugInformation.getClassName(classId) : null;
}
public int getMethodId() {
if (isEndReached()) {
throw new IllegalStateException("End already reached");
}
return methodId;
}
public MethodDescriptor getMethod() {
int methodId = getMethodId();
return methodId >= 0 ? debugInformation.getMethod(methodId) : null;
}
public int getExactMethodId() {
return debugInformation.getExactMethodId(classId, methodId);
}
public MethodReference getExactMethod() {
return new MethodReference(getClassName(), getMethod());
}
public GeneratedLocation getLocation() {
return location;
}
}

View File

@ -1,3 +1,18 @@
/*
* Copyright 2014 Alexey Andreev.
*
* 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 org.teavm.debugging;
/**

View File

@ -1,3 +1,18 @@
/*
* Copyright 2014 Alexey Andreev.
*
* 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 org.teavm.debugging;
/**

View File

@ -0,0 +1,61 @@
/*
* Copyright 2014 Alexey Andreev.
*
* 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 org.teavm.debugging;
import org.teavm.model.MethodDescriptor;
/**
*
* @author Alexey Andreev
*/
public class MethodIterator {
private DebugInformation debugInformation;
private int index;
MethodIterator(DebugInformation debugInformation) {
this.debugInformation = debugInformation;
}
public boolean isEndReached() {
return index < debugInformation.methodMapping.size();
}
public GeneratedLocation getLocation() {
if (isEndReached()) {
throw new IllegalStateException("End already reached");
}
return DebugInformation.key(debugInformation.methodMapping.get(index));
}
public int getMethodId() {
if (isEndReached()) {
throw new IllegalStateException("End already reached");
}
return debugInformation.methodMapping.get(index).get(2);
}
public MethodDescriptor getMethod() {
int methodId = getMethodId();
return methodId >= 0 ? debugInformation.getMethod(methodId) : null;
}
public void next() {
if (isEndReached()) {
throw new IllegalStateException("End already reached");
}
++index;
}
}

View File

@ -1,3 +1,18 @@
/*
* Copyright 2014 Alexey Andreev.
*
* 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 org.teavm.debugging;
import org.teavm.common.RecordArray;

View File

@ -27,9 +27,7 @@ class SourceMapsWriter {
private Writer output;
private int lastLine;
private int lastColumn;
private int sourceLine;
private int lastSourceLine;
private int sourceFile;
private int lastSourceFile;
private boolean first;
@ -58,31 +56,10 @@ class SourceMapsWriter {
first = true;
lastLine = 0;
lastColumn = 0;
sourceLine = -1;
sourceFile = -1;
lastSourceFile = 0;
lastSourceLine = 0;
int i = 0;
int j = 0;
while (i < debugInfo.lineMapping.lines.length && j < debugInfo.fileMapping.lines.length) {
GeneratedLocation a = debugInfo.lineMapping.key(i);
GeneratedLocation b = debugInfo.fileMapping.key(j);
int cmp = a.compareTo(b);
if (cmp < 0) {
writeSegment(a, sourceFile, debugInfo.lineMapping.values[i++]);
} else if (cmp > 0) {
writeSegment(b, debugInfo.fileMapping.values[j++], sourceLine);
} else {
writeSegment(a, debugInfo.fileMapping.values[j++], debugInfo.lineMapping.values[i++] - 1);
}
}
while (i < debugInfo.lineMapping.lines.length) {
GeneratedLocation a = debugInfo.lineMapping.key(i);
writeSegment(a, sourceFile, debugInfo.lineMapping.values[i++]);
}
while (j < debugInfo.fileMapping.lines.length) {
GeneratedLocation b = debugInfo.fileMapping.key(j);
writeSegment(b, debugInfo.fileMapping.values[j++], sourceLine);
for (SourceLocationIterator iter = debugInfo.iterateOverSourceLocations(); !iter.isEndReached(); iter.next()) {
writeSegment(iter.getLocation(), iter.getFileNameId(), iter.getLine());
}
output.write("\"}");
}
@ -106,8 +83,6 @@ class SourceMapsWriter {
lastSourceLine = sourceLine;
}
lastColumn = loc.getColumn();
this.sourceFile = sourceFile;
this.sourceLine = sourceLine;
first = false;
}

View File

@ -608,7 +608,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext
writer.ws().append("=").ws();
}
statement.getRightValue().acceptVisitor(this);
debugEmitter.emitEmptyCallSite();
debugEmitter.emitCallSite();
writer.append(";").softNewLine();
if (statement.getLocation() != null) {
popLocation();
@ -640,7 +640,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext
prevCallSite = debugEmitter.emitCallSite();
writer.append("if").ws().append("(");
statement.getCondition().acceptVisitor(this);
debugEmitter.emitEmptyCallSite();
debugEmitter.emitCallSite();
writer.append(")").ws().append("{").softNewLine().indent();
if (statement.getCondition().getLocation() != null) {
popLocation();
@ -678,7 +678,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext
prevCallSite = debugEmitter.emitCallSite();
writer.append("switch").ws().append("(");
statement.getValue().acceptVisitor(this);
debugEmitter.emitEmptyCallSite();
debugEmitter.emitCallSite();
writer.append(")").ws().append("{").softNewLine().indent();
for (SwitchClause clause : statement.getClauses()) {
for (int condition : clause.getConditions()) {
@ -713,7 +713,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext
if (statement.getCondition() != null) {
prevCallSite = debugEmitter.emitCallSite();
statement.getCondition().acceptVisitor(this);
debugEmitter.emitEmptyCallSite();
debugEmitter.emitCallSite();
} else {
writer.append("true");
}
@ -794,7 +794,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext
writer.append(' ');
prevCallSite = debugEmitter.emitCallSite();
statement.getResult().acceptVisitor(this);
debugEmitter.emitEmptyCallSite();
debugEmitter.emitCallSite();
}
writer.append(";").softNewLine();
if (statement.getLocation() != null) {
@ -815,7 +815,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext
prevCallSite = debugEmitter.emitCallSite();
statement.getException().acceptVisitor(this);
writer.append(");").softNewLine();
debugEmitter.emitEmptyCallSite();
debugEmitter.emitCallSite();
if (statement.getLocation() != null) {
popLocation();
}
@ -1317,6 +1317,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext
if (lastCallSite == null) {
lastCallSite = callSite;
}
boolean virtual = false;
switch (expr.getType()) {
case STATIC:
writer.append(fullName).append("(");
@ -1350,6 +1351,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext
expr.getArguments().get(i).acceptVisitor(this);
}
writer.append(')');
virtual = true;
break;
case CONSTRUCTOR:
writer.append(className).append(".").append(name).append("(");
@ -1364,7 +1366,11 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext
break;
}
if (lastCallSite != null) {
lastCallSite.setMethod(expr.getMethod());
if (virtual) {
lastCallSite.setVirtualMethod(expr.getMethod());
} else {
lastCallSite.setStaticMethod(expr.getMethod());
}
lastCallSite = callSite;
}
if (shouldEraseCallSite) {