Fix various issues in debugger

This commit is contained in:
Alexey Andreev 2017-07-02 16:25:11 +03:00
parent 46ebc2d694
commit db97b7f732
16 changed files with 406 additions and 199 deletions

View File

@ -29,7 +29,6 @@
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JAVA">
<option name="KEEP_LINE_BREAKS" value="false" />
<option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />

View File

@ -40,7 +40,7 @@ script:
- pushd tests/src/test/js
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
- sleep 3
- sleep 10
- firefox index.html &
- FIREFOX_PID=$!
- node start.js $BASE_PATH/tests/target/js-tests

View File

@ -91,14 +91,16 @@ public class Decompiler {
private List<TryCatchBookmark> tryCatchBookmarks = new ArrayList<>();
private Deque<Block> stack;
private Program program;
private boolean friendlyToDebugger;
public Decompiler(ClassHolderSource classSource, ClassLoader classLoader, Set<MethodReference> asyncMethods,
Set<MethodReference> asyncFamilyMethods) {
Set<MethodReference> asyncFamilyMethods, boolean friendlyToDebugger) {
this.classSource = classSource;
this.classLoader = classLoader;
this.asyncMethods = asyncMethods;
splitMethods.addAll(asyncMethods);
splitMethods.addAll(asyncFamilyMethods);
this.friendlyToDebugger = friendlyToDebugger;
}
public MethodNodeCache getRegularMethodCache() {
@ -275,7 +277,7 @@ public class Decompiler {
}
Optimizer optimizer = new Optimizer();
optimizer.optimize(methodNode, method.getProgram());
optimizer.optimize(methodNode, method.getProgram(), friendlyToDebugger);
methodNode.getModifiers().addAll(method.getModifiers());
return methodNode;
@ -339,7 +341,7 @@ public class Decompiler {
}
Optimizer optimizer = new Optimizer();
optimizer.optimize(node, splitter);
optimizer.optimize(node, splitter, friendlyToDebugger);
node.getModifiers().addAll(method.getModifiers());
return node;

View File

@ -18,7 +18,9 @@ package org.teavm.ast.optimization;
import java.util.BitSet;
import org.teavm.ast.AsyncMethodNode;
import org.teavm.ast.AsyncMethodPart;
import org.teavm.ast.MethodNode;
import org.teavm.ast.RegularMethodNode;
import org.teavm.ast.VariableNode;
import org.teavm.common.Graph;
import org.teavm.model.BasicBlock;
import org.teavm.model.Instruction;
@ -31,13 +33,14 @@ import org.teavm.model.util.ProgramUtils;
import org.teavm.model.util.UsageExtractor;
public class Optimizer {
public void optimize(RegularMethodNode method, Program program) {
public void optimize(RegularMethodNode method, Program program, boolean friendlyToDebugger) {
ReadWriteStatsBuilder stats = new ReadWriteStatsBuilder(method.getVariables().size());
stats.analyze(program);
boolean[] preservedVars = new boolean[stats.writes.length];
BreakEliminator breakEliminator = new BreakEliminator();
breakEliminator.eliminate(method.getBody());
OptimizingVisitor optimizer = new OptimizingVisitor(preservedVars, stats.writes, stats.reads);
OptimizingVisitor optimizer = new OptimizingVisitor(preservedVars, stats.writes, stats.reads,
friendlyToDebugger);
method.getBody().acceptVisitor(optimizer);
method.setBody(optimizer.resultStmt);
int paramCount = method.getReference().parameterCount();
@ -55,7 +58,7 @@ public class Optimizer {
}
}
public void optimize(AsyncMethodNode method, AsyncProgramSplitter splitter) {
public void optimize(AsyncMethodNode method, AsyncProgramSplitter splitter, boolean friendlyToDebugger) {
LivenessAnalyzer liveness = new LivenessAnalyzer();
liveness.analyze(splitter.getOriginalProgram());
@ -70,7 +73,8 @@ public class Optimizer {
BreakEliminator breakEliminator = new BreakEliminator();
breakEliminator.eliminate(part.getStatement());
findEscapingLiveVars(liveness, cfg, splitter, i, preservedVars);
OptimizingVisitor optimizer = new OptimizingVisitor(preservedVars, stats.writes, stats.reads);
OptimizingVisitor optimizer = new OptimizingVisitor(preservedVars, stats.writes, stats.reads,
friendlyToDebugger);
part.getStatement().acceptVisitor(optimizer);
part.setStatement(optimizer.resultStmt);
}
@ -93,6 +97,14 @@ public class Optimizer {
}
}
private void preserveDebuggableVars(boolean[] variablesToPreserve, MethodNode methodNode) {
for (VariableNode varNode : methodNode.getVariables()) {
if (varNode.getName() != null) {
variablesToPreserve[varNode.getIndex()] = true;
}
}
}
private void findEscapingLiveVars(LivenessAnalyzer liveness, Graph cfg, AsyncProgramSplitter splitter,
int partIndex, boolean[] output) {
Program originalProgram = splitter.getOriginalProgram();

View File

@ -15,8 +15,11 @@
*/
package org.teavm.ast.optimization;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@ -58,6 +61,7 @@ import org.teavm.ast.UnaryOperation;
import org.teavm.ast.UnwrapArrayExpr;
import org.teavm.ast.VariableExpr;
import org.teavm.ast.WhileStatement;
import org.teavm.model.TextLocation;
class OptimizingVisitor implements StatementVisitor, ExprVisitor {
private Expr resultExpr;
@ -66,11 +70,17 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
private final int[] writeFrequencies;
private final int[] readFrequencies;
private List<Statement> resultSequence;
private boolean friendlyToDebugger;
private TextLocation currentLocation;
private Deque<TextLocation> locationStack = new LinkedList<>();
private Deque<TextLocation> notNullLocationStack = new ArrayDeque<>();
OptimizingVisitor(boolean[] preservedVars, int[] writeFrequencies, int[] readFrequencies) {
OptimizingVisitor(boolean[] preservedVars, int[] writeFrequencies, int[] readFrequencies,
boolean friendlyToDebugger) {
this.preservedVars = preservedVars;
this.writeFrequencies = writeFrequencies;
this.readFrequencies = readFrequencies;
this.friendlyToDebugger = friendlyToDebugger;
}
private static boolean isZero(Expr expr) {
@ -81,8 +91,26 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
return expr instanceof BinaryExpr && ((BinaryExpr) expr).getOperation() == BinaryOperation.COMPARE;
}
private void pushLocation(TextLocation location) {
locationStack.push(location);
if (location != null) {
if (currentLocation != null) {
notNullLocationStack.push(currentLocation);
}
currentLocation = location;
}
}
private void popLocation() {
if (locationStack.pop() != null) {
currentLocation = notNullLocationStack.pollFirst();
}
}
@Override
public void visit(BinaryExpr expr) {
pushLocation(expr.getLocation());
try {
switch (expr.getOperation()) {
case AND:
case OR:
@ -134,10 +162,15 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
expr.setFirstOperand(a);
expr.setSecondOperand(b);
resultExpr = expr;
} finally {
popLocation();
}
}
@Override
public void visit(UnaryExpr expr) {
pushLocation(expr.getLocation());
try {
expr.getOperand().acceptVisitor(this);
Expr operand = resultExpr;
if (expr.getOperation() == UnaryOperation.NEGATE && operand instanceof ConstantExpr) {
@ -149,6 +182,9 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
}
expr.setOperand(operand);
resultExpr = expr;
} finally {
popLocation();
}
}
private boolean tryMakePositive(ConstantExpr constantExpr) {
@ -177,6 +213,8 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
@Override
public void visit(ConditionalExpr expr) {
pushLocation(expr.getLocation());
try {
expr.getCondition().acceptVisitor(this);
Expr cond = resultExpr;
expr.getConsequent().acceptVisitor(this);
@ -187,6 +225,9 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
expr.setConsequent(consequent);
expr.setAlternative(alternative);
resultExpr = expr;
} finally {
popLocation();
}
}
@Override
@ -196,6 +237,8 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
@Override
public void visit(VariableExpr expr) {
pushLocation(expr.getLocation());
try {
int index = expr.getIndex();
resultExpr = expr;
if (writeFrequencies[index] != 1) {
@ -219,19 +262,26 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
return;
}
VariableExpr var = (VariableExpr) assignment.getLeftValue();
if (var.getLocation() != null && assignment.getLocation() != null
&& !assignment.getLocation().equals(var.getLocation())) {
if (friendlyToDebugger) {
if (currentLocation != null && assignment.getLocation() != null
&& !assignment.getLocation().equals(currentLocation)) {
return;
}
}
if (var.getIndex() == index) {
resultSequence.remove(resultSequence.size() - 1);
assignment.getRightValue().setLocation(assignment.getLocation());
assignment.getRightValue().acceptVisitor(this);
}
} finally {
popLocation();
}
}
@Override
public void visit(SubscriptExpr expr) {
pushLocation(expr.getLocation());
try {
expr.getIndex().acceptVisitor(this);
Expr index = resultExpr;
expr.getArray().acceptVisitor(this);
@ -239,18 +289,28 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
expr.setArray(array);
expr.setIndex(index);
resultExpr = expr;
} finally {
popLocation();
}
}
@Override
public void visit(UnwrapArrayExpr expr) {
pushLocation(expr.getLocation());
try {
expr.getArray().acceptVisitor(this);
Expr arrayExpr = resultExpr;
expr.setArray(arrayExpr);
resultExpr = expr;
} finally {
popLocation();
}
}
@Override
public void visit(InvocationExpr expr) {
pushLocation(expr.getLocation());
try {
Expr[] args = new Expr[expr.getArguments().size()];
for (int i = expr.getArguments().size() - 1; i >= 0; --i) {
expr.getArguments().get(i).acceptVisitor(this);
@ -260,6 +320,9 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
expr.getArguments().set(i, args[i]);
}
resultExpr = expr;
} finally {
popLocation();
}
}
private boolean tryApplyConstructor(InvocationExpr expr) {
@ -303,12 +366,17 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
@Override
public void visit(QualificationExpr expr) {
pushLocation(expr.getLocation());
try {
if (expr.getQualified() != null) {
expr.getQualified().acceptVisitor(this);
Expr qualified = resultExpr;
expr.setQualified(qualified);
}
resultExpr = expr;
} finally {
popLocation();
}
}
@Override
@ -318,45 +386,72 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
@Override
public void visit(NewArrayExpr expr) {
pushLocation(expr.getLocation());
try {
expr.getLength().acceptVisitor(this);
Expr length = resultExpr;
expr.setLength(length);
resultExpr = expr;
} finally {
popLocation();
}
}
@Override
public void visit(NewMultiArrayExpr expr) {
pushLocation(expr.getLocation());
try {
for (int i = 0; i < expr.getDimensions().size(); ++i) {
Expr dimension = expr.getDimensions().get(i);
dimension.acceptVisitor(this);
expr.getDimensions().set(i, resultExpr);
}
resultExpr = expr;
} finally {
popLocation();
}
}
@Override
public void visit(InstanceOfExpr expr) {
pushLocation(expr.getLocation());
try {
expr.getExpr().acceptVisitor(this);
expr.setExpr(resultExpr);
resultExpr = expr;
} finally {
popLocation();
}
}
@Override
public void visit(CastExpr expr) {
pushLocation(expr.getLocation());
try {
expr.getValue().acceptVisitor(this);
expr.setValue(resultExpr);
resultExpr = expr;
} finally {
popLocation();
}
}
@Override
public void visit(PrimitiveCastExpr expr) {
pushLocation(expr.getLocation());
try {
expr.getValue().acceptVisitor(this);
expr.setValue(resultExpr);
resultExpr = expr;
} finally {
popLocation();
}
}
@Override
public void visit(AssignmentStatement statement) {
pushLocation(statement.getLocation());
try {
if (statement.getLeftValue() == null) {
statement.getRightValue().acceptVisitor(this);
if (resultExpr instanceof InvocationExpr && tryApplyConstructor((InvocationExpr) resultExpr)) {
@ -377,6 +472,9 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
statement.setRightValue(right);
resultStmt = statement;
}
} finally {
popLocation();
}
}
private List<Statement> processSequence(List<Statement> statements) {
@ -724,23 +822,38 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
@Override
public void visit(ReturnStatement statement) {
pushLocation(statement.getLocation());
try {
if (statement.getResult() != null) {
statement.getResult().acceptVisitor(this);
statement.setResult(resultExpr);
}
resultStmt = statement;
} finally {
popLocation();
}
}
@Override
public void visit(ThrowStatement statement) {
pushLocation(statement.getLocation());
try {
statement.getException().acceptVisitor(this);
statement.setException(resultExpr);
resultStmt = statement;
} finally {
popLocation();
}
}
@Override
public void visit(InitClassStatement statement) {
pushLocation(statement.getLocation());
try {
resultStmt = statement;
} finally {
popLocation();
}
}
@Override
@ -761,15 +874,25 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
@Override
public void visit(MonitorEnterStatement statement) {
pushLocation(statement.getLocation());
try {
statement.getObjectRef().acceptVisitor(this);
statement.setObjectRef(resultExpr);
resultStmt = statement;
} finally {
popLocation();
}
}
@Override
public void visit(MonitorExitStatement statement) {
pushLocation(statement.getLocation());
try {
statement.getObjectRef().acceptVisitor(this);
statement.setObjectRef(resultExpr);
resultStmt = statement;
} finally {
popLocation();
}
}
}

View File

@ -294,7 +294,8 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
asyncMethods.addAll(asyncFinder.getAsyncMethods());
asyncFamilyMethods.addAll(asyncFinder.getAsyncFamilyMethods());
Decompiler decompiler = new Decompiler(classes, controller.getClassLoader(), asyncMethods, asyncFamilyMethods);
Decompiler decompiler = new Decompiler(classes, controller.getClassLoader(), asyncMethods, asyncFamilyMethods,
controller.isFriendlyToDebugger());
decompiler.setRegularMethodCache(controller.isIncremental() ? astCache : null);
for (Map.Entry<MethodReference, Generator> entry : methodGenerators.entrySet()) {

View File

@ -926,7 +926,7 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor {
break;
}
if (expr.getLocation() != null) {
pushLocation(expr.getLocation());
popLocation();
}
} catch (IOException e) {
throw new RenderingException("IO error occured", e);

View File

@ -291,7 +291,7 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost {
classes, vtableProvider, tagRegistry, binaryWriter);
Decompiler decompiler = new Decompiler(classes, controller.getClassLoader(), new HashSet<>(),
new HashSet<>());
new HashSet<>(), false);
WasmStringPool stringPool = classGenerator.getStringPool();
WasmGenerationContext context = new WasmGenerationContext(classes, module, controller.getDiagnostics(),
vtableProvider, tagRegistry, stringPool);

View File

@ -34,8 +34,8 @@ public class Debugger {
private ConcurrentMap<String, ConcurrentMap<DebugInformation, Object>> debugInformationFileMap =
new ConcurrentHashMap<>();
private ConcurrentMap<DebugInformation, String> scriptMap = new ConcurrentHashMap<>();
final ConcurrentMap<JavaScriptBreakpoint, Breakpoint> breakpointMap = new ConcurrentHashMap<>();
ConcurrentMap<Breakpoint, Object> breakpoints = new ConcurrentHashMap<>();
private final ConcurrentMap<JavaScriptBreakpoint, Breakpoint> breakpointMap = new ConcurrentHashMap<>();
private final ConcurrentMap<Breakpoint, Object> breakpoints = new ConcurrentHashMap<>();
private volatile CallFrame[] callStack;
public Debugger(JavaScriptDebugger javaScriptDebugger, DebugInformationProvider debugInformationProvider) {
@ -126,12 +126,12 @@ public class Debugger {
javaScriptDebugger.resume();
}
private static class CallSiteSuccessorFinder implements DebuggerCallSiteVisitor {
static class CallSiteSuccessorFinder implements DebuggerCallSiteVisitor {
private DebugInformation debugInfo;
private String script;
Set<JavaScriptLocation> locations;
public CallSiteSuccessorFinder(DebugInformation debugInfo, String script, Set<JavaScriptLocation> locations) {
CallSiteSuccessorFinder(DebugInformation debugInfo, String script, Set<JavaScriptLocation> locations) {
this.debugInfo = debugInfo;
this.script = script;
this.locations = locations;
@ -185,7 +185,7 @@ public class Debugger {
private List<DebugInformation> debugInformationBySource(String sourceFile) {
Map<DebugInformation, Object> list = debugInformationFileMap.get(sourceFile);
return list != null ? new ArrayList<>(list.keySet()) : Collections.<DebugInformation>emptyList();
return list != null ? new ArrayList<>(list.keySet()) : Collections.emptyList();
}
public void continueToLocation(SourceLocation location) {
@ -218,6 +218,10 @@ public class Debugger {
return createBreakpoint(new SourceLocation(file, line));
}
public Collection<? extends String> getSourceFiles() {
return debugInformationFileMap.keySet();
}
public Breakpoint createBreakpoint(SourceLocation location) {
synchronized (breakpointMap) {
Breakpoint breakpoint = new Breakpoint(this, location);
@ -232,7 +236,7 @@ public class Debugger {
return new HashSet<>(breakpoints.keySet());
}
void updateInternalBreakpoints(Breakpoint breakpoint) {
private void updateInternalBreakpoints(Breakpoint breakpoint) {
if (breakpoint.isDestroyed()) {
return;
}
@ -259,7 +263,7 @@ public class Debugger {
return listeners.keySet().toArray(new DebuggerListener[0]);
}
void updateBreakpointStatus(Breakpoint breakpoint, boolean fireEvent) {
private void updateBreakpointStatus(Breakpoint breakpoint, boolean fireEvent) {
boolean valid = false;
for (JavaScriptBreakpoint jsBreakpoint : breakpoint.jsBreakpoints) {
if (jsBreakpoint.isValid()) {

View File

@ -63,6 +63,9 @@ class VariableMap extends AbstractMap<String, Variable> {
String[] names = debugger.mapVariable(entry.getKey(), location);
Value value = new Value(debugger, jsVar.getValue());
for (String name : names) {
if (name == null) {
name = "js:" + jsVar.getName();
}
vars.put(name, new Variable(name, value));
}
}

View File

@ -216,7 +216,7 @@ public class DebugInformation {
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]];
result[i] = valueIndexes[i] >= 0 ? variableNames[valueIndexes[i]] : null;
}
return result;
}

View File

@ -132,11 +132,8 @@ public class DebugInformationBuilder implements DebugInformationEmitter {
}
Arrays.sort(sourceIndexes);
int generatedIndex = variableNames.index(generatedName);
RecordArrayBuilder mapping = variableMappings.get(generatedIndex);
if (mapping == null) {
mapping = new RecordArrayBuilder(2, 1);
variableMappings.put(generatedIndex, mapping);
}
RecordArrayBuilder mapping = variableMappings.computeIfAbsent(generatedIndex,
k -> new RecordArrayBuilder(2, 1));
RecordArrayBuilder.Record record = add(mapping);
RecordArrayBuilder.SubArray array = record.getArray(0);

View File

@ -661,5 +661,10 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
public Map<String, String> getExportedClasses() {
return readonlyExportedClasses;
}
@Override
public boolean isFriendlyToDebugger() {
return optimizationLevel == TeaVMOptimizationLevel.SIMPLE;
}
};
}

View File

@ -39,6 +39,8 @@ public interface TeaVMTargetController {
boolean isIncremental();
boolean isFriendlyToDebugger();
Map<String, TeaVMEntryPoint> getEntryPoints();
Map<String, String> getExportedClasses();

View File

@ -24,6 +24,8 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@ -119,7 +121,12 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
}
}
CompletableFuture<Object> future = futures.remove(response.getId());
try {
responseHandlers.remove(response.getId()).received(response.getResult(), future);
} catch (RuntimeException e) {
logger.warn("Error processing message ${}", response.getId(), e);
future.completeExceptionally(e);
}
} else {
Message message = mapper.reader(Message.class).readValue(messageText);
if (message.getMethod() == null) {
@ -386,7 +393,7 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
try {
return read(sync);
} catch (InterruptedException e) {
} catch (InterruptedException | TimeoutException e) {
return Collections.emptyList();
}
}
@ -421,6 +428,8 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
return result.isEmpty() ? null : result;
} catch (InterruptedException e) {
return null;
} catch (TimeoutException e) {
return "<timed out>";
}
}
@ -454,6 +463,8 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
return result.repr;
} catch (InterruptedException e) {
return null;
} catch (TimeoutException e) {
return "<timed out>";
}
}
@ -559,9 +570,9 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
void received(JsonNode node, CompletableFuture<T> out) throws IOException;
}
private static <T> T read(Future<T> future) throws InterruptedException {
private static <T> T read(Future<T> future) throws InterruptedException, TimeoutException {
try {
return future.get();
return future.get(1500, TimeUnit.MILLISECONDS);
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {

View File

@ -51,6 +51,11 @@ public final class ChromeRDPRunner {
new Thread(server::start).start();
debugger = new Debugger(jsDebugger, new URLDebugInformationProvider(""));
debugger.addListener(listener);
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
System.err.println("Uncaught exception in thread " + t);
e.printStackTrace();
});
}
private DebuggerListener listener = new DebuggerListener() {
@ -221,12 +226,55 @@ public final class ChromeRDPRunner {
System.out.println("Expected 2 arguments");
return;
}
Breakpoint bp = debugger.createBreakpoint(args[1], Integer.parseInt(args[2]));
String[] fileNames = resolveFileName(args[1]);
if (fileNames.length == 0) {
System.out.println("Unknown file: " + args[1]);
return;
} else if (fileNames.length > 1) {
System.out.println("Ambiguous file name: " + args[1] + ". Possible names are: "
+ Arrays.toString(fileNames));
return;
}
Breakpoint bp = debugger.createBreakpoint(fileNames[0], Integer.parseInt(args[2]));
int id = breakpointIdGen++;
breakpointIds.put(bp, id);
System.out.println("Breakpoint #" + id + " was set at " + bp.getLocation());
};
private String[] resolveFileName(String fileName) {
if (debugger.getSourceFiles().contains(fileName)) {
return new String[] { fileName };
}
String[] result = debugger.getSourceFiles().stream()
.filter(f -> f.endsWith(fileName) && isPrecededByPathSeparator(f, fileName))
.toArray(String[]::new);
if (result.length == 1) {
return result;
}
return debugger.getSourceFiles().stream()
.filter(f -> {
int index = f.lastIndexOf('.');
if (index <= 0) {
return false;
}
String nameWithoutExt = f.substring(0, index);
return nameWithoutExt.endsWith(fileName) && isPrecededByPathSeparator(nameWithoutExt, fileName);
})
.toArray(String[]::new);
}
private static boolean isPrecededByPathSeparator(String actualName, String specifiedName) {
if (actualName.length() < specifiedName.length() + 1) {
return false;
}
char c = actualName.charAt(actualName.length() - specifiedName.length() - 1);
return c == '/' || c == '\\';
}
private Command backtraceCommand = args -> {
CallFrame[] callStack = debugger.getCallStack();
for (int i = 0; i < callStack.length; ++i) {