/*
* 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.google.dart.tools.debug.core.dartium;
import com.google.dart.tools.debug.core.DartDebugCorePlugin;
import com.google.dart.tools.debug.core.dartium.DartiumDebugValue.ValueCallback;
import com.google.dart.tools.debug.core.expr.IExpressionEvaluator;
import com.google.dart.tools.debug.core.expr.WatchExpressionResult;
import com.google.dart.tools.debug.core.source.WorkspaceSourceContainer;
import com.google.dart.tools.debug.core.util.DebuggerUtils;
import com.google.dart.tools.debug.core.util.IDartStackFrame;
import com.google.dart.tools.debug.core.util.IExceptionStackFrame;
import com.google.dart.tools.debug.core.util.IVariableResolver;
import com.google.dart.tools.debug.core.webkit.WebkitCallFrame;
import com.google.dart.tools.debug.core.webkit.WebkitCallback;
import com.google.dart.tools.debug.core.webkit.WebkitLocation;
import com.google.dart.tools.debug.core.webkit.WebkitRemoteObject;
import com.google.dart.tools.debug.core.webkit.WebkitResult;
import com.google.dart.tools.debug.core.webkit.WebkitScope;
import com.google.dart.tools.debug.core.webkit.WebkitScript;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
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;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* The IStackFrame implementation for the dartium debug elements. This stack frame element
* represents a Dart frame.
*/
public class DartiumDebugStackFrame extends DartiumDebugElement implements IStackFrame,
IDartStackFrame, IExceptionStackFrame, IVariableResolver, IExpressionEvaluator {
private IThread thread;
private WebkitCallFrame webkitFrame;
private boolean isExceptionStackFrame;
private VariableCollector variableCollector = VariableCollector.empty();
private IValue classValue;
private IValue globalScopeValue;
public DartiumDebugStackFrame(IDebugTarget target, IThread thread, WebkitCallFrame webkitFrame) {
this(target, thread, webkitFrame, null);
}
public DartiumDebugStackFrame(IDebugTarget target, IThread thread, WebkitCallFrame webkitFrame,
WebkitRemoteObject exception) {
super(target);
this.thread = thread;
this.webkitFrame = webkitFrame;
fillInDartiumVariables(exception);
}
@Override
public boolean canResume() {
return getThread().canResume();
}
@Override
public boolean canStepInto() {
return !hasException() && getThread().canStepInto() && !isOnAwaitKeyword();
}
@Override
public boolean canStepOver() {
return !hasException() && getThread().canStepOver() && !isOnAwaitKeyword();
}
@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 = DartiumDebugValue.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 {
DartiumDebugVariable variable = (DartiumDebugVariable) getVariables()[0];
DartiumDebugValue exceptionValue = (DartiumDebugValue) variable.getValue();
final String[] result = new String[1];
final CountDownLatch latch = new CountDownLatch(1);
exceptionValue.computeDetail(new ValueCallback() {
@Override
public void detailComputed(String stringValue) {
result[0] = stringValue;
latch.countDown();
}
});
try {
latch.await(1000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
return "Exception: " + exceptionValue.getDisplayString();
}
return "Exception: " + result[0];
}
@Override
public int getLineNumber() throws DebugException {
try {
if (getTarget().shouldUseSourceMapping() && isUsingSourceMaps()) {
SourceMapManager.SourceLocation location = getMappedLocation();
return WebkitLocation.webkitToElipseLine(location.line);
} else {
return WebkitLocation.webkitToElipseLine(webkitFrame.getLocation().getLineNumber());
}
} catch (Throwable t) {
DartDebugCorePlugin.logError(t);
return 1;
}
}
@Override
public String getLongName() {
String file = getFileOrLibraryName();
return getShortName() + (file == null ? "" : " - " + file);
}
@Override
public String getName() throws DebugException {
if (DebuggerUtils.areSiblingNamesUnique(this)) {
return getShortName();
} else {
return getLongName();
}
}
@Override
public IRegisterGroup[] getRegisterGroups() throws DebugException {
return new IRegisterGroup[0];
}
@Override
public String getShortName() {
return DebuggerUtils.demangleVmName(webkitFrame.getFunctionName()) + "()";
}
@Override
public String getSourceLocationPath() {
try {
if (getTarget().shouldUseSourceMapping() && isUsingSourceMaps()) {
return getMappedLocationPath();
} else {
return getActualLocationPath();
}
} catch (Throwable t) {
DartDebugCorePlugin.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,
DartDebugCorePlugin.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 String getActualLocationPath() {
final String packageFragment = "/packages/";
String scriptId = webkitFrame.getLocation().getScriptId();
WebkitScript script = getConnection().getDebugger().getScript(scriptId);
if (script != null) {
String url = script.getUrl();
if (script.isSystemScript() || script.isDataUrl() || script.isChromeExtensionUrl()) {
return url;
}
// Check for http://foo/bar/web/packages/baz.dart, convert it into a package: url.
if (url.contains(packageFragment)) {
url = "package:" + url.substring(url.indexOf(packageFragment) + packageFragment.length());
}
IResource resource = getTarget().getResourceResolver().resolveUrl(url);
if (resource != null) {
return resource.getLocation().toOSString();
}
String file = getTarget().getUriToFileResolver().getFileForUri(url);
if (file != null) {
return file;
}
if (url.startsWith("package:")) {
return url;
}
try {
return URI.create(url).getPath();
} catch (IllegalArgumentException iae) {
// Dartium can send us bad paths:
// e:\b\build\slave\dartium-win-full\build ... rt\dart\CanvasRenderingContext2DImpl.dart
DartDebugCorePlugin.logInfo("Illegal path from Dartium: " + url);
}
}
return null;
}
protected IValue getClassValue() throws DebugException {
if (classValue != null) {
return classValue;
}
for (WebkitScope scope : webkitFrame.getScopeChain()) {
if (scope.isClass()) {
classValue = DartiumDebugValue.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.isLibraries()) {
globalScopeValue = DartiumDebugValue.create(getTarget(), null, scope.getObject());
break;
}
}
return globalScopeValue;
}
protected String getMappedLocationPath() {
SourceMapManager.SourceLocation targetLocation = getMappedLocation();
if (DartDebugCorePlugin.LOGGING) {
WebkitLocation sourceLocation = webkitFrame.getLocation();
WebkitScript script = getConnection().getDebugger().getScript(sourceLocation.getScriptId());
String scriptPath = script == null ? "null" : script.getUrl();
System.out.println("[" + scriptPath + "," + sourceLocation.getLineNumber() + ","
+ sourceLocation.getColumnNumber() + "] ==> mapped to " + targetLocation);
}
return targetLocation.file.getLocation().toPortableString();
}
protected IVariable getThisVariable() throws DebugException {
for (IVariable var : getVariables()) {
if (var instanceof DartiumDebugVariable) {
if (((DartiumDebugVariable) var).isThisObject()) {
return var;
}
}
}
return null;
}
protected WebkitCallFrame getWebkitFrame() {
return webkitFrame;
}
/**
* @return whether we are on a line with the 'await' keyword.
*/
protected boolean isOnAwaitKeyword() {
final String await = "await";
if (!DartDebugCorePlugin.DISABLE_STEPPING_ON_AWAIT) {
return false;
}
try {
String line = getCurrentSourceLine();
if (line == null) {
return false;
}
// Find occurrences of `await`.
int index = line.indexOf(await);
while (index != -1) {
// Check to make sure the the beginning and end of the 'await' string are not valid
// identifier parts.
if (index == 0 || !Character.isJavaIdentifierPart(line.charAt(index - 1))) {
if (index + await.length() >= line.length()
|| !Character.isJavaIdentifierPart(line.charAt(index + await.length()))) {
return true;
}
}
index = line.indexOf(await, index + 1);
}
} catch (DebugException e) {
}
return false;
}
/**
* Fill in the IVariables from the Webkit variables.
*
* @param exception can be null
*/
private void fillInDartiumVariables(WebkitRemoteObject exception) {
isExceptionStackFrame = (exception != null);
List<WebkitRemoteObject> remoteObjects = new ArrayList<WebkitRemoteObject>();
WebkitRemoteObject thisObject = null;
if (!webkitFrame.isStaticMethod()) {
thisObject = webkitFrame.getThisObject();
}
for (WebkitScope scope : webkitFrame.getScopeChain()) {
if (!scope.isGlobalLike() && !scope.isInstance()) {
remoteObjects.add(scope.getObject());
}
}
variableCollector = VariableCollector.createCollector(
getTarget(),
thisObject,
remoteObjects,
null,
exception);
}
private String getCurrentSourceLine() throws DebugException {
int line = getLineNumber();
if (line == 0) {
return null;
}
Object sourceElement = getLaunch().getSourceLocator().getSourceElement(this);
if (sourceElement instanceof IFile) {
IFile file = (IFile) sourceElement;
return DebuggerUtils.extractFileLine(file, line - 1);
}
return null;
}
private String getFileOrLibraryName() {
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() {
SourceMapManager sourceMapManager = getTarget().getSourceMapManager();
if (sourceMapManager == null) {
return null;
}
IFile file = WorkspaceSourceContainer.locatePathAsFile(getActualLocationPath());
if (sourceMapManager.isMapSource(file)) {
WebkitLocation location = webkitFrame.getLocation();
return sourceMapManager.getMappingFor(
file,
location.getLineNumber(),
location.getColumnNumber());
} else {
return null;
}
}
}