| /* |
| * 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; |
| } |
| } |
| |
| } |