/*
* Copyright (c) 2012, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html
*
* 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 com.github.sdbg.debug.core.internal.webkit.model;
import com.github.sdbg.debug.core.SDBGDebugCorePlugin;
import com.github.sdbg.debug.core.internal.expr.WatchExpressionResult;
import com.github.sdbg.debug.core.internal.util.DebuggerUtils;
import com.github.sdbg.debug.core.internal.webkit.protocol.WebkitCallFrame;
import com.github.sdbg.debug.core.internal.webkit.protocol.WebkitCallback;
import com.github.sdbg.debug.core.internal.webkit.protocol.WebkitLocation;
import com.github.sdbg.debug.core.internal.webkit.protocol.WebkitRemoteObject;
import com.github.sdbg.debug.core.internal.webkit.protocol.WebkitResult;
import com.github.sdbg.debug.core.internal.webkit.protocol.WebkitScope;
import com.github.sdbg.debug.core.internal.webkit.protocol.WebkitScript;
import com.github.sdbg.debug.core.model.IExceptionStackFrame;
import com.github.sdbg.debug.core.model.IExpressionEvaluator;
import com.github.sdbg.debug.core.model.ISDBGStackFrame;
import com.github.sdbg.debug.core.model.ISDBGValue.IValueCallback;
import com.github.sdbg.debug.core.model.IVariableResolver;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.eclipse.core.resources.IStorage;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.IRegisterGroup;
import org.eclipse.debug.core.model.IStackFrame;
import org.eclipse.debug.core.model.IThread;
import org.eclipse.debug.core.model.IValue;
import org.eclipse.debug.core.model.IVariable;
import org.eclipse.debug.core.model.IWatchExpressionListener;
/**
* The IStackFrame implementation for the Webkit debug elements. This stack frame element represents
* a Dart frame.
*/
public class WebkitDebugStackFrame extends WebkitDebugElement implements IStackFrame,
ISDBGStackFrame, IExceptionStackFrame, IVariableResolver, IExpressionEvaluator {
private IThread thread;
private WebkitCallFrame webkitFrame;
private boolean isExceptionStackFrame;
private VariableCollector variableCollector = VariableCollector.empty();
private IValue classValue;
private IValue globalScopeValue;
public WebkitDebugStackFrame(IDebugTarget target, IThread thread, WebkitCallFrame webkitFrame) {
this(target, thread, webkitFrame, null);
}
public WebkitDebugStackFrame(IDebugTarget target, IThread thread, WebkitCallFrame webkitFrame,
WebkitRemoteObject exception) {
super(target);
this.thread = thread;
this.webkitFrame = webkitFrame;
fillInWebkitVariables(exception);
}
@Override
public boolean canResume() {
return getThread().canResume();
}
@Override
public boolean canStepInto() {
return !hasException() && getThread().canStepInto();
}
@Override
public boolean canStepOver() {
return !hasException() && getThread().canStepOver();
}
@Override
public boolean canStepReturn() {
return !hasException() && getThread().canStepReturn();
}
@Override
public boolean canSuspend() {
return getThread().canSuspend();
}
@Override
public boolean canTerminate() {
return getThread().canTerminate();
}
@Override
public void evaluateExpression(final String expression, final IWatchExpressionListener listener) {
try {
getConnection().getDebugger().evaluateOnCallFrame(
webkitFrame.getCallFrameId(),
expression,
new WebkitCallback<WebkitRemoteObject>() {
@Override
public void handleResult(WebkitResult<WebkitRemoteObject> result) {
if (result.isError()) {
if (result.getError() instanceof WebkitRemoteObject) {
WebkitRemoteObject error = (WebkitRemoteObject) result.getError();
String desc;
if (error.isObject()) {
desc = error.getDescription();
} else if (error.isString()) {
desc = error.getValue();
} else {
desc = error.toString();
}
listener.watchEvaluationFinished(WatchExpressionResult.error(expression, desc));
} else {
listener.watchEvaluationFinished(WatchExpressionResult.error(
expression,
result.getError().toString()));
}
} else {
IValue value = WebkitDebugValue.create(getTarget(), null, result.getResult());
listener.watchEvaluationFinished(WatchExpressionResult.value(expression, value));
}
}
});
} catch (IOException e) {
listener.watchEvaluationFinished(WatchExpressionResult.noOp(expression));
}
}
@Override
public IVariable findVariable(String varName) throws DebugException {
// search in locals
for (IVariable var : getVariables()) {
if (var.getName().equals(varName)) {
return var;
}
}
// search in instance variables
IVariable thisVar = getThisVariable();
if (thisVar != null) {
IValue thisValue = thisVar.getValue();
for (IVariable var : thisValue.getVariables()) {
if (var.getName().equals(varName)) {
return var;
}
}
}
// search statics
if (getClassValue() != null) {
for (IVariable var : getClassValue().getVariables()) {
if (var.getName().equals(varName)) {
return var;
}
}
}
// search globals
if (getGlobalsScope() != null) {
for (IVariable var : getGlobalsScope().getVariables()) {
if (var.getName().equals(varName)) {
return var;
}
}
}
return null;
}
@SuppressWarnings("rawtypes")
@Override
public Object getAdapter(Class adapterClass) {
if (adapterClass == IThread.class) {
return getThread();
} else {
return super.getAdapter(adapterClass);
}
}
@Override
public int getCharEnd() throws DebugException {
return -1;
}
@Override
public int getCharStart() throws DebugException {
return -1;
}
@Override
public String getExceptionDisplayText() throws DebugException {
WebkitDebugVariable variable = (WebkitDebugVariable) getVariables()[0];
WebkitDebugValue exceptionValue = (WebkitDebugValue) variable.getValue();
final String[] result = new String[1];
final CountDownLatch latch = new CountDownLatch(1);
exceptionValue.computeDetail(new IValueCallback() {
@Override
public void detailComputed(String stringValue) {
result[0] = stringValue;
latch.countDown();
}
});
try {
latch.await(1000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
return "Exception: " + exceptionValue.getValueString();
}
return "Exception: " + result[0];
}
@Override
public int getLineNumber() throws DebugException {
try {
if (getTarget().shouldUseSourceMapping() && isUsingSourceMaps()) {
SourceMapManager.SourceLocation location = getMappedLocation();
return WebkitLocation.webkitToElipseLine(location.getLine());
} else {
return WebkitLocation.webkitToElipseLine(webkitFrame.getLocation().getLineNumber());
}
} catch (Throwable t) {
SDBGDebugCorePlugin.logError(t);
return 1;
}
}
@Override
public String getLongName() {
String file = getFileName();
if (file != null) {
try {
int lineNumber = getLineNumber();
if (lineNumber > 0) {
file += ":" + lineNumber;
}
} catch (DebugException e) {
}
}
String name = getCallerName();
if (name != null && name.length() > 0) {
return name + "(" + (file != null ? file : "") + ")";
} else if (file != null) {
return file;
} else {
return "(code block)";
}
}
@Override
public String getName() throws DebugException {
return getLongName();
}
@Override
public IRegisterGroup[] getRegisterGroups() throws DebugException {
return new IRegisterGroup[0];
}
@Override
public String getShortName() {
String callerName = getCallerName();
if (callerName != null && callerName.length() > 0) {
return getCallerName() + "()";
} else {
return "(code block)";
}
}
@Override
public String getSourceLocationPath() {
try {
if (getTarget().shouldUseSourceMapping() && isUsingSourceMaps()) {
return getMappedLocationPath();
} else {
IStorage storage = getTarget().getScriptStorageFor(webkitFrame);
if (storage != null) {
return storage.getFullPath().toPortableString();
} else {
return null;
}
}
} catch (Throwable t) {
SDBGDebugCorePlugin.logError(t);
return null;
}
}
@Override
public IThread getThread() {
return thread;
}
@Override
public IVariable[] getVariables() throws DebugException {
try {
return variableCollector.getVariables();
} catch (InterruptedException e) {
throw new DebugException(new Status(
IStatus.ERROR,
SDBGDebugCorePlugin.PLUGIN_ID,
e.toString(),
e));
}
}
@Override
public boolean hasException() {
return isExceptionStackFrame;
}
@Override
public boolean hasRegisterGroups() throws DebugException {
return false;
}
@Override
public boolean hasVariables() throws DebugException {
return getVariables().length > 0;
}
@Override
public boolean isPrivate() {
return DebuggerUtils.isPrivateName(webkitFrame.getFunctionName());
}
public boolean isPrivateMethod() {
return webkitFrame.isPrivateMethod();
}
@Override
public boolean isStepping() {
return getThread().isStepping();
}
@Override
public boolean isSuspended() {
return getThread().isSuspended();
}
@Override
public boolean isTerminated() {
return getThread().isTerminated();
}
@Override
public boolean isUsingSourceMaps() {
return getMappedLocation() != null;
}
@Override
public void resume() throws DebugException {
getThread().resume();
}
@Override
public void stepInto() throws DebugException {
getThread().stepInto();
}
@Override
public void stepOver() throws DebugException {
getThread().stepOver();
}
@Override
public void stepReturn() throws DebugException {
getThread().stepReturn();
}
@Override
public void suspend() throws DebugException {
getThread().suspend();
}
@Override
public void terminate() throws DebugException {
getThread().terminate();
}
@Override
public String toString() {
return getShortName();
}
protected IValue getClassValue() throws DebugException {
if (classValue != null) {
return classValue;
}
for (WebkitScope scope : webkitFrame.getScopeChain()) {
if (scope.isClass()) {
classValue = WebkitDebugValue.create(getTarget(), null, scope.getObject());
break;
}
}
return classValue;
}
protected IValue getGlobalsScope() throws DebugException {
if (globalScopeValue != null) {
return globalScopeValue;
}
for (WebkitScope scope : webkitFrame.getScopeChain()) {
if (scope.isGlobal()) {
globalScopeValue = WebkitDebugValue.create(getTarget(), null, scope.getObject());
break;
}
}
return globalScopeValue;
}
protected String getMappedLocationPath() {
SourceMapManager.SourceLocation targetLocation = getMappedLocation();
if (SourceMapManager.isTracing()) {
WebkitLocation sourceLocation = webkitFrame.getLocation();
WebkitScript script = getConnection().getDebugger().getScript(sourceLocation.getScriptId());
String scriptPath = script == null ? "null" : script.getUrl();
SourceMapManager.trace("[" + scriptPath + "," + sourceLocation.getLineNumber() + ","
+ sourceLocation.getColumnNumber() + "] ==> mapped to " + targetLocation);
}
//!!!
// if (targetLocation.getStorage() instanceof IFile) {
// return targetLocation.getStorage().getFullPath().toPortableString();
// } else {
return targetLocation.getPath();
// }
}
protected IVariable getThisVariable() throws DebugException {
for (IVariable var : getVariables()) {
if (var instanceof WebkitDebugVariable) {
if (((WebkitDebugVariable) var).isThisObject()) {
return var;
}
}
}
return null;
}
protected WebkitCallFrame getWebkitFrame() {
return webkitFrame;
}
/**
* Fill in the IVariables from the Webkit variables.
*
* @param exception can be null
*/
private void fillInWebkitVariables(WebkitRemoteObject exception) {
isExceptionStackFrame = (exception != null);
WebkitRemoteObject thisObject = null;
if (!webkitFrame.isStaticMethod()) {
thisObject = webkitFrame.getThisObject();
}
variableCollector = VariableCollector.createCollector(
getTarget(),
thisObject,
exception,
true,
webkitFrame.getScopeChain());
}
private String getCallerName() {
String name = null;
if (getTarget().shouldUseSourceMapping() && isUsingSourceMaps()) {
SourceMapManager.SourceLocation location = getMappedLocation();
name = location.getName();
}
if (name == null) {
name = DebuggerUtils.demangleFunctionName(webkitFrame.getFunctionName());
}
return name;
}
private String getFileName() {
String path = getSourceLocationPath();
if (path != null) {
int index = path.lastIndexOf('/');
if (index != -1) {
return path.substring(index + 1);
} else {
return path;
}
}
return null;
}
private SourceMapManager.SourceLocation getMappedLocation() {
IStorage storage = getTarget().getScriptStorageFor(webkitFrame);
if (getTarget().getSourceMapManager() != null
&& getTarget().getSourceMapManager().isMapSource(storage)) {
WebkitLocation location = webkitFrame.getLocation();
return getTarget().getSourceMapManager().getMappingFor(
storage,
location.getLineNumber(),
location.getColumnNumber());
} else {
return null;
}
}
}