blob: 94ff89a4dc31b00639e7d9639a2082898dbfa96c [file] [log] [blame]
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* 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.
*/
/*
* @author Eugene Zhuravlev
*/
package com.intellij.debugger.jdi;
import com.intellij.debugger.DebuggerBundle;
import com.intellij.debugger.engine.DebuggerManagerThreadImpl;
import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil;
import com.intellij.debugger.engine.jdi.StackFrameProxy;
import com.intellij.openapi.diagnostic.Logger;
import com.sun.jdi.*;
import gnu.trove.THashMap;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class StackFrameProxyImpl extends JdiProxy implements StackFrameProxy {
private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.jdi.StackFrameProxyImpl");
private final ThreadReferenceProxyImpl myThreadProxy;
private final int myFrameFromBottomIndex; // 1-based
//caches
private int myFrameIndex = -1;
private StackFrame myStackFrame;
private ObjectReference myThisReference;
private ClassLoaderReference myClassLoader;
private Boolean myIsObsolete = null;
private Map<LocalVariable, Value> myAllValues;
public StackFrameProxyImpl(ThreadReferenceProxyImpl threadProxy, @NotNull StackFrame frame, int fromBottomIndex /* 1-based */) {
super(threadProxy.getVirtualMachine());
myThreadProxy = threadProxy;
myFrameFromBottomIndex = fromBottomIndex;
myStackFrame = frame;
}
public boolean isObsolete() throws EvaluateException {
DebuggerManagerThreadImpl.assertIsManagerThread();
checkValid();
if (myIsObsolete != null) {
return myIsObsolete.booleanValue();
}
InvalidStackFrameException error = null;
for (int attempt = 0; attempt < 2; attempt++) {
try {
boolean isObsolete = (getVirtualMachine().canRedefineClasses() && location().method().isObsolete());
myIsObsolete = isObsolete? Boolean.TRUE : Boolean.FALSE;
return isObsolete;
}
catch (InvalidStackFrameException e) {
error = e;
clearCaches();
}
catch (InternalException e) {
if (e.errorCode() == 23 /*INVALID_METHODID according to JDI sources*/) {
myIsObsolete = Boolean.TRUE;
return true;
}
throw e;
}
}
if (error != null) {
throw new EvaluateException(error.getMessage(), error);
}
return false;
}
@Override
protected void clearCaches() {
DebuggerManagerThreadImpl.assertIsManagerThread();
if (LOG.isDebugEnabled()) {
LOG.debug("caches cleared " + super.toString());
}
myFrameIndex = -1;
myStackFrame = null;
myIsObsolete = null;
myThisReference = null;
myClassLoader = null;
myAllValues = null;
}
/**
* Use with caution. Better access stackframe data through the Proxy's methods
*/
@Override
public StackFrame getStackFrame() throws EvaluateException {
DebuggerManagerThreadImpl.assertIsManagerThread();
checkValid();
if (myStackFrame == null) {
try {
final ThreadReference threadRef = myThreadProxy.getThreadReference();
myStackFrame = threadRef.frame(getFrameIndex());
}
catch (IndexOutOfBoundsException e) {
throw new EvaluateException(e.getMessage(), e);
}
catch (ObjectCollectedException ignored) {
throw EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.thread.collected"));
}
catch (IncompatibleThreadStateException e) {
throw EvaluateExceptionUtil.createEvaluateException(e);
}
}
return myStackFrame;
}
@Override
public int getFrameIndex() throws EvaluateException {
DebuggerManagerThreadImpl.assertIsManagerThread();
checkValid();
if(myFrameIndex == -1) {
int count = myThreadProxy.frameCount();
if(myFrameFromBottomIndex > count) {
throw EvaluateExceptionUtil.createEvaluateException(new IncompatibleThreadStateException());
}
myFrameIndex = count - myFrameFromBottomIndex;
}
return myFrameIndex;
}
// public boolean isProxiedFrameValid() {
// if (myStackFrame != null) {
// try {
// myStackFrame.thread();
// return true;
// }
// catch (InvalidStackFrameException e) {
// }
// }
// return false;
// }
@Override
public VirtualMachineProxyImpl getVirtualMachine() {
return (VirtualMachineProxyImpl) myTimer;
}
@Nullable
@Override
public Location location() throws EvaluateException {
InvalidStackFrameException error = null;
for (int attempt = 0; attempt < 2; attempt++) {
try {
return getStackFrame().location();
}
catch (InvalidStackFrameException e) {
error = e;
clearCaches();
}
}
if (error != null) {
throw new EvaluateException(error.getMessage(), error);
}
return null;
}
@Override
public ThreadReferenceProxyImpl threadProxy() {
return myThreadProxy;
}
public @NonNls String toString() {
try {
return "StackFrameProxyImpl: " + getStackFrame().toString();
}
catch (EvaluateException e) {
return "StackFrameProxyImpl: " + e.getMessage() + "; frameFromBottom = " + myFrameFromBottomIndex + " threadName = " + threadProxy().name();
}
}
@Nullable
public ObjectReference thisObject() throws EvaluateException {
DebuggerManagerThreadImpl.assertIsManagerThread();
checkValid();
try {
for (int attempt = 0; attempt < 2; attempt++) {
try {
if(myThisReference == null) {
myThisReference = getStackFrame().thisObject();
}
break;
}
catch (InvalidStackFrameException ignored) {
clearCaches();
}
}
}
catch (InternalException e) {
// suppress some internal errors caused by bugs in specific JDI implementations
if(e.errorCode() != 23) {
throw EvaluateExceptionUtil.createEvaluateException(e);
}
}
return myThisReference;
}
public List<LocalVariableProxyImpl> visibleVariables() throws EvaluateException {
DebuggerManagerThreadImpl.assertIsManagerThread();
InvalidStackFrameException error = null;
for (int attempt = 0; attempt < 2; attempt++) {
try {
final List<LocalVariable> list = getStackFrame().visibleVariables();
final List<LocalVariableProxyImpl> locals = new ArrayList<LocalVariableProxyImpl>(list.size());
for (LocalVariable localVariable : list) {
LOG.assertTrue(localVariable != null);
locals.add(new LocalVariableProxyImpl(this, localVariable));
}
return locals;
}
catch (InvalidStackFrameException e) {
error = e;
clearCaches();
}
catch (AbsentInformationException e) {
throw EvaluateExceptionUtil.createEvaluateException(e);
}
}
if (error != null) {
throw new EvaluateException(error.getMessage(), error);
}
return Collections.emptyList();
}
@Override
public LocalVariableProxyImpl visibleVariableByName(String name) throws EvaluateException {
DebuggerManagerThreadImpl.assertIsManagerThread();
final LocalVariable variable = visibleVariableByNameInt(name);
return variable != null ? new LocalVariableProxyImpl(this, variable) : null;
}
@Nullable
public Value visibleValueByName(@NotNull String name) throws EvaluateException {
LocalVariable variable = visibleVariableByNameInt(name);
return variable != null ? getValue(new LocalVariableProxyImpl(this, variable)) : null;
}
protected LocalVariable visibleVariableByNameInt(String name) throws EvaluateException {
DebuggerManagerThreadImpl.assertIsManagerThread();
InvalidStackFrameException error = null;
for (int attempt = 0; attempt < 2; attempt++) {
try {
try {
return getStackFrame().visibleVariableByName(name);
}
catch (InvalidStackFrameException e) {
error = e;
clearCaches();
}
}
catch (InvalidStackFrameException e) {
throw EvaluateExceptionUtil.createEvaluateException(e);
}
catch (AbsentInformationException e) {
throw EvaluateExceptionUtil.createEvaluateException(e);
}
}
if (error != null) {
throw new EvaluateException(error.getMessage(), error);
}
return null;
}
public Value getValue(LocalVariableProxyImpl localVariable) throws EvaluateException {
DebuggerManagerThreadImpl.assertIsManagerThread();
InvalidStackFrameException error = null;
for (int attempt = 0; attempt < 2; attempt++) {
try {
return getAllValues().get(localVariable.getVariable());
}
catch (InvalidStackFrameException e) {
error = e;
clearCaches();
}
}
if (error != null) {
throw new EvaluateException(error.getMessage(), error);
}
return null;
}
public List<Value> getArgumentValues() throws EvaluateException {
DebuggerManagerThreadImpl.assertIsManagerThread();
InvalidStackFrameException error = null;
for (int attempt = 0; attempt < 2; attempt++) {
try {
final StackFrame stackFrame = getStackFrame();
return stackFrame != null? stackFrame.getArgumentValues() : Collections.<Value>emptyList();
}
catch (InternalException e) {
// From Oracle's forums:
// This could be a JPDA bug. Unexpected JDWP Error: 32 means that an 'opaque' frame was detected at the lower JPDA levels,
// typically a native frame.
if (e.errorCode() == 32 /*opaque frame JDI bug*/ ) {
return Collections.emptyList();
}
else {
throw e;
}
}
catch (InvalidStackFrameException e) {
error = e;
clearCaches();
}
}
if (error != null) {
throw new EvaluateException(error.getMessage(), error);
}
return Collections.emptyList();
}
private Map<LocalVariable, Value> getAllValues() throws EvaluateException{
DebuggerManagerThreadImpl.assertIsManagerThread();
checkValid();
if (myAllValues == null) {
try {
StackFrame stackFrame = getStackFrame();
myAllValues = new THashMap<LocalVariable, Value>(stackFrame.getValues(stackFrame.visibleVariables()));
}
catch (InconsistentDebugInfoException ignored) {
clearCaches();
throw EvaluateExceptionUtil.INCONSISTEND_DEBUG_INFO;
}
catch (AbsentInformationException e) {
throw EvaluateExceptionUtil.createEvaluateException(e);
}
}
return myAllValues;
}
public void setValue(LocalVariableProxyImpl localVariable, Value value) throws EvaluateException, ClassNotLoadedException, InvalidTypeException {
DebuggerManagerThreadImpl.assertIsManagerThread();
InvalidStackFrameException error = null;
for (int attempt = 0; attempt < 2; attempt++) {
try {
final LocalVariable variable = localVariable.getVariable();
final StackFrame stackFrame = getStackFrame();
stackFrame.setValue(variable, (value instanceof ObjectReference)? ((ObjectReference)value) : value);
if (myAllValues != null) {
// update cached data if any
// re-read the value just set from the stackframe to be 100% sure
myAllValues.put(variable, stackFrame.getValue(variable));
}
return;
}
catch (InvalidStackFrameException e) {
error = e;
clearCaches();
}
}
if (error != null) {
throw new EvaluateException(error.getMessage(), error);
}
}
public int hashCode() {
return 31 * myThreadProxy.hashCode() + myFrameFromBottomIndex;
}
public boolean equals(final Object obj) {
if (!(obj instanceof StackFrameProxyImpl)) {
return false;
}
StackFrameProxyImpl frameProxy = (StackFrameProxyImpl)obj;
if(frameProxy == this)return true;
return (myFrameFromBottomIndex == frameProxy.myFrameFromBottomIndex) &&
(myThreadProxy.equals(frameProxy.myThreadProxy));
}
public boolean isLocalVariableVisible(LocalVariableProxyImpl var) throws EvaluateException {
try {
return var.getVariable().isVisible(getStackFrame());
}
catch (IllegalArgumentException ignored) {
// can be thrown if frame's method is different than variable's method
return false;
}
}
@Override
public ClassLoaderReference getClassLoader() throws EvaluateException {
if(myClassLoader == null) {
myClassLoader = location().declaringType().classLoader();
}
return myClassLoader;
}
public boolean isBottom() {
return myFrameFromBottomIndex == 1;
}
public int getIndexFromBottom() {
return myFrameFromBottomIndex;
}
}