blob: c82d8bccf781a3d29dcb70a17cb2f67e7874afe6 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2010, 2019 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.internal.r.debug.core.model;
import java.util.Objects;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.core.model.IRegisterGroup;
import org.eclipse.debug.core.model.IValue;
import org.eclipse.debug.core.model.IVariable;
import org.eclipse.statet.jcommons.lang.NonNull;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.jcommons.status.ProgressMonitor;
import org.eclipse.statet.jcommons.status.StatusException;
import org.eclipse.statet.jcommons.ts.core.SystemRunnable;
import org.eclipse.statet.jcommons.ts.core.Tool;
import org.eclipse.statet.jcommons.ts.core.ToolService;
import org.eclipse.statet.internal.r.debug.core.Messages;
import org.eclipse.statet.internal.r.debug.core.RDebugCorePlugin;
import org.eclipse.statet.ltk.model.core.elements.IModelElement;
import org.eclipse.statet.r.core.data.CombinedRElement;
import org.eclipse.statet.r.core.data.CombinedRReference;
import org.eclipse.statet.r.core.model.RElementName;
import org.eclipse.statet.r.core.model.RModel;
import org.eclipse.statet.r.debug.core.IRStackFrame;
import org.eclipse.statet.r.debug.core.breakpoints.RBreakpointStatus;
import org.eclipse.statet.r.nico.AbstractRDbgController;
import org.eclipse.statet.r.nico.ICombinedRDataAdapter;
import org.eclipse.statet.rj.data.RObject;
import org.eclipse.statet.rj.server.dbg.CallStack;
import org.eclipse.statet.rj.server.dbg.Frame;
import org.eclipse.statet.rj.server.dbg.FrameContext;
public class RStackFrame extends RDebugElement implements IRStackFrame {
public static interface PositionResolver {
int getLineNumber();
int getCharStart();
int getCharEnd();
}
private class LoadContextRunnable implements SystemRunnable {
private boolean cancel;
public LoadContextRunnable() {
}
@Override
public String getTypeId() {
return "r/dbg/stackframe"; //$NON-NLS-1$
}
@Override
public String getLabel() {
return Messages.DebugContext_UpdateStackFrame_task;
}
@Override
public boolean canRunIn(final Tool tool) {
return (tool == RStackFrame.this.thread.getTool());
}
@Override
public boolean changed(final int event, final Tool process) {
switch (event) {
case REMOVING_FROM:
return this.cancel;
case MOVING_FROM:
return false;
case BEING_ABANDONED:
// case FINISHING_: // handled in #loadContext
RStackFrame.this.lock.writeLock().lock();
try {
RStackFrame.this.contextCondition.signalAll();
}
finally {
RStackFrame.this.lock.writeLock().unlock();
}
break;
default:
break;
}
return true;
}
@Override
public void run(final ToolService service, final ProgressMonitor m) throws StatusException {
loadContext((AbstractRDbgController) service, m);
}
}
private final ReentrantReadWriteLock lock= new ReentrantReadWriteLock();
private final RMainThread thread;
private int stamp;
private Frame dbgFrame;
private FrameContext dbgFrameContext;
private final RElementName elementName;
private final String call;
private final String fileName;
private Long handle;
private boolean detailLoaded;
private LoadContextRunnable contextRunnable;
private final Condition contextCondition= this.lock.writeLock().newCondition();
private final Condition contextWaitCondition= this.lock.writeLock().newCondition();
private PositionResolver positionResolver;
private RElementVariable frameVariable;
private IValue variables;
private RBreakpointStatus breakpointStatus;
public RStackFrame(final RDebugTarget target, final RMainThread thread, final int stamp,
final Frame dbgFrame, final Long handle, final String call, final String fileName,
final RBreakpointStatus breakpointStatus) {
super(target);
this.thread= thread;
this.stamp= stamp;
this.dbgFrame= dbgFrame;
if (dbgFrame.getPosition() > 0) {
this.elementName= RElementName.create(RElementName.SCOPE_SYSFRAME,
Integer.toString(dbgFrame.getPosition()) );
}
else if (dbgFrame.getPosition() == 0) {
this.elementName= RModel.GLOBAL_ENV_NAME;
}
else {
this.elementName= null;
}
this.handle= handle;
this.call= call;
this.fileName= fileName;
this.breakpointStatus= breakpointStatus;
}
public boolean update(final int stamp,
final Frame dbgFrame, final Long handle, final String call, final String fileName,
final RBreakpointStatus breakpointStatus) {
if (call.equals(this.call)
&& Objects.equals(dbgFrame.getFileName(), this.dbgFrame.getFileName())
&& dbgFrame.getFileTimestamp() == this.dbgFrame.getFileTimestamp() ) {
this.lock.writeLock().lock();
try {
if (this.stamp != stamp) {
this.stamp= stamp;
if (((dbgFrame.getExprSrcref() != null) ?
!dbgFrame.getExprSrcref().equals(this.dbgFrame.getExprSrcref()) :
null != this.dbgFrame.getExprSrcref())
|| dbgFrame.getFileTimestamp() == 0
|| breakpointStatus != null ) {
// need new detail
this.dbgFrame= dbgFrame;
this.detailLoaded= false;
this.breakpointStatus= breakpointStatus;
if (this.contextRunnable != null) {
this.contextRunnable.cancel= true;
this.contextCondition.signalAll();
}
}
this.handle= handle;
this.frameVariable= null;
this.variables= null;
}
return true;
}
finally {
this.lock.writeLock().unlock();
}
}
return false;
}
@Override
public RMainThread getThread() {
return this.thread;
}
@Override
public String getName() throws DebugException {
return this.call;
}
@Override
public RElementName getElementName() {
final RElementVariable variable= this.frameVariable;
if (variable != null) {
final CombinedRElement element= variable.getElement();
return element.getElementName();
}
return this.elementName;
}
public @NonNull Long getHandle() {
return this.handle;
}
public Frame getDbgFrame() {
return this.dbgFrame;
}
@Override
public String getInfoFileName() {
return this.fileName;
}
@Override
public int getInfoLineNumber() {
final int[] exprSrcref= this.dbgFrame.getExprSrcref();
return (exprSrcref != null) ? exprSrcref[0] : -1;
}
@Override
public int getPosition() {
return this.dbgFrame.getPosition();
}
@Override
public boolean isTerminated() {
return this.thread.isTerminated();
}
@Override
public boolean canTerminate() {
return this.thread.canTerminate();
}
@Override
public void terminate() throws DebugException {
this.thread.terminate();
}
@Override
public boolean isSuspended() {
return this.thread.isSuspended();
}
@Override
public boolean canSuspend() {
return this.thread.canSuspend();
}
@Override
public boolean canResume() {
return this.thread.canResume();
}
@Override
public void suspend() throws DebugException {
this.thread.suspend();
}
@Override
public void resume() throws DebugException {
this.thread.resume();
}
@Override
public boolean isStepping() {
return this.thread.isStepping();
}
@Override
public boolean canStepInto() {
return (isSuspended() && this.dbgFrame.isTopFrame());
}
@Override
public boolean canStepOver() {
return (isSuspended() && (this.dbgFrame.getFlags() & CallStack.FLAG_NOSTEPPING) == 0);
}
@Override
public boolean canStepReturn() {
return (isSuspended() && this.dbgFrame.getPosition() > 0 && !this.dbgFrame.isTopLevelCommand());
}
@Override
public void stepInto() throws DebugException {
if (canStepInto()) {
getThread().stepInto();
}
}
@Override
public void stepOver() throws DebugException {
if (canStepOver()) {
getThread().stepToFrame(this, 0);
}
}
@Override
public void stepReturn() throws DebugException {
if (canStepReturn()) {
getThread().stepToFrame(this, 1);
}
}
@Override
public boolean hasVariables() throws DebugException {
return this.dbgFrame.getPosition() > 0;
}
@Override
public IVariable[] getVariables() throws DebugException {
this.lock.readLock().lock();
try {
if (ensureContext() != null && this.variables != null) {
return this.variables.getVariables();
}
return new IVariable[0];
}
finally {
this.lock.readLock().unlock();
}
}
@Override
public boolean hasRegisterGroups() throws DebugException {
return false;
}
@Override
public IRegisterGroup[] getRegisterGroups() throws DebugException {
return null;
}
public void setPositionResolver(final FrameContext context, final PositionResolver resolver) {
this.lock.writeLock().lock();
if ((this.dbgFrameContext != null) ? this.dbgFrameContext == context : null == context) {
this.positionResolver= resolver;
}
this.lock.writeLock().unlock();
}
@Override
public int getLineNumber() throws DebugException {
PositionResolver resolver;
this.lock.readLock().lock();
try {
resolver= this.positionResolver;
}
finally {
this.lock.readLock().unlock();
}
if (resolver != null) {
return resolver.getLineNumber() + 1;
}
return getInfoLineNumber();
}
@Override
public int getCharStart() throws DebugException {
final PositionResolver resolver;
this.lock.readLock().lock();
try {
resolver= this.positionResolver;
}
finally {
this.lock.readLock().unlock();
}
if (resolver != null) {
return resolver.getCharStart();
}
return -1;
}
@Override
public int getCharEnd() throws DebugException {
final PositionResolver resolver;
this.lock.readLock().lock();
try {
resolver= this.positionResolver;
}
finally {
this.lock.readLock().unlock();
}
if (resolver != null) {
return resolver.getCharEnd();
}
return -1;
}
private FrameContext ensureContext() {
if (!this.detailLoaded) {
this.lock.readLock().unlock();
this.lock.writeLock().lock();
try {
final Frame frame= this.dbgFrame;
while (this.contextRunnable != null && this.dbgFrame == frame) {
try {
this.contextWaitCondition.await();
}
catch (final InterruptedException e) {}
}
if (!this.detailLoaded && this.dbgFrame == frame) {
if (this.dbgFrame.getPosition() < 0) {
this.detailLoaded= true;
}
else {
this.contextRunnable= new LoadContextRunnable();
try {
if (getDebugTarget().getProcess().getQueue().addHot(this.contextRunnable)
.getSeverity() == org.eclipse.statet.jcommons.status.Status.OK ) {
try {
this.contextCondition.await();
}
catch (final InterruptedException e) {
this.contextRunnable.cancel= true;
}
if (this.contextRunnable.cancel) {
getDebugTarget().getProcess().getQueue().removeHot(this.contextRunnable);
}
}
}
finally {
this.contextRunnable= null;
this.contextWaitCondition.signalAll();
}
}
}
if (this.dbgFrame != frame) {
return null;
}
}
finally {
this.lock.readLock().lock();
this.lock.writeLock().unlock();
}
}
return this.dbgFrameContext;
}
public FrameContext getContext() {
this.lock.readLock().lock();
try {
return ensureContext();
}
finally {
this.lock.readLock().unlock();
}
}
protected void loadContext(final AbstractRDbgController r,
final ProgressMonitor m) throws StatusException {
this.lock.writeLock().lock();
try {
if (!r.isSuspended() || r.getHotTasksState() > 1) {
return;
}
this.detailLoaded= true;
if (this.stamp == r.getChangeStamp()) {
this.dbgFrameContext= r.evalFrameContext(this.dbgFrame.getPosition(), m);
CombinedRElement element= null;
if (this.dbgFrame.getPosition() >= 0) {
final CombinedRReference ref= ICombinedRDataAdapter.createReference(
getHandle(), this.elementName,
RObject.TYPE_ENVIRONMENT, RObject.CLASSNAME_ENVIRONMENT );
element= this.thread.resolveReference(ref, this.stamp, m);
}
if (element != null) {
this.frameVariable= new RElementVariable(element, this.thread, this.stamp, null);
this.variables= this.frameVariable.getValue(m);
}
}
}
catch (final CoreException e) {
RDebugCorePlugin.log(new Status(IStatus.ERROR, RDebugCorePlugin.BUNDLE_ID, 0,
"An error occured when updating the debug context (position "+this.dbgFrame.getPosition()+").", e));
}
finally {
try {
this.contextCondition.signalAll();
}
finally {
this.lock.writeLock().unlock();
}
}
}
@Override
public <T> @Nullable T getAdapter(final Class<T> type) {
if (type == IRStackFrame.class) {
return (T) this;
}
if (type == RBreakpointStatus.class) {
return (T) this.breakpointStatus;
}
if (type == IBreakpoint.class) {
final RBreakpointStatus breakpointStatus= this.breakpointStatus;
return (breakpointStatus != null) ? (T) breakpointStatus.getBreakpoint() : null;
}
if (type == IModelElement.class) {
final RElementVariable variable= this.frameVariable;
return (variable != null) ? (T) this.frameVariable.getElement() : null;
}
return super.getAdapter(type);
}
@Override
public String toString() {
final StringBuilder sb= new StringBuilder(getClass().getName());
sb.append("\n\t"); //$NON-NLS-1$
sb.append("position= ").append(this.dbgFrame.getPosition()); //$NON-NLS-1$
sb.append("\n\t"); //$NON-NLS-1$
sb.append("fileName= ").append(this.dbgFrame.getFileName()); //$NON-NLS-1$
sb.append("\n\t"); //$NON-NLS-1$
sb.append("exprSrcref= ").append(getInfoLineNumber()); //$NON-NLS-1$
return sb.toString();
}
}