From 4ef231c7faeec77a451f533d06e78866b746c1fb Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Thu, 10 Oct 2019 18:41:15 +0300 Subject: [PATCH] JS: preserve stack trace items when agressive inlining enabled --- .../ast/decompilation/StatementGenerator.java | 1 + .../rendering/RenderingContext.java | 62 ++++++++- core/src/main/java/org/teavm/cache/AstIO.java | 98 +++++++------ .../main/java/org/teavm/cache/ProgramIO.java | 99 +++++++------- .../information/ClassNameIterator.java | 55 -------- .../information/DebugInformation.java | 109 ++++++++++----- .../information/DebugInformationBuilder.java | 87 ++++++++---- .../information/DebugInformationEmitter.java | 4 + .../information/DebugInformationReader.java | 13 +- .../information/DebugInformationWriter.java | 11 +- .../DummyDebugInformationEmitter.java | 10 ++ .../information/ExactMethodIterator.java | 23 ++-- .../information/FileNameIterator.java | 55 -------- .../debugging/information/LayerIterator.java | 125 +++++++++++++++++ .../LayerSourceLocationIterator.java | 110 +++++++++++++++ .../information/LineNumberIterator.java | 50 ------- .../debugging/information/MethodIterator.java | 21 +-- .../information/SourceLocationIterator.java | 129 ++++++++++-------- .../information/SourceMapsWriter.java | 3 +- .../teavm/model/optimization/Inlining.java | 2 +- .../model/util/LocationGraphBuilder.java | 6 +- .../devserver/deobfuscate/Deobfuscator.java | 60 ++++---- .../org/teavm/junit/RhinoResultParser.java | 70 ++++++---- .../teavm/junit/TeaVMTestConfiguration.java | 2 +- 24 files changed, 739 insertions(+), 466 deletions(-) delete mode 100644 core/src/main/java/org/teavm/debugging/information/ClassNameIterator.java delete mode 100644 core/src/main/java/org/teavm/debugging/information/FileNameIterator.java create mode 100644 core/src/main/java/org/teavm/debugging/information/LayerIterator.java create mode 100644 core/src/main/java/org/teavm/debugging/information/LayerSourceLocationIterator.java delete mode 100644 core/src/main/java/org/teavm/debugging/information/LineNumberIterator.java diff --git a/core/src/main/java/org/teavm/ast/decompilation/StatementGenerator.java b/core/src/main/java/org/teavm/ast/decompilation/StatementGenerator.java index 02616bb6f..e682b8b3a 100644 --- a/core/src/main/java/org/teavm/ast/decompilation/StatementGenerator.java +++ b/core/src/main/java/org/teavm/ast/decompilation/StatementGenerator.java @@ -497,6 +497,7 @@ class StatementGenerator implements InstructionVisitor { } else { stmt = Statement.assign(null, invocationExpr); } + invocationExpr.setLocation(currentLocation); stmt.setLocation(currentLocation); stmt.setAsync(async); async = false; diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/RenderingContext.java b/core/src/main/java/org/teavm/backend/javascript/rendering/RenderingContext.java index 40916531f..b4564063c 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/RenderingContext.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/RenderingContext.java @@ -25,6 +25,7 @@ import java.util.Deque; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Properties; import java.util.function.Predicate; import org.teavm.backend.javascript.codegen.NamingStrategy; @@ -38,6 +39,7 @@ import org.teavm.interop.PlatformMarker; import org.teavm.model.AnnotationReader; import org.teavm.model.ClassReader; import org.teavm.model.ClassReaderSource; +import org.teavm.model.InliningInfo; import org.teavm.model.ListableClassReaderSource; import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; @@ -62,6 +64,7 @@ public class RenderingContext { private final Map injectorMap = new HashMap<>(); private boolean minifying; private ClassInitializerInfo classInitializerInfo; + private TextLocation lastEmittedLocation = TextLocation.EMPTY; public RenderingContext(DebugInformationEmitter debugEmitter, ClassReaderSource initialClassSource, ListableClassReaderSource classSource, @@ -128,11 +131,11 @@ public class RenderingContext { LocationStackEntry prevEntry = locationStack.peek(); if (location != null) { if (prevEntry == null || !location.equals(prevEntry.location)) { - debugEmitter.emitLocation(location.getFileName(), location.getLine()); + emitLocation(location); } } else { if (prevEntry != null) { - debugEmitter.emitLocation(null, -1); + emitLocation(TextLocation.EMPTY); } } locationStack.push(new LocationStackEntry(location)); @@ -143,13 +146,64 @@ public class RenderingContext { LocationStackEntry entry = locationStack.peek(); if (entry != null) { if (!entry.location.equals(prevEntry.location)) { - debugEmitter.emitLocation(entry.location.getFileName(), entry.location.getLine()); + emitLocation(entry.location); } } else { - debugEmitter.emitLocation(null, -1); + emitLocation(TextLocation.EMPTY); } } + private void emitLocation(TextLocation location) { + if (lastEmittedLocation.equals(location)) { + return; + } + + String fileName = lastEmittedLocation.getFileName(); + int lineNumber = lastEmittedLocation.getLine(); + if (lastEmittedLocation.getInlining() != location.getInlining()) { + InliningInfo[] newPath = location.getInliningPath(); + InliningInfo[] prevPath = lastEmittedLocation.getInliningPath(); + + InliningInfo lastCommonInlining = null; + int pathIndex = 0; + while (pathIndex < prevPath.length && pathIndex < newPath.length + && prevPath[pathIndex].equals(newPath[pathIndex])) { + lastCommonInlining = prevPath[pathIndex++]; + } + + InliningInfo prevInlining = lastEmittedLocation.getInlining(); + while (prevInlining != lastCommonInlining) { + debugEmitter.exitLocation(); + fileName = prevInlining.getFileName(); + lineNumber = prevInlining.getLine(); + prevInlining = prevInlining.getParent(); + } + + while (pathIndex < newPath.length) { + InliningInfo inlining = newPath[pathIndex++]; + emitSimpleLocation(fileName, lineNumber, inlining.getFileName(), inlining.getLine()); + fileName = null; + lineNumber = -1; + + debugEmitter.enterLocation(); + debugEmitter.emitClass(inlining.getMethod().getClassName()); + debugEmitter.emitMethod(inlining.getMethod().getDescriptor()); + } + } + + emitSimpleLocation(fileName, lineNumber, location.getFileName(), location.getLine()); + lastEmittedLocation = location; + } + + + private void emitSimpleLocation(String fileName, int lineNumber, String newFileName, int newLineNumber) { + if (Objects.equals(fileName, newFileName) && lineNumber == newLineNumber) { + return; + } + + debugEmitter.emitLocation(newFileName, newLineNumber); + } + public boolean isMinifying() { return minifying; } diff --git a/core/src/main/java/org/teavm/cache/AstIO.java b/core/src/main/java/org/teavm/cache/AstIO.java index 39c5ee4bb..43bd55bd8 100644 --- a/core/src/main/java/org/teavm/cache/AstIO.java +++ b/core/src/main/java/org/teavm/cache/AstIO.java @@ -250,47 +250,63 @@ public class AstIO { return; } - InliningInfo lastCommonInlining = null; - InliningInfo[] prevPath = lastLocation.getInliningPath(); - InliningInfo[] newPath = location.getInliningPath(); - int pathIndex = 0; - while (pathIndex < prevPath.length && pathIndex < newPath.length - && prevPath[pathIndex].equals(newPath[pathIndex])) { - lastCommonInlining = prevPath[pathIndex++]; - } + String fileName = lastLocation.getFileName(); + int lineNumber = lastLocation.getLine(); - InliningInfo prevInlining = lastLocation.getInlining(); - while (prevInlining != lastCommonInlining) { - output.writeUnsigned(124); - prevInlining = prevInlining.getParent(); - } + if (location.getInlining() != lastLocation.getInlining()) { + InliningInfo lastCommonInlining = null; + InliningInfo[] prevPath = lastLocation.getInliningPath(); + InliningInfo[] newPath = location.getInliningPath(); + int pathIndex = 0; + while (pathIndex < prevPath.length && pathIndex < newPath.length + && prevPath[pathIndex].equals(newPath[pathIndex])) { + lastCommonInlining = prevPath[pathIndex++]; + } - while (pathIndex < newPath.length) { - InliningInfo inlining = newPath[pathIndex++]; - MethodReference method = inlining.getMethod(); - output.writeUnsigned(inlining.isEmpty() ? 122 : 123); - output.writeUnsigned(symbolTable.lookup(method.getClassName())); - output.writeUnsigned(symbolTable.lookup(method.getDescriptor().toString())); - if (!inlining.isEmpty()) { - output.writeUnsigned(fileTable.lookup(inlining.getFileName())); - output.writeUnsigned(inlining.getLine()); + InliningInfo prevInlining = location.getInlining(); + while (prevInlining != lastCommonInlining) { + output.writeUnsigned(123); + fileName = prevInlining.getFileName(); + lineNumber = prevInlining.getLine(); + prevInlining = prevInlining.getParent(); + } + + while (pathIndex < newPath.length) { + InliningInfo inlining = newPath[pathIndex++]; + writeSimpleLocation(fileName, lineNumber, inlining.getFileName(), inlining.getLine()); + fileName = null; + lineNumber = -1; + + output.writeUnsigned(124); + MethodReference method = inlining.getMethod(); + output.writeUnsigned(symbolTable.lookup(method.getClassName())); + output.writeUnsigned(symbolTable.lookup(method.getDescriptor().toString())); } } - if (location.isEmpty()) { - output.writeUnsigned(127); - } else if (!lastLocation.isEmpty() && lastLocation.getFileName().equals(location.getFileName())) { - output.writeUnsigned(126); - output.writeSigned(location.getLine() - lastLocation.getLine()); - } else { - output.writeUnsigned(125); - output.writeUnsigned(fileTable.lookup(location.getFileName())); - output.writeUnsigned(location.getLine()); - } + writeSimpleLocation(fileName, lineNumber, location.getFileName(), location.getLine()); lastLocation = location; } + private void writeSimpleLocation(String fileName, int lineNumber, String newFileName, int newLineNumber) + throws IOException { + if (Objects.equals(fileName, newFileName) && lineNumber == newLineNumber) { + return; + } + + if (newFileName == null) { + output.writeUnsigned(127); + } else if (fileName != null && fileName.equals(newFileName)) { + output.writeUnsigned(126); + output.writeSigned(newLineNumber - lineNumber); + } else { + output.writeUnsigned(125); + output.writeUnsigned(fileTable.lookup(newFileName)); + output.writeUnsigned(newLineNumber); + } + } + private void writeSequence(List sequence) throws IOException { output.writeUnsigned(sequence.size()); for (Statement part : sequence) { @@ -741,25 +757,17 @@ public class AstIO { lastReadLocation = new TextLocation(fileTable.at(input.readUnsigned()), input.readUnsigned(), lastReadInlining); break; - case 122: - case 123: { + case 124: { String className = symbolTable.at(input.readUnsigned()); MethodDescriptor methodDescriptor = MethodDescriptor.parse(symbolTable.at(input.readUnsigned())); methodDescriptor = referenceCache.getCached(methodDescriptor); - String fileName; - int lineNumber; - if (type == 122) { - fileName = fileTable.at(input.readUnsigned()); - lineNumber = input.readUnsigned(); - } else { - fileName = null; - lineNumber = -1; - } lastReadInlining = new InliningInfo(referenceCache.getCached(className, methodDescriptor), - fileName, lineNumber, lastReadInlining); + lastReadLocation.getFileName(), lastReadLocation.getLine(), lastReadInlining); + lastReadLocation = new TextLocation(null, -1, lastReadInlining); break; } - case 124: + case 123: + lastReadLocation = new TextLocation(lastReadInlining.getFileName(), lastReadInlining.getLine()); lastReadInlining = lastReadInlining.getParent(); break; default: diff --git a/core/src/main/java/org/teavm/cache/ProgramIO.java b/core/src/main/java/org/teavm/cache/ProgramIO.java index 5b4a73a2a..42d5d1655 100644 --- a/core/src/main/java/org/teavm/cache/ProgramIO.java +++ b/core/src/main/java/org/teavm/cache/ProgramIO.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.List; +import java.util.Objects; import org.teavm.model.BasicBlock; import org.teavm.model.BasicBlockReader; import org.teavm.model.FieldReference; @@ -208,7 +209,7 @@ public class ProgramIO { } InliningInfo inliningInfo = null; - TextLocation location = null; + TextLocation location = TextLocation.EMPTY; insnLoop: while (true) { int insnType = data.readUnsigned(); switch (insnType) { @@ -228,24 +229,16 @@ public class ProgramIO { location = new TextLocation(location.getFileName(), line, inliningInfo); break; } - case 124: case 125: { String className = symbolTable.at(data.readUnsigned()); MethodDescriptor methodDescriptor = parseMethodDescriptor(symbolTable.at(data.readUnsigned())); - String fileName; - int lineNumber; - if (insnType == 125) { - fileName = fileTable.at(data.readUnsigned()); - lineNumber = data.readUnsigned(); - } else { - fileName = null; - lineNumber = -1; - } inliningInfo = new InliningInfo(createMethodReference(className, methodDescriptor), - fileName, lineNumber, inliningInfo); + location.getFileName(), location.getLine(), inliningInfo); + location = new TextLocation(null, -1, inliningInfo); break; } case 126: + location = new TextLocation(inliningInfo.getFileName(), inliningInfo.getLine()); inliningInfo = inliningInfo.getParent(); break; default: { @@ -276,51 +269,65 @@ public class ProgramIO { newLocation = TextLocation.EMPTY; } - InliningInfo lastCommonInlining = null; - InliningInfo[] prevPath = location.getInliningPath(); - InliningInfo[] newPath = newLocation.getInliningPath(); - int pathIndex = 0; - while (pathIndex < prevPath.length && pathIndex < newPath.length - && prevPath[pathIndex].equals(newPath[pathIndex])) { - lastCommonInlining = prevPath[pathIndex++]; - } + String fileName = location.getFileName(); + int lineNumber = location.getLine(); - InliningInfo prevInlining = location.getInlining(); - while (prevInlining != lastCommonInlining) { - output.writeUnsigned(126); - prevInlining = prevInlining.getParent(); - } + if (newLocation.getInlining() != location.getInlining()) { + InliningInfo lastCommonInlining = null; + InliningInfo[] prevPath = location.getInliningPath(); + InliningInfo[] newPath = newLocation.getInliningPath(); + int pathIndex = 0; + while (pathIndex < prevPath.length && pathIndex < newPath.length + && prevPath[pathIndex].equals(newPath[pathIndex])) { + lastCommonInlining = prevPath[pathIndex++]; + } - while (pathIndex < newPath.length) { - InliningInfo inlining = newPath[pathIndex++]; - MethodReference method = inlining.getMethod(); - output.writeUnsigned(inlining.isEmpty() ? 124 : 125); - output.writeUnsigned(symbolTable.lookup(method.getClassName())); - output.writeUnsigned(symbolTable.lookup(method.getDescriptor().toString())); - if (!inlining.isEmpty()) { - output.writeUnsigned(fileTable.lookup(inlining.getFileName())); - output.writeUnsigned(inlining.getLine()); + InliningInfo prevInlining = location.getInlining(); + while (prevInlining != lastCommonInlining) { + output.writeUnsigned(126); + fileName = prevInlining.getFileName(); + lineNumber = prevInlining.getLine(); + prevInlining = prevInlining.getParent(); + } + + while (pathIndex < newPath.length) { + InliningInfo inlining = newPath[pathIndex++]; + writeSimpleLocation(fileName, lineNumber, inlining.getFileName(), inlining.getLine()); + fileName = null; + lineNumber = -1; + + output.writeUnsigned(125); + MethodReference method = inlining.getMethod(); + output.writeUnsigned(symbolTable.lookup(method.getClassName())); + output.writeUnsigned(symbolTable.lookup(method.getDescriptor().toString())); } } - if (newLocation.isEmpty()) { - output.writeUnsigned(1); - } else { - if (!location.isEmpty() && location.getFileName().equals(newLocation.getFileName())) { - output.writeUnsigned(127); - output.writeSigned(newLocation.getLine() - location.getLine()); - } else { - output.writeUnsigned(2); - output.writeUnsigned(fileTable.lookup(newLocation.getFileName())); - output.writeUnsigned(newLocation.getLine()); - } - } + writeSimpleLocation(fileName, lineNumber, newLocation.getFileName(), newLocation.getLine()); location = newLocation; } catch (IOException e) { throw new IOExceptionWrapper(e); } } + private void writeSimpleLocation(String fileName, int lineNumber, String newFileName, int newLineNumber) + throws IOException { + if (Objects.equals(fileName, newFileName) && lineNumber == newLineNumber) { + return; + } + + if (newFileName == null) { + output.writeUnsigned(1); + } else if (fileName != null && fileName.equals(newFileName)) { + output.writeUnsigned(127); + output.writeSigned(newLineNumber - lineNumber); + } else { + output.writeUnsigned(2); + output.writeUnsigned(fileTable.lookup(newFileName)); + output.writeUnsigned(newLineNumber); + } + } + @Override public void nop() { try { diff --git a/core/src/main/java/org/teavm/debugging/information/ClassNameIterator.java b/core/src/main/java/org/teavm/debugging/information/ClassNameIterator.java deleted file mode 100644 index 2b3200ed2..000000000 --- a/core/src/main/java/org/teavm/debugging/information/ClassNameIterator.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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; - -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; - } -} diff --git a/core/src/main/java/org/teavm/debugging/information/DebugInformation.java b/core/src/main/java/org/teavm/debugging/information/DebugInformation.java index 6e6592169..faf1ecce2 100644 --- a/core/src/main/java/org/teavm/debugging/information/DebugInformation.java +++ b/core/src/main/java/org/teavm/debugging/information/DebugInformation.java @@ -38,10 +38,7 @@ public class DebugInformation { long[] exactMethods; Map exactMethodMap; RecordArray[] fileDescriptions; - RecordArray fileMapping; - RecordArray classMapping; - RecordArray methodMapping; - RecordArray lineMapping; + Layer[] layers; RecordArray callSiteMapping; RecordArray statementStartMapping; RecordArray[] variableMappings; @@ -69,14 +66,6 @@ public class DebugInformation { 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]; } @@ -135,16 +124,12 @@ public class DebugInformation { return id != null ? id : -1; } - public ClassNameIterator iterateOverClassNames() { - return new ClassNameIterator(this); + public int layerCount() { + return layers.length; } - public MethodIterator iterateOverMethods() { - return new MethodIterator(this); - } - - public ExactMethodIterator iterateOverExactMethods() { - return new ExactMethodIterator(this); + public ExactMethodIterator iterateOverExactMethods(int layerIndex) { + return new ExactMethodIterator(this, layers[layerIndex]); } public Collection getGeneratedLocations(String fileName, int line) { @@ -177,29 +162,67 @@ public class DebugInformation { return new SourceLocationIterator(this); } + private LayerSourceLocationIterator iterateOverSourceLocations(int layer) { + return new LayerSourceLocationIterator(this, layers[layer]); + } + public SourceLocation getSourceLocation(int line, int column) { return getSourceLocation(new GeneratedLocation(line, column)); } + public SourceLocation getSourceLocation(int line, int column, int layerIndex) { + return getSourceLocation(new GeneratedLocation(line, column), layerIndex); + } + 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 getSourceLocation(generatedLocation, autodetectLayer(generatedLocation)); + } + + public SourceLocation getSourceLocation(GeneratedLocation generatedLocation, int layerIndex) { + if (layerIndex < 0 || layerIndex >= layers.length) { + return null; + } + + Layer layer = layers[layerIndex]; + String fileName = componentByKey(layer.fileMapping, fileNames, generatedLocation); + int lineNumberIndex = indexByKey(layer.lineMapping, generatedLocation); + int lineNumber = lineNumberIndex >= 0 ? layer.lineMapping.get(lineNumberIndex).get(2) : -1; return new SourceLocation(fileName, lineNumber); } public MethodReference getMethodAt(GeneratedLocation generatedLocation) { - String className = componentByKey(classMapping, classNames, generatedLocation); + return getMethodAt(generatedLocation, autodetectLayer(generatedLocation)); + } + + public MethodReference getMethodAt(GeneratedLocation generatedLocation, int layerIndex) { + if (layerIndex < 0 || layerIndex >= layers.length) { + return null; + } + + Layer layer = layers[layerIndex]; + + String className = componentByKey(layer.classMapping, classNames, generatedLocation); if (className == null) { return null; } - String method = componentByKey(methodMapping, methods, generatedLocation); + String method = componentByKey(layer.methodMapping, methods, generatedLocation); if (method == null) { return null; } return referenceCache.getCached(className, referenceCache.parseDescriptorCached(method)); } + private int autodetectLayer(GeneratedLocation generatedLocation) { + int layerIndex = 0; + for (int i = 1; i < layers.length; ++i) { + if (componentByKey(layers[i].classMapping, classNames, generatedLocation) == null) { + break; + } + layerIndex = i; + } + return layerIndex; + } + public MethodReference getMethodAt(int line, int column) { return getMethodAt(new GeneratedLocation(line, column)); } @@ -460,16 +483,19 @@ public class DebugInformation { 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(); + for (int layer = 0; layer < layers.length; ++layer) { + for (LayerSourceLocationIterator iter = iterateOverSourceLocations(layer); + !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()); } - 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]; @@ -486,7 +512,8 @@ public class DebugInformation { GeneratedLocation prevLocation = new GeneratedLocation(0, 0); MethodReference prevMethod = null; int prevMethodId = -1; - for (ExactMethodIterator iter = iterateOverExactMethods(); !iter.isEndReached(); iter.next()) { + RecordArray lineMapping = layers[0].lineMapping; + for (ExactMethodIterator iter = iterateOverExactMethods(0); !iter.isEndReached(); iter.next()) { int id = iter.getExactMethodId(); if (prevMethod != null) { int lineIndex = Math.max(0, indexByKey(lineMapping, prevLocation)); @@ -596,8 +623,9 @@ public class DebugInformation { GeneratedLocation loc = key(callSiteRec); int callSiteType = callSiteRec.get(2); if (callSiteType != DebuggerCallSite.NONE) { - int line = valueByKey(lineMapping, loc); - int fileId = valueByKey(fileMapping, loc); + int layerIndex = autodetectLayer(loc); + int line = valueByKey(layers[layerIndex].lineMapping, loc); + int fileId = valueByKey(layers[layerIndex].fileMapping, loc); if (fileId >= 0 && line >= 0) { RecordArrayBuilder builder = builders[fileId]; while (builder.size() <= line) { @@ -699,4 +727,11 @@ public class DebugInformation { return references; } } + + static class Layer { + RecordArray fileMapping; + RecordArray classMapping; + RecordArray methodMapping; + RecordArray lineMapping; + } } diff --git a/core/src/main/java/org/teavm/debugging/information/DebugInformationBuilder.java b/core/src/main/java/org/teavm/debugging/information/DebugInformationBuilder.java index d41fad85b..ffe26a1ca 100644 --- a/core/src/main/java/org/teavm/debugging/information/DebugInformationBuilder.java +++ b/core/src/main/java/org/teavm/debugging/information/DebugInformationBuilder.java @@ -34,23 +34,20 @@ public class DebugInformationBuilder implements DebugInformationEmitter { private List exactMethods = new ArrayList<>(); private Map exactMethodMap = new HashMap<>(); private RecordArrayBuilder statementStartMapping = new RecordArrayBuilder(2, 0); - 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 List layers = new ArrayList<>(); + private LayerBuilder currentLayer; + private int currentLayerIndex; private RecordArrayBuilder callSiteMapping = new RecordArrayBuilder(4, 0); private Map variableMappings = new HashMap<>(); - private MethodDescriptor currentMethod; - private String currentClass; - private String currentFileName; private int currentClassMetadata = -1; private List classesMetadata = new ArrayList<>(); private List cfgs = new ArrayList<>(); - private int currentLine; private ReferenceCache referenceCache; public DebugInformationBuilder(ReferenceCache referenceCache) { this.referenceCache = referenceCache; + currentLayer = new LayerBuilder(); + layers.add(currentLayer); } public LocationProvider getLocationProvider() { @@ -66,16 +63,32 @@ public class DebugInformationBuilder implements DebugInformationEmitter { public void emitLocation(String fileName, int line) { debugInformation = null; int fileIndex = files.index(fileName); - if (!Objects.equals(currentFileName, fileName)) { - add(fileMapping, fileIndex); - currentFileName = fileName; + if (!Objects.equals(currentLayer.currentFileName, fileName)) { + add(currentLayer.fileMapping, fileIndex); + currentLayer.currentFileName = fileName; } - if (currentLine != line) { - add(lineMapping, line); - currentLine = line; + if (currentLayer.currentLine != line) { + add(currentLayer.lineMapping, line); + currentLayer.currentLine = line; } } + @Override + public void enterLocation() { + if (++currentLayerIndex >= layers.size()) { + layers.add(new LayerBuilder()); + } + currentLayer = layers.get(currentLayerIndex); + } + + @Override + public void exitLocation() { + emitClass(null); + emitMethod(null); + emitLocation(null, -1); + currentLayer = layers.get(--currentLayerIndex); + } + private RecordArrayBuilder.Record add(RecordArrayBuilder builder) { if (builder.size() > 1) { RecordArrayBuilder.Record lastRecord = builder.get(builder.size() - 1); @@ -99,9 +112,9 @@ public class DebugInformationBuilder implements DebugInformationEmitter { public void emitClass(String className) { debugInformation = null; int classIndex = classes.index(className); - if (!Objects.equals(className, currentClass)) { - add(classMapping, classIndex); - currentClass = className; + if (!Objects.equals(className, currentLayer.currentClass)) { + add(currentLayer.classMapping, classIndex); + currentLayer.currentClass = className; } } @@ -109,12 +122,12 @@ public class DebugInformationBuilder implements DebugInformationEmitter { public void emitMethod(MethodDescriptor method) { debugInformation = null; int methodIndex = methods.index(method != null ? method.toString() : null); - if (!Objects.equals(method, currentMethod)) { - add(methodMapping, methodIndex); - currentMethod = method; + if (!Objects.equals(method, currentLayer.currentMethod)) { + add(currentLayer.methodMapping, methodIndex); + currentLayer.currentMethod = method; } - if (currentClass != null) { - int classIndex = classes.index(currentClass); + if (currentLayer.currentClass != null) { + int classIndex = classes.index(currentLayer.currentClass); long fullIndex = ((long) classIndex << 32) | methodIndex; if (!exactMethodMap.containsKey(fullIndex)) { exactMethodMap.put(fullIndex, exactMethods.size()); @@ -283,10 +296,19 @@ public class DebugInformationBuilder implements DebugInformationEmitter { debugInformation.exactMethodMap = new HashMap<>(exactMethodMap); debugInformation.statementStartMapping = statementStartMapping.build(); - debugInformation.fileMapping = compress(fileMapping).build(); - debugInformation.lineMapping = compress(lineMapping).build(); - debugInformation.classMapping = compress(classMapping).build(); - debugInformation.methodMapping = compress(methodMapping).build(); + + DebugInformation.Layer[] builtLayers = new DebugInformation.Layer[layers.size()]; + for (int i = 0; i < builtLayers.length; ++i) { + LayerBuilder layerBuilder = layers.get(i); + DebugInformation.Layer builtLayer = new DebugInformation.Layer(); + builtLayer.fileMapping = compress(layerBuilder.fileMapping).build(); + builtLayer.lineMapping = compress(layerBuilder.lineMapping).build(); + builtLayer.classMapping = compress(layerBuilder.classMapping).build(); + builtLayer.methodMapping = compress(layerBuilder.methodMapping).build(); + builtLayers[i] = builtLayer; + } + debugInformation.layers = builtLayers; + debugInformation.callSiteMapping = callSiteMapping.build(); debugInformation.variableMappings = new RecordArray[variableNames.list.size()]; for (int var : variableMappings.keySet()) { @@ -339,7 +361,7 @@ public class DebugInformationBuilder implements DebugInformationEmitter { } public String[] getItems() { - return list.toArray(new String[list.size()]); + return list.toArray(new String[0]); } public Map getIndexes() { @@ -352,4 +374,15 @@ public class DebugInformationBuilder implements DebugInformationEmitter { String jsName; Map fieldMap = new HashMap<>(); } + + static class LayerBuilder { + 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 MethodDescriptor currentMethod; + private String currentClass; + private String currentFileName; + private int currentLine; + } } diff --git a/core/src/main/java/org/teavm/debugging/information/DebugInformationEmitter.java b/core/src/main/java/org/teavm/debugging/information/DebugInformationEmitter.java index e0cf01c6e..856211ee5 100644 --- a/core/src/main/java/org/teavm/debugging/information/DebugInformationEmitter.java +++ b/core/src/main/java/org/teavm/debugging/information/DebugInformationEmitter.java @@ -23,6 +23,10 @@ public interface DebugInformationEmitter { void emitLocation(String fileName, int line); + void enterLocation(); + + void exitLocation(); + void emitStatementStart(); void emitMethod(MethodDescriptor method); diff --git a/core/src/main/java/org/teavm/debugging/information/DebugInformationReader.java b/core/src/main/java/org/teavm/debugging/information/DebugInformationReader.java index e33c5fac7..56341db4b 100644 --- a/core/src/main/java/org/teavm/debugging/information/DebugInformationReader.java +++ b/core/src/main/java/org/teavm/debugging/information/DebugInformationReader.java @@ -43,10 +43,15 @@ class DebugInformationReader { debugInfo.methods = readStrings(); debugInfo.variableNames = readStrings(); debugInfo.exactMethods = readExactMethods(); - debugInfo.fileMapping = readMapping(); - debugInfo.lineMapping = readMapping(); - debugInfo.classMapping = readMapping(); - debugInfo.methodMapping = readMapping(); + debugInfo.layers = new DebugInformation.Layer[input.read()]; + for (int i = 0; i < debugInfo.layers.length; ++i) { + DebugInformation.Layer layer = new DebugInformation.Layer(); + layer.fileMapping = readMapping(); + layer.lineMapping = readMapping(); + layer.classMapping = readMapping(); + layer.methodMapping = readMapping(); + debugInfo.layers[i] = layer; + } debugInfo.statementStartMapping = readBooleanMapping(); debugInfo.callSiteMapping = readCallSiteMapping(); debugInfo.variableMappings = readVariableMappings(debugInfo.variableNames.length); diff --git a/core/src/main/java/org/teavm/debugging/information/DebugInformationWriter.java b/core/src/main/java/org/teavm/debugging/information/DebugInformationWriter.java index 7aeda5118..617a7623f 100644 --- a/core/src/main/java/org/teavm/debugging/information/DebugInformationWriter.java +++ b/core/src/main/java/org/teavm/debugging/information/DebugInformationWriter.java @@ -42,10 +42,13 @@ class DebugInformationWriter { writeStringArray(debugInfo.variableNames); writeExactMethods(debugInfo.exactMethods); - writeMapping(debugInfo.fileMapping); - writeMapping(debugInfo.lineMapping); - writeMapping(debugInfo.classMapping); - writeMapping(debugInfo.methodMapping); + output.write(debugInfo.layers.length); + for (DebugInformation.Layer layer : debugInfo.layers) { + writeMapping(layer.fileMapping); + writeMapping(layer.lineMapping); + writeMapping(layer.classMapping); + writeMapping(layer.methodMapping); + } writeLinesAndColumns(debugInfo.statementStartMapping); writeCallSiteMapping(debugInfo.callSiteMapping); writeVariableMappings(debugInfo); diff --git a/core/src/main/java/org/teavm/debugging/information/DummyDebugInformationEmitter.java b/core/src/main/java/org/teavm/debugging/information/DummyDebugInformationEmitter.java index d6f1a7b2e..81d90d819 100644 --- a/core/src/main/java/org/teavm/debugging/information/DummyDebugInformationEmitter.java +++ b/core/src/main/java/org/teavm/debugging/information/DummyDebugInformationEmitter.java @@ -24,6 +24,16 @@ public class DummyDebugInformationEmitter implements DebugInformationEmitter { public void emitLocation(String fileName, int line) { } + @Override + public void enterLocation() { + + } + + @Override + public void exitLocation() { + + } + @Override public void emitMethod(MethodDescriptor method) { } diff --git a/core/src/main/java/org/teavm/debugging/information/ExactMethodIterator.java b/core/src/main/java/org/teavm/debugging/information/ExactMethodIterator.java index f36af4ab5..c3e9c63fa 100644 --- a/core/src/main/java/org/teavm/debugging/information/ExactMethodIterator.java +++ b/core/src/main/java/org/teavm/debugging/information/ExactMethodIterator.java @@ -21,29 +21,30 @@ import org.teavm.model.MethodReference; public class ExactMethodIterator { private DebugInformation debugInformation; + private DebugInformation.Layer layer; private GeneratedLocation location; private int classIndex; private int methodIndex; private int classId = -1; private int methodId = -1; - ExactMethodIterator(DebugInformation debugInformation) { + ExactMethodIterator(DebugInformation debugInformation, DebugInformation.Layer layer) { this.debugInformation = debugInformation; + this.layer = layer; if (!isEndReached()) { read(); } } public boolean isEndReached() { - return methodIndex >= debugInformation.methodMapping.size() - && classIndex >= debugInformation.classMapping.size(); + return methodIndex >= layer.methodMapping.size() && classIndex >= layer.classMapping.size(); } private void read() { - if (classIndex < debugInformation.classMapping.size() - && methodIndex < debugInformation.methodMapping.size()) { - RecordArray.Record classRecord = debugInformation.classMapping.get(classIndex); - RecordArray.Record methodRecord = debugInformation.methodMapping.get(methodIndex); + if (classIndex < layer.classMapping.size() + && methodIndex < layer.methodMapping.size()) { + RecordArray.Record classRecord = layer.classMapping.get(classIndex); + RecordArray.Record methodRecord = layer.methodMapping.get(methodIndex); GeneratedLocation classLoc = DebugInformation.key(classRecord); GeneratedLocation methodLoc = DebugInformation.key(methodRecord); int cmp = classLoc.compareTo(methodLoc); @@ -55,9 +56,9 @@ public class ExactMethodIterator { nextClassRecord(); nextMethodRecord(); } - } else if (classIndex < debugInformation.classMapping.size()) { + } else if (classIndex < layer.classMapping.size()) { nextClassRecord(); - } else if (methodIndex < debugInformation.methodMapping.size()) { + } else if (methodIndex < layer.methodMapping.size()) { nextMethodRecord(); } else { throw new IllegalStateException("End already reached"); @@ -65,13 +66,13 @@ public class ExactMethodIterator { } private void nextClassRecord() { - RecordArray.Record record = debugInformation.classMapping.get(classIndex++); + RecordArray.Record record = layer.classMapping.get(classIndex++); classId = record.get(2); location = DebugInformation.key(record); } private void nextMethodRecord() { - RecordArray.Record record = debugInformation.methodMapping.get(methodIndex++); + RecordArray.Record record = layer.methodMapping.get(methodIndex++); methodId = record.get(2); location = DebugInformation.key(record); } diff --git a/core/src/main/java/org/teavm/debugging/information/FileNameIterator.java b/core/src/main/java/org/teavm/debugging/information/FileNameIterator.java deleted file mode 100644 index 1383254f6..000000000 --- a/core/src/main/java/org/teavm/debugging/information/FileNameIterator.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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; - -public class FileNameIterator { - private DebugInformation debugInformation; - private int index; - - FileNameIterator(DebugInformation debugInformation) { - this.debugInformation = debugInformation; - } - - public boolean isEndReached() { - return index < debugInformation.fileMapping.size(); - } - - public GeneratedLocation getLocation() { - if (isEndReached()) { - throw new IllegalStateException("End already reached"); - } - return DebugInformation.key(debugInformation.fileMapping.get(index)); - } - - public int getFileNameId() { - if (isEndReached()) { - throw new IllegalStateException("End already reached"); - } - return debugInformation.fileMapping.get(index).get(2); - } - - public String getFileName() { - int fileNameId = getFileNameId(); - return fileNameId >= 0 ? debugInformation.getFileName(fileNameId) : null; - } - - public void next() { - if (isEndReached()) { - throw new IllegalStateException("End already reached"); - } - ++index; - } -} diff --git a/core/src/main/java/org/teavm/debugging/information/LayerIterator.java b/core/src/main/java/org/teavm/debugging/information/LayerIterator.java new file mode 100644 index 000000000..ee8e5ae59 --- /dev/null +++ b/core/src/main/java/org/teavm/debugging/information/LayerIterator.java @@ -0,0 +1,125 @@ +/* + * Copyright 2019 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; + +class LayerIterator { + private int currentLayer; + private MethodIterator[] methodIterators; + private GeneratedLocation currentLocation; + + LayerIterator(DebugInformation debugInformation) { + methodIterators = new MethodIterator[debugInformation.layerCount()]; + for (int i = 0; i < methodIterators.length; ++i) { + methodIterators[i] = new MethodIterator(debugInformation.layers[i]); + } + + nextImpl(); + } + + public boolean isEndReached() { + return currentLayer == 0 && methodIterators[currentLayer].isEndReached(); + } + + public int getLayer() { + if (isEndReached()) { + throw new IllegalStateException(); + } + return currentLayer; + } + + public GeneratedLocation getLocation() { + if (isEndReached()) { + throw new IllegalStateException(); + } + return currentLocation; + } + + public void next() { + if (isEndReached()) { + throw new IllegalStateException(); + } + int previous = currentLayer; + do { + nextImpl(); + } while (!isEndReached() && previous == currentLayer); + } + + private void nextImpl() { + if (isEndReached()) { + throw new IllegalStateException("End already reached"); + } + + if (canEnterLayer()) { + ++currentLayer; + while (canEnterLayer()) { + ++currentLayer; + } + currentLocation = methodIterators[currentLayer].getLocation(); + methodIterators[currentLayer].next(); + } else if (canExitLayer()) { + GeneratedLocation limitLocation = methodIterators[currentLayer].getLocation(); + moveToNextNotEmpty(); + while (true) { + --currentLayer; + moveToLocation(limitLocation); + if (currentLayer <= 0 || methodIterators[currentLayer].getMethodId() >= 0 + || !methodIterators[currentLayer].getLocation().equals(limitLocation)) { + break; + } + moveToNextNotEmpty(); + } + currentLocation = limitLocation; + } else { + currentLocation = methodIterators[currentLayer].getLocation(); + methodIterators[currentLayer].next(); + } + } + + private boolean canEnterLayer() { + return currentLayer < methodIterators.length - 1 + && !methodIterators[currentLayer + 1].isEndReached() + && !methodIterators[currentLayer].isEndReached() + && isLocationLessOrEqual(currentLayer + 1, currentLayer); + } + + private boolean canExitLayer() { + if (currentLayer == 0) { + return false; + } + return methodIterators[currentLayer].getMethodId() < 0; + } + + private void moveToNextNotEmpty() { + while (!methodIterators[currentLayer].isEndReached() && methodIterators[currentLayer].getMethodId() < 0) { + methodIterators[currentLayer].next(); + } + } + + private void moveToLocation(GeneratedLocation location) { + while (true) { + GeneratedLocation nextLocation = methodIterators[currentLayer].getLocation(); + if (nextLocation.compareTo(location) >= 0) { + break; + } + currentLocation = location; + methodIterators[currentLayer].next(); + } + } + + private boolean isLocationLessOrEqual(int firstLayer, int secondLayer) { + return methodIterators[firstLayer].getLocation().compareTo(methodIterators[secondLayer].getLocation()) <= 0; + } +} diff --git a/core/src/main/java/org/teavm/debugging/information/LayerSourceLocationIterator.java b/core/src/main/java/org/teavm/debugging/information/LayerSourceLocationIterator.java new file mode 100644 index 000000000..ab3ea0995 --- /dev/null +++ b/core/src/main/java/org/teavm/debugging/information/LayerSourceLocationIterator.java @@ -0,0 +1,110 @@ +/* + * Copyright 2019 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 org.teavm.common.RecordArray; + +class LayerSourceLocationIterator { + private DebugInformation debugInformation; + private int lineIndex; + private int fileIndex; + private GeneratedLocation location; + private int fileId = -1; + private int line = -1; + private boolean endReached; + private DebugInformation.Layer layer; + + LayerSourceLocationIterator(DebugInformation debugInformation, DebugInformation.Layer layer) { + this.debugInformation = debugInformation; + this.layer = layer; + read(); + } + + public boolean isEndReached() { + return endReached; + } + + private void read() { + if (fileIndex < layer.fileMapping.size() && lineIndex < layer.lineMapping.size()) { + RecordArray.Record fileRecord = layer.fileMapping.get(fileIndex); + RecordArray.Record lineRecord = layer.lineMapping.get(lineIndex); + GeneratedLocation fileLoc = DebugInformation.key(fileRecord); + GeneratedLocation lineLoc = DebugInformation.key(lineRecord); + int cmp = fileLoc.compareTo(lineLoc); + if (cmp < 0) { + nextFileRecord(); + } else if (cmp > 0) { + nextLineRecord(); + } else { + nextFileRecord(); + nextLineRecord(); + } + } else if (fileIndex < layer.fileMapping.size()) { + nextFileRecord(); + } else if (lineIndex < layer.lineMapping.size()) { + nextLineRecord(); + } else if (endReached) { + throw new IllegalStateException("End already reached"); + } else { + endReached = true; + } + } + + private void nextFileRecord() { + RecordArray.Record record = layer.fileMapping.get(fileIndex++); + location = DebugInformation.key(record); + fileId = record.get(2); + } + + private void nextLineRecord() { + RecordArray.Record record = layer.lineMapping.get(lineIndex++); + location = DebugInformation.key(record); + line = record.get(2); + } + + public void next() { + if (isEndReached()) { + throw new IllegalStateException("End already reached"); + } + read(); + } + + public GeneratedLocation getLocation() { + if (isEndReached()) { + throw new IllegalStateException("End already reached"); + } + return location; + } + + public int getFileNameId() { + if (isEndReached()) { + throw new IllegalStateException("End already reached"); + } + return fileId; + } + + public String getFileName() { + int fileId = getFileNameId(); + return fileId >= 0 ? debugInformation.getFileName(fileId) : null; + } + + public int getLine() { + if (isEndReached()) { + throw new IllegalStateException("End already reached"); + } + return line; + } +} diff --git a/core/src/main/java/org/teavm/debugging/information/LineNumberIterator.java b/core/src/main/java/org/teavm/debugging/information/LineNumberIterator.java deleted file mode 100644 index fe828c1f3..000000000 --- a/core/src/main/java/org/teavm/debugging/information/LineNumberIterator.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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; - -public class LineNumberIterator { - private DebugInformation debugInformation; - private int index; - - LineNumberIterator(DebugInformation debugInformation) { - this.debugInformation = debugInformation; - } - - public boolean isEndReached() { - return index < debugInformation.lineMapping.size(); - } - - public GeneratedLocation getLocation() { - if (isEndReached()) { - throw new IllegalStateException("End already reached"); - } - return DebugInformation.key(debugInformation.lineMapping.get(index)); - } - - public int getLineNumber() { - if (isEndReached()) { - throw new IllegalStateException("End already reached"); - } - return debugInformation.lineMapping.get(index).get(2); - } - - public void next() { - if (isEndReached()) { - throw new IllegalStateException("End already reached"); - } - ++index; - } -} diff --git a/core/src/main/java/org/teavm/debugging/information/MethodIterator.java b/core/src/main/java/org/teavm/debugging/information/MethodIterator.java index 34f54b23d..c4991bf4a 100644 --- a/core/src/main/java/org/teavm/debugging/information/MethodIterator.java +++ b/core/src/main/java/org/teavm/debugging/information/MethodIterator.java @@ -15,37 +15,30 @@ */ package org.teavm.debugging.information; -import org.teavm.model.MethodDescriptor; - -public class MethodIterator { - private DebugInformation debugInformation; +class MethodIterator { + private DebugInformation.Layer layer; private int index; - MethodIterator(DebugInformation debugInformation) { - this.debugInformation = debugInformation; + MethodIterator(DebugInformation.Layer layer) { + this.layer = layer; } public boolean isEndReached() { - return index < debugInformation.methodMapping.size(); + return index >= layer.methodMapping.size(); } public GeneratedLocation getLocation() { if (isEndReached()) { throw new IllegalStateException("End already reached"); } - return DebugInformation.key(debugInformation.methodMapping.get(index)); + return DebugInformation.key(layer.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; + return layer.methodMapping.get(index).get(2); } public void next() { diff --git a/core/src/main/java/org/teavm/debugging/information/SourceLocationIterator.java b/core/src/main/java/org/teavm/debugging/information/SourceLocationIterator.java index e937b55eb..feeedcd05 100644 --- a/core/src/main/java/org/teavm/debugging/information/SourceLocationIterator.java +++ b/core/src/main/java/org/teavm/debugging/information/SourceLocationIterator.java @@ -15,85 +15,77 @@ */ package org.teavm.debugging.information; -import org.teavm.common.RecordArray; - public class SourceLocationIterator { private DebugInformation debugInformation; - private int lineIndex; - private int fileIndex; - private GeneratedLocation location; - private int fileId = -1; - private int line = -1; + private LayerIterator layerIterator; + private LayerInfo[] layerSourceIterators; private boolean endReached; + private int currentLayer; + private GeneratedLocation lastLocation; - SourceLocationIterator(DebugInformation debugInformation) { + public SourceLocationIterator(DebugInformation debugInformation) { this.debugInformation = debugInformation; - if (!isEndReached()) { - read(); + layerIterator = new LayerIterator(debugInformation); + layerSourceIterators = new LayerInfo[debugInformation.layerCount()]; + for (int i = 0; i < layerSourceIterators.length; ++i) { + layerSourceIterators[i] = new LayerInfo(new LayerSourceLocationIterator( + debugInformation, debugInformation.layers[i])); } + + if (!layerIterator.isEndReached()) { + currentLayer = layerIterator.getLayer(); + layerIterator.next(); + } else { + currentLayer = 0; + } + + lastLocation = layerSourceIterators[currentLayer].lastLocation; + if (lastLocation == null) { + endReached = true; + } + } + + public GeneratedLocation getLocation() { + return lastLocation; } public boolean isEndReached() { return endReached; } - private void read() { - if (fileIndex < debugInformation.fileMapping.size() && lineIndex < debugInformation.lineMapping.size()) { - RecordArray.Record fileRecord = debugInformation.fileMapping.get(fileIndex); - RecordArray.Record lineRecord = debugInformation.lineMapping.get(lineIndex); - GeneratedLocation fileLoc = DebugInformation.key(fileRecord); - GeneratedLocation lineLoc = DebugInformation.key(lineRecord); - int cmp = fileLoc.compareTo(lineLoc); - if (cmp < 0) { - nextFileRecord(); - } else if (cmp > 0) { - nextLineRecord(); - } else { - nextFileRecord(); - nextLineRecord(); - } - } else if (fileIndex < debugInformation.fileMapping.size()) { - nextFileRecord(); - } else if (lineIndex < debugInformation.lineMapping.size()) { - nextLineRecord(); - } else if (endReached) { - throw new IllegalStateException("End already reached"); - } else { - endReached = true; - } - } - - private void nextFileRecord() { - RecordArray.Record record = debugInformation.fileMapping.get(fileIndex++); - location = DebugInformation.key(record); - fileId = record.get(2); - } - - private void nextLineRecord() { - RecordArray.Record record = debugInformation.lineMapping.get(lineIndex++); - location = DebugInformation.key(record); - line = record.get(2); - } - public void next() { - if (isEndReached()) { - throw new IllegalStateException("End already reached"); + if (endReached) { + throw new IllegalStateException(); } - read(); - } - public GeneratedLocation getLocation() { - if (isEndReached()) { - throw new IllegalStateException("End already reached"); + LayerInfo currentIterator = layerSourceIterators[currentLayer]; + if (currentLayer == 0 && currentIterator.iterator.isEndReached()) { + endReached = true; + return; + } + + if (currentIterator.iterator.isEndReached() || (!layerIterator.isEndReached() + && currentIterator.iterator.getLocation().compareTo(layerIterator.getLocation()) >= 0)) { + currentLayer = layerIterator.getLayer(); + lastLocation = layerIterator.getLocation(); + layerIterator.next(); + + currentIterator = layerSourceIterators[currentLayer]; + while (!currentIterator.iterator.isEndReached() + && currentIterator.iterator.getLocation().compareTo(lastLocation) <= 0) { + currentIterator.next(); + } + } else { + currentIterator.next(); + lastLocation = currentIterator.lastLocation; } - return location; } public int getFileNameId() { if (isEndReached()) { throw new IllegalStateException("End already reached"); } - return fileId; + return layerSourceIterators[currentLayer].lastFileId; } public String getFileName() { @@ -105,6 +97,27 @@ public class SourceLocationIterator { if (isEndReached()) { throw new IllegalStateException("End already reached"); } - return line; + return layerSourceIterators[currentLayer].lastLine; + } + + static class LayerInfo { + LayerSourceLocationIterator iterator; + int lastFileId; + int lastLine; + GeneratedLocation lastLocation; + + LayerInfo(LayerSourceLocationIterator iterator) { + this.iterator = iterator; + if (!iterator.isEndReached()) { + next(); + } + } + + void next() { + this.lastFileId = iterator.getFileNameId(); + this.lastLine = iterator.getLine(); + this.lastLocation = iterator.getLocation(); + iterator.next(); + } } } diff --git a/core/src/main/java/org/teavm/debugging/information/SourceMapsWriter.java b/core/src/main/java/org/teavm/debugging/information/SourceMapsWriter.java index 1b8ae21e0..9bb1555d4 100644 --- a/core/src/main/java/org/teavm/debugging/information/SourceMapsWriter.java +++ b/core/src/main/java/org/teavm/debugging/information/SourceMapsWriter.java @@ -62,7 +62,8 @@ class SourceMapsWriter { output.write("\"}"); } - private void writeSegment(GeneratedLocation loc, int sourceFile, int sourceLine) throws IOException { + private void writeSegment(GeneratedLocation loc, int sourceFile, int sourceLine) + throws IOException { while (loc.getLine() > lastLine) { output.write(';'); ++lastLine; diff --git a/core/src/main/java/org/teavm/model/optimization/Inlining.java b/core/src/main/java/org/teavm/model/optimization/Inlining.java index d00300003..556cca562 100644 --- a/core/src/main/java/org/teavm/model/optimization/Inlining.java +++ b/core/src/main/java/org/teavm/model/optimization/Inlining.java @@ -398,7 +398,7 @@ public class Inlining { entry.targetInstruction = insn; entry.program = invokedProgram; entry.innerPlan.addAll(buildPlan(invokedProgram, depth + 1, innerStep, invokedMethod.getReference(), - inliningInfo)); + innerInliningInfo)); entry.depth = depth; entry.method = invokedMethod.getReference(); entry.locationInfo = innerInliningInfo; diff --git a/core/src/main/java/org/teavm/model/util/LocationGraphBuilder.java b/core/src/main/java/org/teavm/model/util/LocationGraphBuilder.java index 50e5bcd50..fb8428305 100644 --- a/core/src/main/java/org/teavm/model/util/LocationGraphBuilder.java +++ b/core/src/main/java/org/teavm/model/util/LocationGraphBuilder.java @@ -61,7 +61,7 @@ class LocationGraphBuilder { while (!stack.isEmpty()) { Step step = stack.pop(); if (visited[step.block]) { - if (step.location != null) { + if (step.location != null && !step.location.isEmpty()) { additionalConnections.add(new AdditionalConnection(step.location, startLocations.get(step.block))); } continue; @@ -83,14 +83,14 @@ class LocationGraphBuilder { if (blockLocations[step.block] == null) { blockLocations[step.block] = insn.getLocation(); } - if (location != null && !Objects.equals(location, insn.getLocation())) { + if (location != null && !location.isEmpty() && !Objects.equals(location, insn.getLocation())) { addEdge(location, insn.getLocation()); } location = insn.getLocation(); } } if (graph.outgoingEdgesCount(step.block) == 0) { - if (location != null) { + if (location != null && !location.isEmpty()) { addEdge(location, new TextLocation(null, -1)); } } else { diff --git a/tools/devserver/src/main/java/org/teavm/devserver/deobfuscate/Deobfuscator.java b/tools/devserver/src/main/java/org/teavm/devserver/deobfuscate/Deobfuscator.java index e8c1be687..86462efc6 100644 --- a/tools/devserver/src/main/java/org/teavm/devserver/deobfuscate/Deobfuscator.java +++ b/tools/devserver/src/main/java/org/teavm/devserver/deobfuscate/Deobfuscator.java @@ -17,8 +17,11 @@ package org.teavm.devserver.deobfuscate; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; import org.teavm.debugging.information.DebugInformation; +import org.teavm.debugging.information.GeneratedLocation; import org.teavm.debugging.information.SourceLocation; import org.teavm.jso.JSBody; import org.teavm.jso.ajax.XMLHttpRequest; @@ -71,11 +74,12 @@ public final class Deobfuscator { String fileName = groups.get(2).stringValue(); int lineNumber = Integer.parseInt(groups.get(3).stringValue()); int columnNumber = Integer.parseInt(groups.get(4).stringValue()); - Frame frame = deobfuscateFrame(debugInformation, classesFileName, fileName, lineNumber, columnNumber); - if (frame == null) { - frame = createDefaultFrame(fileName, functionName, lineNumber); + List framesPerLine = deobfuscateFrames(debugInformation, classesFileName, fileName, + lineNumber, columnNumber); + if (framesPerLine == null) { + framesPerLine = Arrays.asList(createDefaultFrame(fileName, functionName, lineNumber)); } - frames.add(frame); + frames.addAll(framesPerLine); } return frames.toArray(new Frame[0]); }); @@ -85,32 +89,42 @@ public final class Deobfuscator { } } - private static Frame deobfuscateFrame(DebugInformation debugInformation, String classesFileName, + private static List deobfuscateFrames(DebugInformation debugInformation, String classesFileName, String fileName, int lineNumber, int columnNumber) { if (!fileName.equals(classesFileName)) { return null; } - MethodReference method = debugInformation.getMethodAt(lineNumber - 1, columnNumber - 1); - if (method == null) { + List result = new ArrayList<>(); + + for (int layer = 0; layer < debugInformation.layerCount(); ++layer) { + GeneratedLocation jsLocation = new GeneratedLocation(lineNumber - 1, columnNumber - 1); + MethodReference method = debugInformation.getMethodAt(jsLocation, layer); + if (method == null) { + break; + } + + SourceLocation location = debugInformation.getSourceLocation(jsLocation, layer); + + String decodedFileName = location != null ? location.getFileName() : null; + if (decodedFileName != null) { + decodedFileName = decodedFileName.substring(decodedFileName.lastIndexOf('/') + 1); + } + + Frame frame = createEmptyFrame(); + frame.setClassName(method.getClassName()); + frame.setMethodName(method.getName()); + frame.setFileName(decodedFileName); + if (location != null) { + frame.setLineNumber(location.getLine()); + } + } + + if (result.isEmpty()) { return null; } - - SourceLocation location = debugInformation.getSourceLocation(lineNumber - 1, columnNumber - 1); - - String decodedFileName = location != null ? location.getFileName() : null; - if (decodedFileName != null) { - decodedFileName = decodedFileName.substring(decodedFileName.lastIndexOf('/') + 1); - } - - Frame frame = createEmptyFrame(); - frame.setClassName(method.getClassName()); - frame.setMethodName(method.getName()); - frame.setFileName(decodedFileName); - if (location != null) { - frame.setLineNumber(location.getLine()); - } - return frame; + Collections.reverse(result); + return result; } private static Frame createDefaultFrame(String fileName, String functionName, int lineNumber) { diff --git a/tools/junit/src/main/java/org/teavm/junit/RhinoResultParser.java b/tools/junit/src/main/java/org/teavm/junit/RhinoResultParser.java index d73f543d6..4ba03879f 100644 --- a/tools/junit/src/main/java/org/teavm/junit/RhinoResultParser.java +++ b/tools/junit/src/main/java/org/teavm/junit/RhinoResultParser.java @@ -25,16 +25,18 @@ import java.io.InputStreamReader; import java.io.Reader; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.sourceforge.htmlunit.corejs.javascript.Scriptable; import org.teavm.debugging.information.DebugInformation; +import org.teavm.debugging.information.GeneratedLocation; import org.teavm.debugging.information.SourceLocation; import org.teavm.model.MethodReference; final class RhinoResultParser { - private static Pattern pattern = Pattern.compile("(([A-Za-z_$]+)\\(\\))?@.+:([0-9]+)"); + private static Pattern pattern = Pattern.compile("(([A-Za-z_$][A-Za-z0-9_$]*)\\(\\))?@.+:([0-9]+)"); private static Pattern lineSeparator = Pattern.compile("\\r\\n|\r|\n"); private DebugInformation debugInformation; private String[] script; @@ -70,8 +72,7 @@ final class RhinoResultParser { String stack = result.get("stack", result).toString(); StackTraceElement[] decodedStack = null; if (debugInformation != null) { - List elements = new ArrayList<>(); - elements.addAll(Arrays.asList(decodeStack(stack))); + List elements = new ArrayList<>(Arrays.asList(decodeStack(stack))); List currentElements = Arrays.asList(Thread.currentThread().getStackTrace()); elements.addAll(currentElements.subList(2, currentElements.size())); decodedStack = elements.toArray(new StackTraceElement[0]); @@ -104,34 +105,49 @@ final class RhinoResultParser { } String functionName = matcher.group(2); - int lineNumber = Integer.parseInt(matcher.group(3)) - 1; + int jsLineNumber = Integer.parseInt(matcher.group(3)) - 1; - String scriptLine = script[lineNumber]; - int column = firstNonSpace(scriptLine); - MethodReference method = debugInformation.getMethodAt(lineNumber, column); - String className; - String methodName; + String scriptLine = script[jsLineNumber]; + int jsColumn = firstNonSpace(scriptLine); - if (method != null) { - className = method.getClassName(); - methodName = method.getName(); - } else { - className = ""; - methodName = functionName != null ? functionName : ""; + int layer = 0; + List elementsByLine = new ArrayList<>(); + GeneratedLocation jsLocation = new GeneratedLocation(jsLineNumber, jsColumn); + while (true) { + int lineNumber = jsLineNumber; + + MethodReference method = debugInformation.getMethodAt(jsLocation, layer); + String className; + String methodName; + + if (method != null) { + className = method.getClassName(); + methodName = method.getName(); + } else if (layer > 0) { + break; + } else { + className = ""; + methodName = functionName != null ? functionName : ""; + } + + String fileName; + SourceLocation location = debugInformation.getSourceLocation(jsLocation, layer); + if (location != null && location.getFileName() != null) { + fileName = location.getFileName(); + fileName = fileName.substring(fileName.lastIndexOf('/') + 1); + lineNumber = location.getLine(); + } else { + fileName = "test.js"; + lineNumber++; + } + + elementsByLine.add(new StackTraceElement(className, methodName, fileName, lineNumber)); + + ++layer; } - String fileName; - SourceLocation location = debugInformation.getSourceLocation(lineNumber, column); - if (location != null && location.getFileName() != null) { - fileName = location.getFileName(); - fileName = fileName.substring(fileName.lastIndexOf('/') + 1); - lineNumber = location.getLine(); - } else { - fileName = "test.js"; - lineNumber++; - } - - elements.add(new StackTraceElement(className, methodName, fileName, lineNumber)); + Collections.reverse(elementsByLine); + elements.addAll(elementsByLine); } return elements.toArray(new StackTraceElement[0]); diff --git a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestConfiguration.java b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestConfiguration.java index b80e1260f..59459088c 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestConfiguration.java +++ b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestConfiguration.java @@ -37,7 +37,7 @@ interface TeaVMTestConfiguration { @Override public void apply(TeaVM vm) { - vm.setOptimizationLevel(TeaVMOptimizationLevel.FULL); + vm.setOptimizationLevel(TeaVMOptimizationLevel.SIMPLE); } @Override