/*=============================================================================# # Copyright (c) 2008-2016 Stephan Wahlbrink (WalWare.de) and others. # All rights reserved. This program and the accompanying materials # are made available under the terms of the GNU Lesser General Public License # v2.1 which accompanies this distribution, and is available at # http://www.gnu.org/licenses/lgpl.html # # Contributors: # Stephan Wahlbrink - initial API and implementation #=============================================================================*/ package de.walware.rj.server.jri; import static de.walware.rj.server.jri.JRIServerErrors.CODE_DBG_CONTEXT; import static de.walware.rj.server.jri.JRIServerErrors.CODE_DBG_DEBUG; import static de.walware.rj.server.jri.JRIServerErrors.LOGGER; import java.io.File; import java.lang.reflect.Field; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import org.rosuda.JRI.REXP; import org.rosuda.JRI.Rengine; import de.walware.rj.RjException; import de.walware.rj.server.RjsException; import de.walware.rj.server.RjsStatus; import de.walware.rj.server.dbg.CallStack; import de.walware.rj.server.dbg.CtrlReport; import de.walware.rj.server.dbg.DbgEnablement; import de.walware.rj.server.dbg.DbgFilterState; import de.walware.rj.server.dbg.DbgListener; import de.walware.rj.server.dbg.DbgRequest; import de.walware.rj.server.dbg.Frame; import de.walware.rj.server.dbg.FrameContext; import de.walware.rj.server.dbg.FrameContextDetailRequest; import de.walware.rj.server.dbg.FrameRef; import de.walware.rj.server.dbg.SetDebugReport; import de.walware.rj.server.dbg.SetDebugRequest; import de.walware.rj.server.dbg.Srcref; import de.walware.rj.server.dbg.TracepointEvent; final class JRIServerDbg { private static final class CallStackFrame extends Frame { public CallStackFrame(final int position, final String call) { super(position, call); } void setHandle(final long handle) { this.handle = handle; } void setFileName(final String fileName) { this.fileName = fileName; } void setFileTimestamp(final long fileTimestamp) { this.fileTimestamp = fileTimestamp; } void setExprSrcref(final int[] srcref) { this.exprSrcref = srcref; } } private final JRIServer server; private final Rengine rEngine; private final JRIServerRni rni; private final ServerUtils utils; public final long source_SymP; public final long srcfile_SymP; public final long srcref_SymP; public final long timestamp_SymP; private final long linesSymP; private final long linesSrcrefSymP; private final long sysNFrameFunP; private final long sysFrameFunP; private final long sysFramesFunP; private final long sysCallFunP; private final long sysCallsFunP; private final long sysFunctionFunP; private final long sysNFrameCallP; private final long sysFramesCallP; private final long sysCalls0CallP; private final long sysCall0CallP; private final long getTopFrameCallP; private final long isFunP; private final long traceableStringP; private final long enableTraceingCallP; private final long disableTraceingCallP; private final long enableDebuggingCallP; private final long disableDebuggingCallP; private int disabled; private int savedTracingState; private int savedDebuggingState; private final DbgListener dbgListener; private boolean stepFilterEnabled = !Boolean.parseBoolean( System.getProperty("de.walware.rj.dbg.stepfilter.disabled")); //$NON-NLS-1$ private boolean isSuspended; // browser prompt private int suspendedNFrame; private long suspendedFrameP; private boolean deferredSuspend; private final List<Long> contextTmpDebugFrames = new ArrayList<>(); private final List<Long> contextTmpDebugFuns = new ArrayList<>(); private boolean checkToRelease; private final List<Long> toRelease = new ArrayList<>(); private final TracepointManager tracepointManager; public JRIServerDbg(final JRIServer server, final JRIServerRni rni, final ServerUtils utils) throws RjsException { this.server= server; this.rEngine = rni.getREngine(); this.rni = rni; this.utils = utils; this.dbgListener= server; final int savedProtected= this.rni.saveProtected(); try { this.linesSymP= this.rEngine.rniInstallSymbol("lines"); //$NON-NLS-1$ this.linesSrcrefSymP= this.rEngine.rniInstallSymbol("linesSrcref"); //$NON-NLS-1$ this.source_SymP= this.rEngine.rniInstallSymbol("source"); //$NON-NLS-1$ this.srcfile_SymP= this.rEngine.rniInstallSymbol("srcfile"); //$NON-NLS-1$ this.srcref_SymP= this.rEngine.rniInstallSymbol("srcref"); //$NON-NLS-1$ this.timestamp_SymP= this.rEngine.rniInstallSymbol("timestamp"); //$NON-NLS-1$ this.sysNFrameFunP= this.rni.checkAndPreserve(this.rEngine.rniEval( this.rEngine.rniInstallSymbol("sys.nframe"), //$NON-NLS-1$ this.rni.Base_EnvP )); this.sysFramesFunP= this.rni.checkAndPreserve(this.rEngine.rniEval( this.rEngine.rniInstallSymbol("sys.frames"), //$NON-NLS-1$ this.rni.Base_EnvP )); this.sysFrameFunP= this.rni.checkAndPreserve(this.rEngine.rniEval( this.rEngine.rniInstallSymbol("sys.frame"), //$NON-NLS-1$ this.rni.Base_EnvP )); this.sysCallFunP= this.rni.checkAndPreserve(this.rEngine.rniEval( this.rEngine.rniInstallSymbol("sys.call"), //$NON-NLS-1$ this.rni.Base_EnvP )); this.sysCallsFunP= this.rni.checkAndPreserve(this.rEngine.rniEval( this.rEngine.rniInstallSymbol("sys.calls"), //$NON-NLS-1$ this.rni.Base_EnvP )); this.sysFunctionFunP = this.rni.checkAndPreserve(this.rEngine.rniEval( this.rEngine.rniInstallSymbol("sys.function"), //$NON-NLS-1$ this.rni.Base_EnvP )); this.sysNFrameCallP= this.rni.checkAndPreserve(this.rEngine.rniCons( this.sysNFrameFunP, this.rni.NULL_P, 0, true )); this.sysFramesCallP= this.rni.checkAndPreserve(this.rEngine.rniCons( this.sysFramesFunP, this.rni.NULL_P, 0, true )); this.sysCalls0CallP= this.rni.checkAndPreserve(this.rEngine.rniCons( this.rEngine.rniEval(this.rni.protect(this.rEngine.rniCons( this.rni.function_SymP, this.rEngine.rniCons( this.rni.NULL_P, this.rEngine.rniCons( this.rEngine.rniCons(this.sysCallsFunP, this.rni.NULL_P, 0, true ), this.rni.NULL_P, 0, true), 0, false ), 0, true )), this.rni.Base_EnvP ), this.rni.NULL_P, 0, true )); this.sysCall0CallP= this.rni.checkAndPreserve(this.rEngine.rniCons( this.rEngine.rniEval(this.rni.protect(this.rEngine.rniCons( this.rni.function_SymP, this.rEngine.rniCons( this.rni.NULL_P, this.rEngine.rniCons( this.rEngine.rniCons(this.sysCallFunP, this.rni.NULL_P, 0, true ), this.rni.NULL_P, 0, true), 0, false ), 0, true )), this.rni.Base_EnvP ), this.rni.NULL_P, 0, true )); this.getTopFrameCallP= this.rni.checkAndPreserve(this.rEngine.rniCons( this.sysFrameFunP, this.rEngine.rniCons( this.sysNFrameCallP, this.rni.NULL_P, 0, false), // which 0, true )); this.isFunP= this.rni.checkAndPreserve(this.rEngine.rniEval( this.rEngine.rniParse("methods::is", 1), //$NON-NLS-1$ this.rni.Base_EnvP )); this.traceableStringP= this.rni.checkAndPreserve( this.rEngine.rniPutString("traceable") ); //$NON-NLS-1$ { final long funP= this.rni.protect(this.rEngine.rniEval( this.rEngine.rniInstallSymbol("tracingState"), //$NON-NLS-1$ this.rni.Base_EnvP )); if (funP != 0) { this.enableTraceingCallP= this.rni.checkAndPreserve(this.rEngine.rniCons( funP, this.rEngine.rniCons( this.rni.TRUE_BoolP, this.rni.NULL_P, this.rni.on_SymP, false ), 0, true )); this.disableTraceingCallP= this.rni.checkAndPreserve(this.rEngine.rniCons( funP, this.rEngine.rniCons( this.rni.FALSE_BoolP, this.rni.NULL_P, this.rni.on_SymP, false ), 0, true )); } else { this.enableTraceingCallP= 0; this.disableTraceingCallP= 0; } } { final long funP= this.rni.protect(this.rEngine.rniEval( this.rEngine.rniInstallSymbol("debuggingState"), //$NON-NLS-1$ this.rni.Base_EnvP )); if (funP != 0) { this.enableDebuggingCallP= this.rni.checkAndPreserve(this.rEngine.rniCons( funP, this.rEngine.rniCons( this.rni.TRUE_BoolP, this.rni.NULL_P, this.rni.on_SymP, false ), 0, true )); this.disableDebuggingCallP= this.rni.checkAndPreserve(this.rEngine.rniCons( funP, this.rEngine.rniCons( this.rni.FALSE_BoolP, this.rni.NULL_P, this.rni.on_SymP, false ), 0, true )); } else { this.enableDebuggingCallP= 0; this.disableDebuggingCallP= 0; } } this.tracepointManager= new TracepointManager(this, rni); if (LOGGER.isLoggable(Level.FINER)) { final StringBuilder sb= new StringBuilder("Dbg Pointers:"); final Field[] fields= getClass().getDeclaredFields(); for (final Field field : fields) { final String name= field.getName(); if (name.endsWith("P") && Long.TYPE.equals(field.getType())) { sb.append("\n\t"); sb.append(name.substring(0, name.length() - 1)); sb.append("= "); try { final long p= field.getLong(this); sb.append("0x"); sb.append(Long.toHexString(p)); } catch (final Exception e) { sb.append(e.getMessage()); } } } LOGGER.log(Level.FINER, sb.toString()); } } finally { this.rni.looseProtected(savedProtected); } } public void beginSafeMode() { this.disabled++; if (this.savedTracingState == 0 && this.enableTraceingCallP != 0) { final long p= this.rEngine.rniEval(this.disableTraceingCallP, this.rni.rniSafeBaseExecEnvP ); if (p != 0) { this.savedTracingState= this.rEngine.rniIsTrue(p) ? 1 : -1; } } if (this.savedDebuggingState == 0 && this.enableDebuggingCallP != 0) { final long p= this.rEngine.rniEval(this.disableDebuggingCallP, this.rni.rniSafeBaseExecEnvP ); if (p != 0) { this.savedDebuggingState= this.rEngine.rniIsTrue(p) ? 1 : -1; } } } public void endSafeMode() { this.disabled--; if (this.disabled != 0) { return; } if (this.deferredSuspend) { doSuspend(); } if (this.savedDebuggingState != 0) { if (this.savedDebuggingState == 1) { this.rEngine.rniEval(this.enableDebuggingCallP, this.rni.rniSafeBaseExecEnvP ); } this.savedDebuggingState= 0; } if (this.savedTracingState != 0) { if (this.savedTracingState == 1) { this.rEngine.rniEval(this.enableTraceingCallP, this.rni.rniSafeBaseExecEnvP ); } this.savedTracingState= 0; } } public synchronized void setEnablement(final DbgEnablement request) { this.tracepointManager.setBreakpointsEnabled(request.getBreakpointsEnabled()); } public TracepointManager getTracepointManager() { return this.tracepointManager; } public String handleBrowserPrompt(final String prompt) { if (this.disabled > 0) { return "c\n"; //$NON-NLS-1$ } beginSafeMode(); try { final long srcrefP; { final long call = this.rEngine.rniEval(this.sysCall0CallP, this.rni.rniSafeBaseExecEnvP ); srcrefP = (call != 0) ? this.rEngine.rniGetAttrBySym(call, this.srcref_SymP) : 0; } if (srcrefP != 0) { final long p = this.rEngine.rniGetAttrBySym(srcrefP, this.rni.what_SymP); if (p != 0) { final String s = this.rEngine.rniGetString(p); if (s != null && s.length() > 8 && s.startsWith("browser:")) { //$NON-NLS-1$ return s.substring(8)+'\n'; } } } clearContext(); this.tracepointManager.handleSuspended(srcrefP); this.isSuspended= true; this.suspendedNFrame= getNFrame(); this.suspendedFrameP= (this.suspendedNFrame != 0) ? getTopFrame() : this.rni.Global_EnvP; } finally { endSafeMode(); } return null; } public void rCancelled() { this.tracepointManager.handleCancelled(); } /** * Executes dbg commands from R * * @param commandId the specified command id * @param argsP pointer to command specified arguments * @return pointer to answer or <code>0</code> * @throws RjException if an error occurred when executing the command */ public long execRJCommand(final String commandId, final long argsP) throws RjException { if (this.disabled > 0) { return 0; } this.rni.newDataLevel(255, Integer.MAX_VALUE, Integer.MAX_VALUE); final int savedProtected = this.rni.saveProtected(); try { this.rni.protect(argsP); switch (commandId) { case "checkBreakpoint": //$NON-NLS-1$ return this.tracepointManager.checkBreakpoint(argsP); case "checkTB": //$NON-NLS-1$ return this.tracepointManager.checkTB(argsP); case "checkEB": //$NON-NLS-1$ return this.tracepointManager.checkEB(argsP); } } finally { this.rni.looseProtected(savedProtected); this.rni.exitDataLevel(); } return 0; } public RjsStatus setFilterState(final DbgFilterState state) { synchronized (this) { this.stepFilterEnabled = state.stepFilterEnabled(); } return RjsStatus.OK_STATUS; } public boolean isStepFilterEnabled() { return this.stepFilterEnabled; } /** * Returns the list of the current frames * * @return the frame list or <code>null</code> if not available * @throws RjsException */ public CallStack getCallStack() throws RjsException { return loadCallStack(); } private CallStack loadCallStack() throws RjsException { int n = getNFrame(); if (n < 0) { return null; } final List<CallStackFrame> list = new ArrayList<>(n + 1); { long cdr = this.rni.evalExpr(this.sysCalls0CallP, this.rni.rniSafeGlobalExecEnvP, CODE_DBG_CONTEXT ); if (cdr == 0 || this.rEngine.rniExpType(cdr) != REXP.LISTSXP) { if (n == 0 && cdr == this.rni.NULL_P) { cdr = 0; } else { return null; } } int i = 0; String call = null; while (true) { final CallStackFrame item = new CallStackFrame(i, call); call = null; long srcrefP = 0; if (cdr != 0) { final long car = this.rEngine.rniCAR(cdr); if (car != 0 && this.rEngine.rniExpType(car) == REXP.LANGSXP) { if (i < n) { call = this.rni.getSourceLine(car); } srcrefP = this.rEngine.rniGetAttrBySym(car, this.srcref_SymP); } } if (srcrefP != 0) { final long srcfileP = this.rEngine.rniGetAttrBySym(srcrefP, this.srcfile_SymP); if (srcfileP != 0) { final String fileName = getFileName(srcfileP); if (fileName != null) { item.setFileName(fileName); item.setFileTimestamp(getFileTimestamp(srcfileP)); } int[] srcref = getSrcrefObject(srcrefP); if (srcref != null) { final int[] add= getSrcrefObject(this.rEngine.rniGetVarBySym( this.linesSrcrefSymP, srcfileP, 0 )); if (add != null) { srcref = Srcref.add(srcref, add); } item.setExprSrcref(srcref); } } } list.add(item); if (++i > n) { n++; break; } if (call != null) { if (call.startsWith(".doTrace") //$NON-NLS-1$ || call.startsWith("rj:::.breakpoint(") ) { //$NON-NLS-1$ n= i; break; } if (call.startsWith("rj:::.checkError(")) { //$NON-NLS-1$ if (item.getCall() != null && item.getCall().startsWith("stop(")) { n= i - 1; list.remove(n); break; } n= i; break; } } cdr = this.rEngine.rniCDR(cdr); } } { long cdr = this.rEngine.rniEval(this.sysFramesCallP, this.rni.rniSafeBaseExecEnvP ); if (cdr != 0 && this.rEngine.rniExpType(cdr) == REXP.LISTSXP) { int i = 1; while (cdr != 0 && i < n) { final long car = this.rEngine.rniCAR(cdr); if (car != 0 && this.rEngine.rniExpType(car) == REXP.ENVSXP) { if (i < n) { list.get(i).setHandle(car); } } i++; cdr = this.rEngine.rniCDR(cdr); } } } return new CallStack(list, true); } /** * Loads the details of a frame * * @param request the request specifying the frame * @return the frame detail * @throws RjsException */ public FrameContext loadFrameDetail(final FrameContextDetailRequest request) throws RjsException { final int n = getNFrame(); if (n < 0 || request.getPosition() < 0 || request.getPosition() > n) { return null; } String call = null; if (request.getPosition() != 0) { final long currentCallP= this.rni.evalExpr(createSysCallCall(request.getPosition()), this.rni.rniSafeGlobalExecEnvP, CODE_DBG_CONTEXT ); if (currentCallP != 0) { call = this.rni.getSourceLine(currentCallP); } } int[] firstSrcref = null; int[] lastSrcref = null; int[] exprSrcref = null; long contextFunP = 0; long contextSrcrefP = 0; long contextSrcfileP = 0; { final long nextCallP= this.rEngine.rniEval((request.getPosition() < n) ? createSysCallCall(request.getPosition() + 1) : this.sysCall0CallP, this.rni.rniSafeGlobalExecEnvP ); if (nextCallP != 0) { contextSrcrefP = this.rEngine.rniGetAttrBySym(nextCallP, this.srcref_SymP); } } if (contextSrcrefP != 0) { exprSrcref = getSrcrefObject(contextSrcrefP); contextSrcfileP = this.rEngine.rniGetAttrBySym(contextSrcrefP, this.srcfile_SymP); } if (request.getPosition() != 0) { { // function final long currentFunP = this.rEngine.rniEval( this.rEngine.rniCons(this.sysFunctionFunP, this.rEngine.rniCons( this.rEngine.rniPutIntArray(new int[] { request.getPosition() }), this.rni.NULL_P, 0, false ), 0, true ), this.rni.rniSafeBaseExecEnvP ); if (currentFunP != 0) { contextFunP = getOrgFun(currentFunP); // { // package info // final long packageP = this.rEngine.rniGetAttr(infoFunP, "package"); // if (packageP != 0) { // String[] name = this.rEngine.rniGetStringArray(packageP); // if (name != null && name.length == 0) { // info[DataCmdItem.CONTEXT_DETAIL_FUN_PACKAGE] = new JRIVectorImpl<RStore>(new JRICharacterDataImpl(name), null, null); // } // } // } } } if (contextFunP != 0) { final long bodyP = this.rEngine.rniGetCloBodyExpr(contextFunP); if (bodyP != 0) { if (contextSrcfileP == 0) { final long srcrefP = this.rEngine.rniGetAttr(bodyP, "wholeSrcref"); if (srcrefP != 0) { final long srcfileP = this.rEngine.rniGetAttrBySym(srcrefP, this.srcfile_SymP); if (srcfileP != 0) { contextSrcrefP = srcrefP; contextSrcfileP = srcfileP; } } } { long srcrefP = this.rEngine.rniGetAttrBySym(bodyP, this.srcref_SymP); if (srcrefP != 0) { final long[] srcrefItemsP = this.rEngine.rniGetVector(srcrefP); if (srcrefItemsP != null && srcrefItemsP.length > 0) { srcrefP = srcrefItemsP[0]; final long srcfileP = this.rEngine.rniGetAttrBySym(srcrefP, this.srcfile_SymP); if (srcfileP != 0) { if (contextSrcrefP == 0) { contextSrcrefP = srcrefP; contextSrcfileP = srcfileP; } if (srcfileP == contextSrcfileP) { firstSrcref = getSrcrefObject(srcrefP); lastSrcref = getSrcrefObject( srcrefItemsP[srcrefItemsP.length-1] ); } } } } } if (contextSrcrefP == 0) { contextSrcfileP = this.rEngine.rniGetAttrBySym(bodyP, this.srcfile_SymP); if (contextSrcfileP == 0) { contextSrcfileP = this.rEngine.rniGetAttrBySym(contextFunP, this.srcfile_SymP); } } } } } // file info String fileName = null; long fileTimestamp = 0; String fileEncoding = null; String filePath = null; if (contextSrcfileP != 0) { fileName = getFileName(contextSrcfileP); if (fileName != null) { filePath = getFilePath(contextSrcfileP, 0); // contextSrcrefP ? fileEncoding = getFileEncoding(contextSrcfileP); fileTimestamp = getFileTimestamp(contextSrcfileP); } } // source code int sourceType = 0; String sourceCode = null; int[] sourceSrcref = null; { if (contextSrcfileP != 0) { final long sourceP= this.rEngine.rniGetVarBySym( this.linesSymP, contextSrcfileP, 0 ); if (sourceP != 0) { final String[] sourceLines = this.rEngine.rniGetStringArray(sourceP); if (sourceLines != null && sourceLines.length > 0) { sourceType = FrameContext.SOURCETYPE_1_LINES; sourceCode = this.utils.concat(sourceLines, '\n'); sourceSrcref= getSrcrefObject(this.rEngine.rniGetVarBySym( this.linesSrcrefSymP, contextSrcfileP, 0 )); } } } if (fileName != null && fileTimestamp != 0) { final File file = new File(fileName); { final long modified = file.lastModified()/1000; if (Math.abs(modified - fileTimestamp) == 3600) { fileTimestamp = modified; } } if (sourceType == 0 && contextSrcfileP != 0 && file.exists() && file.lastModified()/1000 == fileTimestamp) { String encoding = fileEncoding; if (encoding == null) { encoding = "UTF-8"; //$NON-NLS-1$ } Charset charset; if (encoding.equals("native.enc")) { //$NON-NLS-1$ charset = Charset.defaultCharset(); } else { try { charset = Charset.forName(encoding); } catch (final Exception e) { charset = Charset.forName("UTF-8"); //$NON-NLS-1$ } } final String content = this.utils.readFile(file, charset); if (content != null && file.lastModified()/1000 == fileTimestamp) { sourceType = FrameContext.SOURCETYPE_1_FILE; sourceCode = content; } } } if (sourceType == 0 && contextFunP != 0) { final long sourceP= this.rEngine.rniGetAttrBySym(contextFunP, this.source_SymP); if (sourceP != 0) { final String[] sourceLines = this.rEngine.rniGetStringArray(sourceP); if (sourceLines != null && sourceLines.length > 0) { sourceType = FrameContext.SOURCETYPE_2_LINES; sourceCode = this.utils.concat(sourceLines, '\n'); } } } if (sourceType == 0 && contextFunP != 0) { final String[] sourceLines = this.rni.getSourceLines(contextFunP); if (sourceLines != null && sourceLines.length > 0) { sourceType = FrameContext.SOURCETYPE_3_DEPARSE; sourceCode = this.utils.concat(sourceLines, '\n'); } } if (sourceType != 0) { if (sourceType >= 3) { int idx; sourceCode = ((call != null && (idx = call.indexOf('(')) > 0 ) ? call.substring(0, idx) : "f") + " <- " + sourceCode; //$NON-NLS-1$ //$NON-NLS-2$ } } } return new FrameContext(request.getPosition(), call, fileName, fileTimestamp, fileEncoding, filePath, sourceType, sourceCode, sourceSrcref, firstSrcref, lastSrcref, exprSrcref ); } public SetDebugReport setDebug(final SetDebugRequest request) throws RjsException { final boolean changed; switch (request.getType()) { case SetDebugRequest.FRAME: changed = doSetDebug(request.getHandle(), request.getDebug(), request.isTemp()); break; case SetDebugRequest.FUNCTION: changed = doSetDebug(request.getName(), request.getDebug(), request.isTemp()); break; default: throw new RjsException(0, "Unsupported debug request type 0x" + Integer.toHexString(request.getType())); } return new SetDebugReport(changed); } public void requestSuspend() { if (this.disabled > 0) { this.deferredSuspend = true; } else { doSuspend(); } } private void doSuspend() { this.deferredSuspend = false; final int savedProtected = this.rni.saveProtected(); try { final long[] frames = searchSuspendHandles(); for (int i = 0; i < frames.length; i++) { if (frames[i] == 0) { break; } doSetDebug(frames[i], 1, true); } } catch (final Exception e) { JRIServerErrors.LOGGER.log(Level.SEVERE, "Setting suspend request failed.", e); } finally { this.rni.looseProtected(savedProtected); } } private boolean doSetDebug(long frameP, final int v, final boolean temp) { if (v != 0 && this.rni.isInternEnv(frameP)) { return false; } if (frameP == this.rni.Global_EnvP) { frameP = 0; } final boolean changed = this.rEngine.rniSetDebug(frameP, v); final Long handle = Long.valueOf(frameP); if (v == 0 || !temp) { this.contextTmpDebugFrames.remove(handle); } else if (changed && !this.contextTmpDebugFrames.contains(handle)) { // && enable && temp this.contextTmpDebugFrames.add(handle); } return changed; } private long[] searchSuspendHandles() throws RjsException { final long[] handles = new long[8]; final CallStack stack = loadCallStack(); final int n = stack.getFrames().size() - 1; if (n < 0) { return handles; } if (n == 0) { handles[0] = this.rni.Global_EnvP; return handles; } final int last = Math.max(n-3, 0); if (!this.stepFilterEnabled) { for (int i = n-1; i >= last; i--) { final Frame frame0 = stack.getFrames().get(i); add(handles, frame0); } } else { for (int i = n-1; i >= last; i--) { final Frame frame0 = stack.getFrames().get(i); switch (frame0.getFlags() & 0xff) { case (CallStack.FLAG_COMMAND | 0): case (CallStack.FLAG_SOURCE | 0): case (CallStack.FLAG_COMMAND | 1): case (CallStack.FLAG_SOURCE | 1): i -= 1; continue; case (CallStack.FLAG_COMMAND | 2): case (CallStack.FLAG_SOURCE | 2): case (CallStack.FLAG_COMMAND | 3): case (CallStack.FLAG_SOURCE | 3): i -= 1; break; default: if (i > 0 && frame0.getCall() != null && frame0.getCall().startsWith("eval(")) { //$NON-NLS-1$ final Frame frame1 = stack.getFrames().get(i-1); if (frame1.getCall() != null && frame1.getCall().startsWith("eval(")) { //$NON-NLS-1$ i -= 1; break; } } } add(handles, frame0); } } return handles; } private void add(final long[] frameIds, final Frame frame) { long handle = frame.getHandle(); if (handle == 0) { if (frame.getPosition() == 0) { handle = this.rni.Global_EnvP; } else { return; } } for (int i = 0; i < frameIds.length; i++) { if (frameIds[i] == handle) { return; } if (frameIds[i] == 0) { frameIds[i] = handle; return; } } } private boolean doSetDebug(final String fName, final int v, final boolean temp) throws RjsException { if (fName == null) { return false; } final long frameP= getTopFrame(); long funP; { long exprP = this.rEngine.rniParse(fName, 1); final long[] list; if (exprP != 0 && (list = this.rEngine.rniGetVector(exprP)) != null && list.length == 1) { exprP = this.rni.protect(list[0]); } else { throw new RjsException(CODE_DBG_DEBUG | 0x2, "Invalid function name."); } if (this.rEngine.rniGetLength(exprP) == 1 && this.rEngine.rniExpType(exprP) == REXP.SYMSXP) { funP = this.rEngine.rniFindFunBySym(exprP, frameP); } else { funP= this.rni.evalExpr(exprP, frameP, CODE_DBG_DEBUG | 0x4); } } final int type; if (funP != 0 && ((type = this.rEngine.rniExpType(funP)) == REXP.CLOSXP || type == REXP.SPECIALSXP || type == REXP.BUILTINSXP )) { final boolean changed = this.rEngine.rniSetDebug(funP, v); final Long handle = Long.valueOf(funP); if (v == 0 || !temp) { if (this.contextTmpDebugFuns.remove(handle)) { this.rEngine.rniRelease(funP); } } else if (changed && !this.contextTmpDebugFuns.contains(handle)) { this.rEngine.rniPreserve(funP); this.contextTmpDebugFuns.add(handle); } return changed; } else { throw new RjsException(CODE_DBG_DEBUG | 0x6, "No function found."); } } public CtrlReport exec(final DbgRequest request) throws RjsException { switch (request.getOp()) { case DbgRequest.RESUME: if (this.isSuspended) { scheduleResume("c"); //$NON-NLS-1$ return CtrlReport.createRequestExecuted(DbgRequest.RESUME); } return CtrlReport.createRequestNotApplicable(this.isSuspended); case DbgRequest.STEP_INTO: if (this.isSuspended) { if (!this.utils.isRVersionEqualGreater(3, 1)) { return CtrlReport.createRequestNotSupported(this.isSuspended); } scheduleResume("s"); //$NON-NLS-1$ return CtrlReport.createRequestExecuted(DbgRequest.STEP_INTO); } return CtrlReport.createRequestNotApplicable(this.isSuspended); case DbgRequest.STEP_OVER: if (this.isSuspended) { scheduleResume("n"); //$NON-NLS-1$ return CtrlReport.createRequestExecuted(DbgRequest.STEP_OVER); } return CtrlReport.createRequestNotApplicable(this.isSuspended); case DbgRequest.STEP_RETURN: if (this.isSuspended) { final CallStack callStack= getCallStack(); final Object target= ((DbgRequest.StepReturn) request).getTarget(); Frame targetFrame= null; if (target instanceof FrameRef.ByHandle) { final long targetHandle= ((FrameRef.ByHandle) target).getHandle(); targetFrame= callStack.findFrame(targetHandle); } if (targetFrame != null && !targetFrame.isTopFrame()) { doSetDebug(targetFrame.getHandle(), 1, false); scheduleResume("c"); //$NON-NLS-1$ // scheduleResume("browserSetDebug(n= " + (relPos) + "L); c"); //$NON-NLS-1$ //$NON-NLS-2$ return CtrlReport.createRequestExecuted(DbgRequest.STEP_RETURN); } } return CtrlReport.createRequestNotApplicable(this.isSuspended); default: return CtrlReport.createRequestNotSupported(this.isSuspended); } } private void scheduleResume(final String rCommand) { this.server.dorAppend2SConsoleAnswer(rCommand); } public void addToRelease(final long p) { synchronized (this.toRelease) { this.checkToRelease= true; this.toRelease.add(p); } } public void clearContext() { this.isSuspended= false; if (this.checkToRelease) { synchronized (this.toRelease) { this.checkToRelease = false; for (int i = 0; i < this.toRelease.size(); i++) { this.rEngine.rniRelease(this.toRelease.get(i).longValue()); } this.toRelease.clear(); } } this.deferredSuspend= false; if (!this.contextTmpDebugFuns.isEmpty()) { try { for (final Long handle : this.contextTmpDebugFuns) { this.rEngine.rniSetDebug(handle.longValue(), 0); this.rEngine.rniRelease(handle.longValue()); } } finally { this.contextTmpDebugFuns.clear(); } } if (!this.contextTmpDebugFrames.isEmpty() || this.suspendedNFrame != 0) { beginSafeMode(); try { if (!this.contextTmpDebugFrames.isEmpty()) { try { long cdr= this.rni.evalExpr(this.sysFramesCallP, this.rni.rniSafeBaseExecEnvP, CODE_DBG_DEBUG | 0xe ); final List<Long> stack = new ArrayList<>(this.contextTmpDebugFrames.size()); boolean topInStack = false; if (cdr != 0 && this.rEngine.rniExpType(cdr) == REXP.LISTSXP) { while (cdr != 0) { final long car = this.rEngine.rniCAR(cdr); if (car != 0 && this.rEngine.rniExpType(car) == REXP.ENVSXP) { final Long handle = Long.valueOf(car); if (!stack.contains(handle) && this.rEngine.rniGetDebug(car) != 0) { if (this.contextTmpDebugFrames.contains(handle)) { stack.add(handle); topInStack = true; } else { topInStack = false; } } } cdr = this.rEngine.rniCDR(cdr); } } if (topInStack) { stack.remove(stack.size()-1); } for (int i = stack.size()-1; i >= 0; i--) { this.rEngine.rniSetDebug(stack.get(i).longValue(), 0); } } catch (final Throwable e) { LOGGER.log(Level.SEVERE, "An error occured when checking suspended frames", e); } finally { this.contextTmpDebugFrames.clear(); } } if (this.suspendedNFrame != 0) { final int nFrame= getNFrame(); if (nFrame < this.suspendedNFrame) { this.suspendedNFrame= 0; this.suspendedFrameP= 0; } } } finally { endSafeMode(); } } } void sendNotification(final TracepointEvent notification) { if (this.dbgListener != null) { this.dbgListener.handle(notification); } } void addAllStackEnvs(final List<Long> envs) throws RjsException { this.rni.addAllEnvs(envs, this.rni.Global_EnvP); { long cdr= this.rEngine.rniEval(this.sysFramesCallP, this.rni.rniSafeBaseExecEnvP ); if (cdr != 0 && this.rEngine.rniExpType(cdr) == REXP.LISTSXP) { while (cdr != 0) { final long car= this.rEngine.rniCAR(cdr); if (car != 0 && this.rEngine.rniExpType(car) == REXP.ENVSXP) { this.rni.addAllEnvs(envs, car); } cdr= this.rEngine.rniCDR(cdr); } } } } int getNFrame() { final long p= this.rEngine.rniEval(this.sysNFrameCallP, this.rni.rniSafeBaseExecEnvP ); if (p != 0) { final int[] na= this.rEngine.rniGetIntArray(p); if (na != null && na.length > 0) { return na[0]; } } return -1; } long getTopFrame() { return this.rEngine.rniEval(this.getTopFrameCallP, this.rni.rniSafeBaseExecEnvP ); } int getSuspendedNFrame() { return this.suspendedNFrame; } long getSuspendedFrame() { return this.suspendedFrameP; } long createSysFrameCall(final int which) { return this.rEngine.rniCons( this.sysFrameFunP, this.rEngine.rniCons( this.rEngine.rniPutIntArray(new int[] { which }), this.rni.NULL_P, this.rni.which_SymP, false ), 0, true); } long createSysCallCall(final int which) { return this.rEngine.rniCons( this.sysCallFunP, this.rEngine.rniCons( this.rEngine.rniPutIntArray(new int[] { which }), this.rni.NULL_P, this.rni.which_SymP, false ), 0, true); } long getOrgFun(final long funP) { final long p = this.rEngine.rniGetAttrBySym(funP, this.rni.original_SymP); if (p != 0 && p != funP /*&& isTraceable(funP)*/) { if (this.rEngine.rniExpType(p) != REXP.CLOSXP) { return 0; } return p; } return funP; } boolean isTraceable(final long funP) { final long p= this.rEngine.rniEval(this.rEngine.rniCons( this.isFunP, this.rEngine.rniCons( funP, this.rEngine.rniCons( this.traceableStringP, this.rni.NULL_P, 0, false ), 0, false ), 0, true), this.rni.rniSafeGlobalExecEnvP ); return (p != 0 && this.rEngine.rniIsTrue(p)); } long getExpr0SrcrefP(final long exprP) { if (exprP != 0) { final long listP= this.rEngine.rniGetAttrBySym(exprP, this.srcref_SymP); if (listP != 0) { return this.rEngine.rniGetVectorElt(listP, 0); } } return 0; } long getSrcfileEnvP(final long p) { // srcfileP or bodyP if (p != 0) { final long srcfileP= this.rEngine.rniGetAttrBySym(p, this.srcfile_SymP); if (srcfileP != 0 && this.rEngine.rniExpType(srcfileP) == REXP.ENVSXP) { return srcfileP; } } return 0; } String getFilePath(final long srcfileP, final long srcrefP) { long p= this.rEngine.rniGetVarBySym(this.rni.appFilePath_SymP, srcfileP, 0); if (p == 0 && (srcrefP == 0 || (p = this.rEngine.rniGetAttrBySym(srcrefP, this.rni.appFilePath_SymP)) == 0 )) { return null; } return this.rEngine.rniGetString(p); } String getFileName(final long srcfileP) { String filename; { final long p= this.rEngine.rniGetVarBySym(this.rni.filename_SymP, srcfileP, 0); if (p == 0 || (filename = this.rEngine.rniGetString(p)) == null || filename.length() == 0) { return null; } } if (filename.charAt(0) == '<') { return null; } if (filename.charAt(0) != '/' && filename.charAt(0) != '\\' && filename.indexOf(':') < 0) { { final long p= this.rEngine.rniGetVarBySym(this.rni.wd_SymP, srcfileP, 0); if (p != 0) { final String wd = this.rEngine.rniGetString(p); if (wd != null && wd.length() > 0) { filename = wd + File.separatorChar + filename; } } } } return this.utils.checkFilename(filename); } String getFileEncoding(final long srcfileP) { final long p= this.rEngine.rniGetVarBySym(this.rni.encoding_SymP, srcfileP, 0); if (p != 0) { return this.rEngine.rniGetString(p); } return null; } long getFileTimestamp(final long srcfileP) { final long p= this.rEngine.rniGetVarBySym(this.timestamp_SymP, srcfileP, 0); if (p != 0) { final double[] array = this.rEngine.rniGetDoubleArray(p); if (array != null && array.length > 0) { return (long) array[0]; } } return 0; } int[] getSrcrefObject(final long srcrefP) { if (srcrefP != 0) { final int[] array = this.rEngine.rniGetIntArray(srcrefP); if (array != null && array.length >= 6 && array[0] != Integer.MIN_VALUE) { return array; } } return null; } }