blob: c422ba2d768ddf396baa3a3af7c9a57ff921dca9 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.netbeans.modules.debugger.jpda;
import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.IncompatibleThreadStateException;
import com.sun.jdi.Location;
import com.sun.jdi.Method;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.StackFrame;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.VirtualMachine;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.netbeans.api.debugger.DebuggerManager;
import org.netbeans.api.debugger.Session;
import org.netbeans.modules.debugger.jpda.jdi.IllegalThreadStateExceptionWrapper;
import org.netbeans.modules.debugger.jpda.jdi.InternalExceptionWrapper;
import org.netbeans.modules.debugger.jpda.jdi.InvalidStackFrameExceptionWrapper;
import org.netbeans.modules.debugger.jpda.jdi.LocationWrapper;
import org.netbeans.modules.debugger.jpda.jdi.MethodWrapper;
import org.netbeans.modules.debugger.jpda.jdi.MirrorWrapper;
import org.netbeans.modules.debugger.jpda.jdi.ObjectCollectedExceptionWrapper;
import org.netbeans.modules.debugger.jpda.jdi.ReferenceTypeWrapper;
import org.netbeans.modules.debugger.jpda.jdi.StackFrameWrapper;
import org.netbeans.modules.debugger.jpda.jdi.ThreadReferenceWrapper;
import org.netbeans.modules.debugger.jpda.jdi.VMDisconnectedExceptionWrapper;
import org.netbeans.modules.debugger.jpda.jdi.VirtualMachineWrapper;
import org.netbeans.spi.debugger.jpda.EditorContext;
import org.netbeans.spi.debugger.jpda.EditorContext.Operation;
/**
* The pool of operations, which are used for expression stepping.
*
* @author Martin Entlicher
*/
public class ExpressionPool {
private static Logger logger = Logger.getLogger("org.netbeans.modules.debugger.jpda.step"); // NOI18N
private Map<ExpressionLocation, Expression> expressions = new HashMap<ExpressionLocation, Expression>();
/**
* Creates a new instance of ExpressionPool
*/
ExpressionPool() {
}
public synchronized Expression getExpressionAt(Location loc, String url) {
try {
ExpressionLocation exprLocation = new ExpressionLocation(LocationWrapper.method(loc), LocationWrapper.lineNumber(loc));
if (!expressions.containsKey(exprLocation)) {
LinkedHashSet<Location> lineLocationsInExpression = new LinkedHashSet<Location>();
lineLocationsInExpression.add(loc);
Expression expr = createExpressionAt(loc, url, lineLocationsInExpression);
expressions.put(exprLocation, expr);
// Add the rest of in-expression locations:
Iterator<Location> locIt = lineLocationsInExpression.iterator();
locIt.next(); // Skip the first one
while (locIt.hasNext()) {
loc = locIt.next();
exprLocation = new ExpressionLocation(LocationWrapper.method(loc), LocationWrapper.lineNumber(loc));
expressions.put(exprLocation, expr);
}
}
return expressions.get(exprLocation);
} catch (InternalExceptionWrapper ex) {
logger.log(Level.INFO, "No expression at '"+url+"', JDI internal error", ex);
return null;
} catch (ObjectCollectedExceptionWrapper ex) {
return null;
} catch (VMDisconnectedExceptionWrapper ex) {
return null;
}
}
// TODO: Clean unnecessray expressions:
/*
public synchronized void removeExpressionAt(Location loc) {
expressions.remove(new ExpressionLocation(loc.method(), loc.lineNumber()));
}
*/
public void cleanUnusedExpressions(ThreadReference thr) {
synchronized (this) {
if (expressions.size() == 0) {
return ;
}
}
List<StackFrame> stackFrames;
try {
stackFrames = ThreadReferenceWrapper.frames(thr);
synchronized (this) {
for (Iterator<ExpressionLocation> locIt = expressions.keySet().iterator(); locIt.hasNext(); ) {
ExpressionLocation exprLoc = locIt.next();
// TODO: Check the correct thread.
Method method = exprLoc.getMethod();
//int line = exprLoc.getLine();
for (Iterator<StackFrame> it = stackFrames.iterator(); it.hasNext(); ) {
StackFrame sf = it.next();
if (method.equals(LocationWrapper.method(StackFrameWrapper.location(sf)))) {
//&& line == sf.location().lineNumber()) {
method = null;
break;
}
}
if (method != null) {
locIt.remove();
}
}
}
} catch (InternalExceptionWrapper ex) {
} catch (ObjectCollectedExceptionWrapper ex) {
} catch (VMDisconnectedExceptionWrapper ex) {
} catch (IncompatibleThreadStateException ex) {
} catch (IllegalThreadStateExceptionWrapper ex) {
} catch (InvalidStackFrameExceptionWrapper ex) {
// Ignore
}
}
/**
* Clean cached expressions.
*/
synchronized void clear() {
expressions.clear();
}
private Expression createExpressionAt(final Location loc, final String url,
final Set<Location> lineLocationsInExpression)
throws InternalExceptionWrapper, VMDisconnectedExceptionWrapper, ObjectCollectedExceptionWrapper {
VirtualMachine vm = MirrorWrapper.virtualMachine(loc);
if (!VirtualMachineWrapper.canGetBytecodes(vm)) {
// Can not analyze expressions without bytecode
return null;
}
ReferenceType clazzType = LocationWrapper.declaringType(loc);
final Method method = LocationWrapper.method(loc);
final byte[] bytecodes = MethodWrapper.bytecodes(method);
byte[] constantPool = null;
if (VirtualMachineWrapper.canGetConstantPool(vm)) {
constantPool = ReferenceTypeWrapper.constantPool(clazzType);
}
final byte[] theConstantPool = constantPool;
Session currentSession = DebuggerManager.getDebuggerManager().getCurrentSession();
final String language = currentSession == null ? null : currentSession.getCurrentLanguage();
int line = LocationWrapper.lineNumber(loc, language);
final List<Location> methodLocations;
try {
methodLocations = MethodWrapper.allLineLocations(method, language, null);
} catch (AbsentInformationException aiex) {
logger.log(Level.FINE, aiex.getLocalizedMessage());
return null;
}
final int[] boundingLines = new int[2];
final int[][] codeIndexIntervalsPtr = new int[1][];
Operation[] ops = EditorContextBridge.getContext().getOperations(
url, line, new EditorContext.BytecodeProvider() {
public byte[] constantPool() {
return theConstantPool;
}
public byte[] byteCodes() {
return bytecodes;
}
public int[] indexAtLines(int startLine, int endLine) {
int[] indexes = getIndexesAtLines(methodLocations, language, startLine, endLine,
bytecodes.length, lineLocationsInExpression);
boundingLines[0] = startLine;
boundingLines[1] = endLine;
codeIndexIntervalsPtr[0] = indexes;
return indexes;
}
});
logger.fine("Operations:");
if (ops == null) {
logger.log(Level.FINE, "Unsuccessfull bytecode matching.");
return null;
}
if (ops.length == 0) { // No operations - do a line step instead
return null;
}
if (logger.isLoggable(Level.FINE)) {
for (Operation op : ops) {
logger.fine(" "+op.getMethodName()+"():"+op.getMethodStartPosition().getLine()+", bci = "+op.getBytecodeIndex());
}
}
Location[] locations = new Location[ops.length];
for (int i = 0; i < ops.length; i++) {
int codeIndex = ops[i].getBytecodeIndex();
locations[i] = MethodWrapper.locationOfCodeIndex(method, codeIndex);
if (locations[i] == null) {
logger.log(Level.FINE, "Location of the operation not found.");
return null;
}
}
Expression expr = new Expression(new ExpressionLocation(method, line), ops, locations,
new Interval(boundingLines[0], boundingLines[1]),
codeIndexIntervalsPtr[0]);
return expr;
}
/**
*
* @param allLocations all locations in the method
* @param language
* @param startLine expression start line
* @param endLine expression end line
* @param methodEndIndex the last code index in the method + 1
* @return pairs of code indexes on individual lines.
* Every pair is an interval of code indexes on the respective lines.
*/
private static int[] getIndexesAtLines(List<Location> allLocations, String language,
int startLine, int endLine, int methodEndIndex,
Set<Location> lineLocationsInExpression) {
int startlocline = 0;
int endlocline;
try {
Location startLocation;
int firstLine = LocationWrapper.lineNumber(allLocations.get(0), language);
do {
startLocation = getLocationOfLine(allLocations, language, startLine - startlocline++);
} while (startLocation == null && (startLine - (startlocline - 1)) >= firstLine);
} catch (VMDisconnectedExceptionWrapper e) {
return null;
} catch (InternalExceptionWrapper e) {
return null;
}
startLine -= (startlocline - 1);
if (endLine > startLine) {
endlocline = 0;
} else {
endlocline = 1;
}
endLine += endlocline;
List<int[]> indexes = new ArrayList<int[]>();
int startIndex = -1;
Location locInExpression = null;
try {
for (Location l : allLocations) {
int line = LocationWrapper.lineNumber(l, language);
if (startIndex == -1 && startLine <= line && line < endLine) {
startIndex = (int) LocationWrapper.codeIndex(l);
locInExpression = l;
} else if (startIndex >= 0) {
int ci = (int) LocationWrapper.codeIndex(l);
lineLocationsInExpression.add(locInExpression);
if (startLine <= line && line <= (endLine - endlocline)) {
indexes.add(new int[] { startIndex, ci });
startIndex = ci;
locInExpression = l;
} else {
indexes.add(new int[] { startIndex, ci });
startIndex = -1;
locInExpression = null;
}
}
}
} catch (VMDisconnectedExceptionWrapper e) {
return null;
} catch (InternalExceptionWrapper e) {
return null;
}
if (indexes.size() == 0) {
if (startIndex >= 0) {
// End of the method
if (logger.isLoggable(Level.FINE)) {
logger.fine("getIndexesAtLines("+startLine+", "+endLine+") = "+
Arrays.toString(new int[] { startIndex, methodEndIndex }));
}
lineLocationsInExpression.add(locInExpression);
return new int[] { startIndex, methodEndIndex };
}
return null;
} else if (indexes.size() == 1) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("getIndexesAtLines("+startLine+", "+endLine+") = "+
Arrays.toString(indexes.get(0)));
}
return indexes.get(0);
} else {
int[] arr = new int[2*indexes.size()];
for (int i = 0; i < indexes.size(); i++) {
arr[2*i] = indexes.get(i)[0];
arr[2*i + 1] = indexes.get(i)[1];
}
if (logger.isLoggable(Level.FINE)) {
logger.fine("getIndexesAtLines("+startLine+", "+endLine+") = "+
Arrays.toString(arr));
}
return arr;
}
}
private static Location getLocationOfLine(List<Location> allLocations, String language, int line) throws InternalExceptionWrapper, VMDisconnectedExceptionWrapper {
for (Location l : allLocations) {
if (LocationWrapper.lineNumber(l, language) == line) {
return l;
}
}
return null;
}
//private int[] singleIndexHolder = new int[1]; // Perf. optimization only
public static final class Expression {
private ExpressionLocation location;
private Operation[] operations;
private Location[] locations;
private Interval lines;
private int[] codeIndexIntervals;
Expression(ExpressionLocation location, Operation[] operations, Location[] locations,
Interval lines, int[] codeIndexIntervals) {
this.location = location;
this.operations = operations;
this.locations = locations;
this.lines = lines;
this.codeIndexIntervals = codeIndexIntervals;
}
public Operation[] getOperations() {
return operations;
}
public Location[] getLocations() {
return locations;
}
public Interval getInterval() {
return lines;
}
int[] getCodeIndexIntervals() {
return codeIndexIntervals;
}
public int findNextOperationIndex(int codeIndex) {
for (int i = 0; i < operations.length; i++) {
int operationIndex = operations[i].getBytecodeIndex();
if (operationIndex > codeIndex) {
return i;
}
}
return -1;
}
int[] findNextOperationIndexes(int codeIndex) {
for (int i = 0; i < operations.length; i++) {
int operationIndex = operations[i].getBytecodeIndex();
if (operationIndex == codeIndex) {
List<Operation> nextOperations = operations[i].getNextOperations();
if (!nextOperations.isEmpty()) {
int l = nextOperations.size();
int[] indexes = new int[l];
for (int ni = 0; ni < l; ni++) {
Operation op = nextOperations.get(ni);
int j;
for (j = 0; j < operations.length; j++) {
if (op == operations[j]) break;
}
if (j < operations.length) {
indexes[ni] = j;
} else {
indexes[ni] = -1;
}
}
return indexes;
}
}
if (operationIndex > codeIndex) {
return new int[] { i };
}
}
return null;
}
OperationLocation[] findNextOperationLocations(int codeIndex) {
for (int i = 0; i < operations.length; i++) {
int operationIndex = operations[i].getBytecodeIndex();
if (operationIndex == codeIndex) {
List<Operation> nextOperations = operations[i].getNextOperations();
if (!nextOperations.isEmpty()) {
int l = nextOperations.size();
OperationLocation[] opLocations = new OperationLocation[l];
for (int ni = 0; ni < l; ni++) {
Operation op = nextOperations.get(ni);
int j;
for (j = 0; j < operations.length; j++) {
if (op == operations[j]) break;
}
if (j < operations.length) {
opLocations[ni] = //locations[j];
new OperationLocation(operations[j], locations[j], j);
} else {
int ci = op.getBytecodeIndex();
Location loc;
try {
loc = MethodWrapper.locationOfCodeIndex(location.getMethod(), ci);
} catch (InternalExceptionWrapper ex) {
return null;
} catch (VMDisconnectedExceptionWrapper ex) {
return null;
}
if (loc == null) {
logger.log(Level.FINE, "Location of the operation not found.");
return null;
}
opLocations[ni] = //loc;
new OperationLocation(op, loc, -1);
}
}
return opLocations;
}
}
if (operationIndex > codeIndex) {
return new OperationLocation[] { new OperationLocation(
operations[i],
locations[i],
i
) };
}
}
return null;
}
}
public static final class ExpressionLocation {
private Method method;
private int line;
public ExpressionLocation(Method method, int line) {
this.method = method;
this.line = line;
}
public Method getMethod() {
return method;
}
public int getLine() {
return line;
}
public boolean equals(Object obj) {
if (!(obj instanceof ExpressionLocation)) {
return false;
}
return ((ExpressionLocation) obj).line == line && ((ExpressionLocation) obj).method.equals(method);
}
public int hashCode() {
return method.hashCode() + line;
}
}
public static final class OperationLocation {
private Operation op;
private Location loc;
private int index;
OperationLocation(Operation op, Location loc, int index) {
this.op = op;
this.loc = loc;
this.index = index;
}
public Operation getOperation() {
return op;
}
public Location getLocation() {
return loc;
}
public int getIndex() {
return index;
}
}
public static final class Interval {
private int i1;
private int i2;
Interval(int i1, int i2) {
this.i1 = i1;
this.i2 = i2;
}
public int begin() {
return i1;
}
public int end() {
return i2;
}
public boolean contains(int i) {
return i1 <= i && i <= i2;
}
}
}