/*
* 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.information;
import java.io.*;
import java.util.*;
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;
/**
*
* @author Alexey Andreev
*/
public class DebugInformation {
String[] fileNames;
Map<String, Integer> fileNameMap;
String[] classNames;
Map<String, Integer> classNameMap;
String[] fields;
Map<String, Integer> fieldMap;
String[] methods;
Map<String, Integer> methodMap;
String[] variableNames;
Map<String, Integer> variableNameMap;
long[] exactMethods;
Map<Long, Integer> exactMethodMap;
RecordArray[] fileDescriptions;
RecordArray fileMapping;
RecordArray classMapping;
RecordArray methodMapping;
RecordArray lineMapping;
RecordArray callSiteMapping;
RecordArray statementStartMapping;
RecordArray[] variableMappings;
RecordArray[] lineCallSites;
RecordArray[] controlFlowGraphs;
List<ClassMetadata> classesMetadata;
RecordArray methodEntrances;
MethodTree methodTree;
public String[] getFilesNames() {
return fileNames.clone();
}
public String[] getVariableNames() {
return variableNames.clone();
}
public LineNumberIterator iterateOverLineNumbers() {
return new LineNumberIterator(this);
}
public FileNameIterator iterateOverFileNames() {
return new FileNameIterator(this);
}
public String getFileName(int fileNameId) {
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) {
return Collections.emptyList();
}
RecordArray description = fileIndex >= 0 ? fileDescriptions[fileIndex] : null;
if (description == null) {
return Collections.emptyList();
}
if (line >= description.size()) {
return Collections.emptyList();
}
int[] data = description.get(line).getArray(0);
GeneratedLocation[] resultArray = new GeneratedLocation[data.length / 2];
for (int i = 0; i < resultArray.length; ++i) {
int genLine = data[i * 2];
int genColumn = data[i * 2 + 1];
resultArray[i] = new GeneratedLocation(genLine, genColumn);
}
return Arrays.asList(resultArray);
}
public Collection<GeneratedLocation> getGeneratedLocations(SourceLocation sourceLocation) {
return getGeneratedLocations(sourceLocation.getFileName(), sourceLocation.getLine());
}
public SourceLocationIterator iterateOverSourceLocations() {
return new SourceLocationIterator(this);
}
public SourceLocation getSourceLocation(int line, int column) {
return getSourceLocation(new GeneratedLocation(line, column));
}
public SourceLocation getSourceLocation(GeneratedLocation generatedLocation) {
String fileName = componentByKey(fileMapping, fileNames, generatedLocation);
int lineNumberIndex = indexByKey(lineMapping, generatedLocation);
int lineNumber = lineNumberIndex >= 0 ? lineMapping.get(lineNumberIndex).get(2) : -1;
return new SourceLocation(fileName, lineNumber);
}
public MethodReference getMethodAt(GeneratedLocation generatedLocation) {
String className = componentByKey(classMapping, classNames, generatedLocation);
if (className == null) {
return null;
}
String method = componentByKey(methodMapping, methods, generatedLocation);
if (method == null) {
return null;
}
return new MethodReference(className, MethodDescriptor.parse(method));
}
public MethodReference getMethodAt(int line, int column) {
return getMethodAt(new GeneratedLocation(line, column));
}
public String[] getVariableMeaningAt(int line, int column, String variable) {
return getVariableMeaningAt(new GeneratedLocation(line, column), variable);
}
public String[] getVariableMeaningAt(GeneratedLocation location, String variable) {
Integer varIndex = variableNameMap.get(variable);
if (varIndex == null) {
return new String[0];
}
RecordArray mapping = variableMappings[varIndex];
if (mapping == null) {
return new String[0];
}
int keyIndex = indexByKey(mapping, location);
if (keyIndex < 0) {
return new String[0];
}
GeneratedLocation keyLocation = key(mapping.get(keyIndex));
if (!Objects.equals(getMethodAt(keyLocation), getMethodAt(location))) {
return new String[0];
}
int[] valueIndexes = mapping.get(keyIndex).getArray(0);
String[] result = new String[valueIndexes.length];
for (int i = 0; i < result.length; ++i) {
result[i] = variableNames[valueIndexes[i]];
}
return result;
}
public SourceLocation[] getFollowingLines(SourceLocation location) {
Integer fileIndex = fileNameMap.get(location.getFileName());
if (fileIndex == null) {
return null;
}
RecordArray cfg = controlFlowGraphs[fileIndex];
if (cfg == null) {
return null;
}
if (location.getLine() >= cfg.size()) {
return null;
}
int type = cfg.get(location.getLine()).get(0);
if (type == 0) {
return null;
}
int[] data = cfg.get(location.getLine()).getArray(0);
int length = data.length / 2;
int size = length;
if (type == 2) {
++size;
}
SourceLocation[] result = new SourceLocation[size];
for (int i = 0; i < length; ++i) {
result[i] = new SourceLocation(fileNames[data[i * 2]], data[i * 2 + 1]);
}
return result;
}
public String getFieldMeaning(String className, String jsName) {
Integer classIndex = classNameMap.get(className);
if (classIndex == null) {
return null;
}
Integer jsIndex = fieldMap.get(jsName);
if (jsIndex == null) {
return null;
}
while (classIndex != null) {
ClassMetadata cls = classesMetadata.get(classIndex);
Integer fieldIndex = cls.fieldMap.get(jsIndex);
if (fieldIndex != null) {
return fields[fieldIndex];
}
classIndex = cls.parentId;
}
return null;
}
public DebuggerCallSite getCallSite(GeneratedLocation location) {
int keyIndex = indexByKey(callSiteMapping, location);
return keyIndex >= 0 ? getCallSite(keyIndex) : null;
}
private DebuggerCallSite getCallSite(int index) {
RecordArray.Record record = callSiteMapping.get(index);
int type = record.get(2);
int method = record.get(3);
switch (type) {
case DebuggerCallSite.NONE:
return null;
case DebuggerCallSite.STATIC:
return new DebuggerStaticCallSite(getExactMethod(method));
case DebuggerCallSite.VIRTUAL:
return new DebuggerVirtualCallSite(getExactMethod(method));
default:
throw new AssertionError("Unrecognized call site type: " + type);
}
}
public DebuggerCallSite getCallSite(int line, int column) {
return getCallSite(new GeneratedLocation(line, column));
}
public GeneratedLocation[] getMethodEntrances(MethodReference methodRef) {
Integer index = getExactMethodIndex(methodRef);
if (index == null) {
return new GeneratedLocation[0];
}
int[] data = methodEntrances.get(index).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]);
}
return entrances;
}
public MethodReference[] getDirectOverridingMethods(MethodReference methodRef) {
Integer methodIndex = getExactMethodIndex(methodRef);
if (methodIndex == null) {
return new MethodReference[0];
}
int start = methodTree.offsets[methodIndex];
int end = methodTree.offsets[methodIndex + 1];
MethodReference[] result = new MethodReference[end - start];
for (int i = 0; i < result.length; ++i) {
result[i] = getExactMethod(methodTree.data[i]);
}
return result;
}
public MethodReference[] getOverridingMethods(MethodReference methodRef) {
Set<MethodReference> overridingMethods = new HashSet<>();
getOverridingMethods(methodRef, overridingMethods);
return overridingMethods.toArray(new MethodReference[0]);
}
private void getOverridingMethods(MethodReference methodRef, Set<MethodReference> overridingMethods) {
if (overridingMethods.add(methodRef)) {
for (MethodReference overridingMethod : getDirectOverridingMethods(methodRef)) {
getOverridingMethods(overridingMethod, overridingMethods);
}
}
}
public DebuggerCallSite[] getCallSites(SourceLocation location) {
Integer fileIndex = fileNameMap.get(location.getFileName());
if (fileIndex == null) {
return new DebuggerCallSite[0];
}
RecordArray mapping = lineCallSites[fileIndex];
if (location.getLine() >= mapping.size()) {
return new DebuggerCallSite[0];
}
int[] callSiteIds = mapping.get(location.getLine()).getArray(0);
DebuggerCallSite[] callSites = new DebuggerCallSite[callSiteIds.length];
for (int i = 0; i < callSiteIds.length; ++i) {
callSites[i] = getCallSite(callSiteIds[i]);
}
return callSites;
}
public List<GeneratedLocation> getStatementStartLocations() {
return new LocationList(statementStartMapping);
}
public GeneratedLocation getStatementLocation(GeneratedLocation location) {
int index = indexByKey(statementStartMapping, location);
if (index < 0) {
return new GeneratedLocation(0, 0);
}
RecordArray.Record record = statementStartMapping.get(index);
return new GeneratedLocation(record.get(0), record.get(1));
}
public GeneratedLocation getNextStatementLocation(GeneratedLocation location) {
int index = indexByKey(statementStartMapping, location);
if (index >= statementStartMapping.size()) {
return new GeneratedLocation(0, 0);
}
RecordArray.Record record = statementStartMapping.get(index + 1);
return new GeneratedLocation(record.get(0), record.get(1));
}
private <T> T componentByKey(RecordArray mapping, T[] values, GeneratedLocation location) {
int keyIndex = indexByKey(mapping, location);
int valueIndex = keyIndex >= 0 ? mapping.get(keyIndex).get(2) : -1;
return valueIndex >= 0 ? values[valueIndex] : null;
}
private int indexByKey(RecordArray mapping, GeneratedLocation location) {
int index = Collections.binarySearch(new LocationList(mapping), location);
return index >= 0 ? index : -index - 2;
}
private int valueByKey(RecordArray mapping, GeneratedLocation location) {
int index = indexByKey(mapping, location);
return index >= 0 ? mapping.get(index).get(2) : -1;
}
public void write(OutputStream output) throws IOException {
DebugInformationWriter writer = new DebugInformationWriter(new DataOutputStream(output));
writer.write(this);
}
public void writeAsSourceMaps(Writer output, String sourceRoot, String sourceFile) throws IOException {
new SourceMapsWriter(output).write(sourceFile, sourceRoot, this);
}
public static DebugInformation read(InputStream input) throws IOException {
DebugInformationReader reader = new DebugInformationReader(input);
return reader.read();
}
void rebuild() {
rebuildMaps();
rebuildFileDescriptions();
rebuildEntrances();
rebuildMethodTree();
rebuildLineCallSites();
}
void rebuildMaps() {
fileNameMap = mapArray(fileNames);
classNameMap = mapArray(classNames);
fieldMap = mapArray(fields);
methodMap = mapArray(methods);
variableNameMap = mapArray(variableNames);
exactMethodMap = new HashMap<>();
for (int i = 0; i < exactMethods.length; ++i) {
exactMethodMap.put(exactMethods[i], i);
}
}
private Map<String, Integer> mapArray(String[] array) {
Map<String, Integer> map = new HashMap<>();
for (int i = 0; i < array.length; ++i) {
map.put(array[i], i);
}
return map;
}
void rebuildFileDescriptions() {
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.SubArray array = builder.get(iter.getLine()).getArray(0);
array.add(loc.getLine());
array.add(loc.getColumn());
}
}
fileDescriptions = new RecordArray[builders.length];
for (int i = 0; i < fileDescriptions.length; ++i) {
fileDescriptions[i] = builders[i].build();
}
}
void rebuildEntrances() {
RecordArrayBuilder builder = new RecordArrayBuilder(0, 1);
for (int i = 0; i < exactMethods.length; ++i) {
builder.add();
}
GeneratedLocation prevLocation = new GeneratedLocation(0, 0);
MethodReference prevMethod = null;
int prevMethodId = -1;
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 < lineMapping.size()) {
if (key(lineMapping.get(lineIndex)).compareTo(iter.getLocation()) >= 0) {
break;
}
int line = lineMapping.get(lineIndex).get(2);
if (line >= 0) {
GeneratedLocation firstLineLoc = key(lineMapping.get(lineIndex));
RecordArrayBuilder.SubArray array = builder.get(prevMethodId).getArray(0);
array.add(firstLineLoc.getLine());
array.add(firstLineLoc.getColumn());
break;
}
++lineIndex;
}
}
prevMethod = iter.getExactMethod();
prevMethodId = id;
prevLocation = iter.getLocation();
}
methodEntrances = builder.build();
}
void rebuildMethodTree() {
long[] exactMethods = this.exactMethods.clone();
Arrays.sort(exactMethods);
IntegerArray methods = new IntegerArray(1);
int lastClass = -1;
for (int i = 0; i < exactMethods.length; ++i) {
long exactMethod = exactMethods[i];
int classIndex = (int)(exactMethod >>> 32);
if (classIndex != lastClass) {
if (lastClass >= 0) {
ClassMetadata clsData = classesMetadata.get(lastClass);
clsData.methods = methods.getAll();
methods.clear();
}
lastClass = classIndex;
}
int methodIndex = (int)exactMethod;
methods.add(methodIndex);
}
if (lastClass >= 0) {
ClassMetadata clsData = classesMetadata.get(lastClass);
clsData.methods = methods.getAll();
Arrays.sort(clsData.methods);
}
int[] start = new int[exactMethods.length];
Arrays.fill(start, -1);
IntegerArray data = new IntegerArray(1);
IntegerArray next = new IntegerArray(1);
for (int i = 0; i < classesMetadata.size(); ++i) {
ClassMetadata clsData = classesMetadata.get(i);
if (clsData.parentId == null || clsData.methods == null) {
continue;
}
for (int methodIndex : clsData.methods) {
ClassMetadata superclsData = classesMetadata.get(clsData.parentId);
Integer parentId = clsData.parentId;
while (superclsData != null) {
if (superclsData.methods != null && Arrays.binarySearch(superclsData.methods, methodIndex) >= 0) {
int childMethod = getExactMethodIndex(i, methodIndex);
int parentMethod = getExactMethodIndex(parentId, methodIndex);
int ptr = start[parentMethod];
start[parentMethod] = data.size();
data.add(childMethod);
next.add(ptr);
break;
}
parentId = superclsData.parentId;
superclsData = parentId != null ? classesMetadata.get(parentId) : null;
}
}
}
MethodTree methodTree = new MethodTree();
methodTree.offsets = new int[start.length + 1];
methodTree.data = new int[data.size()];
int index = 0;
for (int i = 0; i < start.length; ++i) {
int ptr = start[i];
while (ptr != -1) {
methodTree.data[index++] = data.get(ptr);
ptr = next.get(ptr);
}
methodTree.offsets[i + 1] = index;
}
this.methodTree = methodTree;
}
private Integer getExactMethodIndex(int classIndex, int methodIndex) {
long entry = ((long)classIndex << 32) | methodIndex;
return exactMethodMap.get(entry);
}
private void rebuildLineCallSites() {
lineCallSites = new RecordArray[fileNames.length];
RecordArrayBuilder[] builders = new RecordArrayBuilder[fileNames.length];
for (int i = 0; i < lineCallSites.length; ++i) {
builders[i] = new RecordArrayBuilder(0, 1);
}
for (int i = 0; i < callSiteMapping.size(); ++i) {
RecordArray.Record callSiteRec = callSiteMapping.get(i);
GeneratedLocation loc = key(callSiteRec);
int callSiteType = callSiteRec.get(2);
if (callSiteType != DebuggerCallSite.NONE) {
int line = valueByKey(lineMapping, loc);
int fileId = valueByKey(fileMapping, loc);
if (fileId >= 0 && line >= 0) {
RecordArrayBuilder builder = builders[fileId];
while (builder.size() <= line) {
builder.add();
}
builder.get(line).getArray(0).add(i);
}
}
}
for (int i = 0; i < lineCallSites.length; ++i) {
lineCallSites[i] = builders[i].build();
}
}
static GeneratedLocation key(RecordArray.Record record) {
return new GeneratedLocation(record.get(0), record.get(1));
}
static class LocationList extends AbstractList<GeneratedLocation> {
private RecordArray recordArray;
public LocationList(RecordArray recordArray) {
this.recordArray = recordArray;
}
@Override
public GeneratedLocation get(int index) {
RecordArray.Record record = recordArray.get(index);
return new GeneratedLocation(record.get(0), record.get(1));
}
@Override
public int size() {
return recordArray.size();
}
}
static class ClassMetadata {
Integer parentId;
Map<Integer, Integer> fieldMap = new HashMap<>();
int[] methods;
}
class MethodTree {
int[] data;
int[] offsets;
public MethodReference[] getOverridingMethods(int index) {
if (index < 0 || index > offsets.length - 1) {
return new MethodReference[0];
}
int start = offsets[index];
int end = offsets[index + 1];
MethodReference[] references = new MethodReference[end - start];
for (int i = 0; i < references.length; ++i) {
long item = exactMethods[data[start + i]];
int classIndex = (int)(item >>> 32);
int methodIndex = (int)item;
references[i] = new MethodReference(classNames[classIndex],
MethodDescriptor.parse(methods[methodIndex]));
}
return references;
}
}
}