mirror of
https://github.com/eclipse-cdt/cdt
synced 2025-04-23 14:42:11 +02:00
Bug 341731 - Show values returned from function calls when doing a
step-return operation Change-Id: I4ac5c64a940ffcbe75b21618a74f2c4eba93d27e Signed-off-by: Marc Khouzam <marc.khouzam@ericsson.com> Reviewed-on: https://git.eclipse.org/r/15377 Reviewed-by: Marc-Andre Laperle <marc-andre.laperle@ericsson.com> IP-Clean: Marc-Andre Laperle <marc-andre.laperle@ericsson.com> Tested-by: Marc-Andre Laperle <marc-andre.laperle@ericsson.com> Reviewed-by: Mikhail Khodjaiants <mikhailkhod@googlemail.com> IP-Clean: Mikhail Khodjaiants <mikhailkhod@googlemail.com> Tested-by: Mikhail Khodjaiants <mikhailkhod@googlemail.com>
This commit is contained in:
parent
2186138b87
commit
2a935a9926
15 changed files with 875 additions and 174 deletions
|
@ -9,6 +9,7 @@
|
|||
* Freescale Semiconductor - initial API and implementation
|
||||
* Jens Elmenthaler (Verigy) - Added Full GDB pretty-printing support (bug 302121)
|
||||
* Marc Khouzam (Ericsson) - Add support disable "View Memory" action (bug 418710)
|
||||
* Marc Khouzam (Ericsson) - Turn off "watch" action for return values of methods (bug 341731)
|
||||
*******************************************************************************/
|
||||
package org.eclipse.cdt.dsf.gdb.internal.ui.viewmodel;
|
||||
|
||||
|
@ -48,6 +49,7 @@ import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenCountUpd
|
|||
import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenUpdate;
|
||||
import org.eclipse.debug.internal.ui.viewers.model.provisional.IHasChildrenUpdate;
|
||||
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta;
|
||||
import org.eclipse.debug.ui.actions.IWatchExpressionFactoryAdapter2;
|
||||
import org.eclipse.jface.util.PropertyChangeEvent;
|
||||
import org.eclipse.jface.viewers.TreePath;
|
||||
|
||||
|
@ -194,6 +196,15 @@ public class GdbVariableVMNode extends VariableVMNode {
|
|||
}
|
||||
return super.canViewInMemory();
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
@Override
|
||||
public Object getAdapter(Class adapter) {
|
||||
if (adapter.isAssignableFrom(IWatchExpressionFactoryAdapter2.class)) {
|
||||
return fGdbVariableExpressionFactory;
|
||||
}
|
||||
return super.getAdapter(adapter);
|
||||
}
|
||||
};
|
||||
|
||||
private static boolean isConvenienceVariable(String expr) {
|
||||
|
@ -229,6 +240,25 @@ public class GdbVariableVMNode extends VariableVMNode {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* A factory to control the "Watch" action for GDB variables.
|
||||
*/
|
||||
protected class GdbVariableExpressionFactory extends VariableExpressionFactory {
|
||||
@Override
|
||||
public boolean canCreateWatchExpression(Object element) {
|
||||
if (element instanceof VariableExpressionVMC) {
|
||||
String expression = ((VariableExpressionVMC)element).getExpression();
|
||||
if (isConvenienceVariable(expression)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return super.canCreateWatchExpression(element);
|
||||
}
|
||||
}
|
||||
|
||||
final protected VariableExpressionFactory fGdbVariableExpressionFactory = new GdbVariableExpressionFactory();
|
||||
|
||||
/**
|
||||
* The special context representing more children to be available.
|
||||
*
|
||||
|
|
|
@ -10,13 +10,17 @@
|
|||
* Ericsson - Modified for handling of multiple execution contexts
|
||||
* Axel Mueller - Bug 306555 - Add support for cast to type / view as array (IExpressions2)
|
||||
* Jens Elmenthaler (Verigy) - Added Full GDB pretty-printing support (bug 302121)
|
||||
* Marc Khouzam (Ericsson) - Added support for expression aliases for return values of functions (bug 341731)
|
||||
*******************************************************************************/
|
||||
package org.eclipse.cdt.dsf.mi.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Hashtable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.eclipse.cdt.core.IAddress;
|
||||
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
|
||||
|
@ -35,7 +39,11 @@ import org.eclipse.cdt.dsf.debug.service.IFormattedValues;
|
|||
import org.eclipse.cdt.dsf.debug.service.IMemory.IMemoryChangedEvent;
|
||||
import org.eclipse.cdt.dsf.debug.service.IMemory.IMemoryDMContext;
|
||||
import org.eclipse.cdt.dsf.debug.service.IRegisters.IRegisterDMContext;
|
||||
import org.eclipse.cdt.dsf.debug.service.IRunControl;
|
||||
import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerSuspendedDMEvent;
|
||||
import org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext;
|
||||
import org.eclipse.cdt.dsf.debug.service.IRunControl.IExitedDMEvent;
|
||||
import org.eclipse.cdt.dsf.debug.service.IRunControl.IResumedDMEvent;
|
||||
import org.eclipse.cdt.dsf.debug.service.IRunControl.ISuspendedDMEvent;
|
||||
import org.eclipse.cdt.dsf.debug.service.IRunControl.StateChangeReason;
|
||||
import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMContext;
|
||||
import org.eclipse.cdt.dsf.debug.service.command.CommandCache;
|
||||
|
@ -49,12 +57,16 @@ import org.eclipse.cdt.dsf.mi.service.command.commands.ExprMetaGetChildCount;
|
|||
import org.eclipse.cdt.dsf.mi.service.command.commands.ExprMetaGetChildren;
|
||||
import org.eclipse.cdt.dsf.mi.service.command.commands.ExprMetaGetValue;
|
||||
import org.eclipse.cdt.dsf.mi.service.command.commands.ExprMetaGetVar;
|
||||
import org.eclipse.cdt.dsf.mi.service.command.events.IMIDMEvent;
|
||||
import org.eclipse.cdt.dsf.mi.service.command.events.MIFunctionFinishedEvent;
|
||||
import org.eclipse.cdt.dsf.mi.service.command.events.MIStoppedEvent;
|
||||
import org.eclipse.cdt.dsf.mi.service.command.output.ExprMetaGetAttributesInfo;
|
||||
import org.eclipse.cdt.dsf.mi.service.command.output.ExprMetaGetChildCountInfo;
|
||||
import org.eclipse.cdt.dsf.mi.service.command.output.ExprMetaGetChildrenInfo;
|
||||
import org.eclipse.cdt.dsf.mi.service.command.output.ExprMetaGetValueInfo;
|
||||
import org.eclipse.cdt.dsf.mi.service.command.output.ExprMetaGetVarInfo;
|
||||
import org.eclipse.cdt.dsf.mi.service.command.output.MIDataEvaluateExpressionInfo;
|
||||
import org.eclipse.cdt.dsf.mi.service.command.output.MIFrame;
|
||||
import org.eclipse.cdt.dsf.service.AbstractDsfService;
|
||||
import org.eclipse.cdt.dsf.service.DsfServiceEventHandler;
|
||||
import org.eclipse.cdt.dsf.service.DsfServicesTracker;
|
||||
|
@ -65,6 +77,8 @@ import org.eclipse.core.runtime.IStatus;
|
|||
import org.eclipse.core.runtime.Status;
|
||||
import org.osgi.framework.BundleContext;
|
||||
|
||||
import com.ibm.icu.text.MessageFormat;
|
||||
|
||||
/**
|
||||
* This class implements a debugger expression evaluator as a DSF service. The
|
||||
* primary interface that clients of this class should use is IExpressions.
|
||||
|
@ -815,6 +829,116 @@ public class MIExpressions extends AbstractDsfService implements IMIExpressions,
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps track of aliases for return values of methods.
|
||||
*/
|
||||
private class ReturnValueAliasing {
|
||||
/**
|
||||
* Map of expression to alias. The expression is the name of the convenience variable
|
||||
* storing the return value, e.g., $1 -> "foo() returned"
|
||||
* This map allows to quickly find the alias to be used for return value variables.
|
||||
*/
|
||||
private Map<String, String> fExpressionAliasesMap = new HashMap<String, String>();
|
||||
/**
|
||||
* Map of thread to aliases expression list. This map allows to know which aliases are related
|
||||
* to a thread of execution. This is important to allow us to delete aliases when a
|
||||
* thread exits. Note that we need a list because we keep all previous aliases until
|
||||
* the thread exits.
|
||||
*/
|
||||
private Map<IMIExecutionDMContext, List<String>> fThreadToAliasedExpressionsMap = new HashMap<IMIExecutionDMContext, List<String>>();
|
||||
/**
|
||||
* Map of thread to the name of the method the thread last stopped in.
|
||||
* This allows us to create the alias based on the method the thread was in
|
||||
* before it returned out of the method.
|
||||
*/
|
||||
private Map<IMIExecutionDMContext, String> fThreadToTopMethodName = new HashMap<IMIExecutionDMContext, String>();
|
||||
|
||||
/**
|
||||
* Create an alias for expr with respect to threadDmc.
|
||||
* The alias is created based on where threadDmc was previously stopped.
|
||||
*/
|
||||
public void createAlias(IMIExecutionDMContext threadDmc, String expr) {
|
||||
String alias = expr;
|
||||
String methodName = fThreadToTopMethodName.get(threadDmc);
|
||||
if (methodName != null) {
|
||||
alias = MessageFormat.format(Messages.MIExpressions_ReturnValueAlias,
|
||||
methodName + "()"); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
fExpressionAliasesMap.put(expr, alias);
|
||||
|
||||
List<String> aliasedExprList = fThreadToAliasedExpressionsMap.get(threadDmc);
|
||||
if (aliasedExprList == null) {
|
||||
aliasedExprList = new ArrayList<String>();
|
||||
fThreadToAliasedExpressionsMap.put(threadDmc, aliasedExprList);
|
||||
}
|
||||
aliasedExprList.add(expr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all information related to a particular thread of execution.
|
||||
*/
|
||||
public void clearThread(IMIExecutionDMContext threadDmc) {
|
||||
fThreadToTopMethodName.remove(threadDmc);
|
||||
clearAliases(threadDmc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all aliased expressions related to a particular thread of execution.
|
||||
* It is good to keep the aliases around as long as the thread is alive;
|
||||
* even if we won't show the return value automatically, the user
|
||||
* could add the expression in the expression view, and the alias
|
||||
* would then be used.
|
||||
*/
|
||||
public void clearAliases(IMIExecutionDMContext threadDmc) {
|
||||
List<String> aliasedExprList = fThreadToAliasedExpressionsMap.remove(threadDmc);
|
||||
if (aliasedExprList != null) {
|
||||
for (String expr : aliasedExprList) {
|
||||
fExpressionAliasesMap.remove(expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the method name of the last location where threadDmc was stopped.
|
||||
*/
|
||||
public void updateStoppedLocation(IMIExecutionDMContext threadDmc, String methodName) {
|
||||
fThreadToTopMethodName.put(threadDmc, methodName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The alias for 'expr' if there is one. null if there
|
||||
* is no alias for that expression.
|
||||
*/
|
||||
public String getAlias(String expr) {
|
||||
String alias = fExpressionAliasesMap.get(expr);
|
||||
if (alias == null) {
|
||||
// Check if the expression contains the string that must be aliased.
|
||||
// E.g., $1[0], *$2
|
||||
// If it does, just replace that string within the expression to
|
||||
// create the full alias
|
||||
for (Entry<String, String> entry : fExpressionAliasesMap.entrySet()) {
|
||||
int index = expr.indexOf(entry.getKey());
|
||||
if (index != -1) {
|
||||
// Found the string! Now replace it with our alias.
|
||||
// We put it between () to make things clearer to the user.
|
||||
// Note that there can only be one string contained
|
||||
// in the expression, so once we found it, we are done.
|
||||
alias = expr.substring(0, index) +
|
||||
"(" + entry.getValue() + ")" + //$NON-NLS-1$ //$NON-NLS-2$
|
||||
expr.substring(index + entry.getKey().length());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return alias;
|
||||
}
|
||||
}
|
||||
|
||||
/** Structure to keep track of aliases for method return values. */
|
||||
private ReturnValueAliasing fReturnValueAliases = new ReturnValueAliasing();
|
||||
|
||||
|
||||
/**
|
||||
* @since 4.3
|
||||
*/
|
||||
|
@ -1061,8 +1185,13 @@ public class MIExpressions extends AbstractDsfService implements IMIExpressions,
|
|||
}
|
||||
}
|
||||
|
||||
String relativeExpr = getData().getExpr();
|
||||
String alias = fReturnValueAliases.getAlias(relativeExpr);
|
||||
if (alias != null) {
|
||||
relativeExpr = alias;
|
||||
}
|
||||
rm.setData(new ExpressionDMData(
|
||||
getData().getExpr(),getData().getType(), getData().getNumChildren(),
|
||||
relativeExpr, getData().getType(), getData().getNumChildren(),
|
||||
getData().getEditable(), basicType));
|
||||
rm.done();
|
||||
}
|
||||
|
@ -1492,7 +1621,7 @@ public class MIExpressions extends AbstractDsfService implements IMIExpressions,
|
|||
}
|
||||
|
||||
@DsfServiceEventHandler
|
||||
public void eventDispatched(IRunControl.IResumedDMEvent e) {
|
||||
public void eventDispatched(IResumedDMEvent e) {
|
||||
fExpressionCache.setContextAvailable(e.getDMContext(), false);
|
||||
if (e.getReason() != StateChangeReason.STEP) {
|
||||
fExpressionCache.reset();
|
||||
|
@ -1500,9 +1629,53 @@ public class MIExpressions extends AbstractDsfService implements IMIExpressions,
|
|||
}
|
||||
|
||||
@DsfServiceEventHandler
|
||||
public void eventDispatched(IRunControl.ISuspendedDMEvent e) {
|
||||
public void eventDispatched(ISuspendedDMEvent e) {
|
||||
fExpressionCache.setContextAvailable(e.getDMContext(), true);
|
||||
fExpressionCache.reset();
|
||||
|
||||
handleReturnValueAliasing(e);
|
||||
}
|
||||
|
||||
private void handleReturnValueAliasing(ISuspendedDMEvent e) {
|
||||
// Process MIStoppedEvent from within the ISuspendedDMEvent
|
||||
// to avoid any race conditions where the actual MIStoppedEvent
|
||||
// can arrive faster that a preceding IResumedDMEvent
|
||||
if (e instanceof IMIDMEvent) {
|
||||
Object miEvent = ((IMIDMEvent)e).getMIEvent();
|
||||
if (miEvent instanceof MIStoppedEvent) {
|
||||
IMIExecutionDMContext stoppedEventThread = null;
|
||||
if (e instanceof IContainerSuspendedDMEvent) {
|
||||
// All-stop mode
|
||||
IExecutionDMContext[] triggerContexts = ((IContainerSuspendedDMEvent)e).getTriggeringContexts();
|
||||
if (triggerContexts.length != 0 && triggerContexts[0] instanceof IMIExecutionDMContext) {
|
||||
stoppedEventThread = (IMIExecutionDMContext)triggerContexts[0];
|
||||
}
|
||||
} else {
|
||||
// Non-stop mode
|
||||
IDMContext dmc = e.getDMContext();
|
||||
if (dmc instanceof IMIExecutionDMContext) {
|
||||
stoppedEventThread = (IMIExecutionDMContext)dmc;
|
||||
}
|
||||
}
|
||||
|
||||
if (stoppedEventThread != null) {
|
||||
if (miEvent instanceof MIFunctionFinishedEvent) {
|
||||
// When getting an MIFunctionFinishedEvent we must set
|
||||
// a proper alias for the convenience variable
|
||||
String resultVar = ((MIFunctionFinishedEvent)miEvent).getGDBResultVar();
|
||||
fReturnValueAliases.createAlias(stoppedEventThread, resultVar);
|
||||
}
|
||||
|
||||
// Keep track of the latest method the thread is stopped in.
|
||||
// Must do this after creating any alias, or else we will overwrite
|
||||
// the previous function name, which we need for the alias
|
||||
MIFrame frame = ((MIStoppedEvent)miEvent).getFrame();
|
||||
if (frame != null) {
|
||||
fReturnValueAliases.updateStoppedLocation(stoppedEventThread, frame.getFunction());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DsfServiceEventHandler
|
||||
|
@ -1520,6 +1693,20 @@ public class MIExpressions extends AbstractDsfService implements IMIExpressions,
|
|||
fTraceVisualization = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @nooverride This method is not intended to be re-implemented or extended by clients.
|
||||
* @noreference This method is not intended to be referenced by clients.
|
||||
*/
|
||||
@DsfServiceEventHandler
|
||||
public void eventDispatched(IExitedDMEvent e) {
|
||||
IDMContext ctx = e.getDMContext();
|
||||
if (ctx instanceof IMIExecutionDMContext) {
|
||||
// When a thread exits, clear the alias structure for that
|
||||
// thread to avoid leaks
|
||||
fReturnValueAliases.clearThread((IMIExecutionDMContext)ctx);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2006, 2010 Wind River Systems and others.
|
||||
* Copyright (c) 2006, 2013 Wind River Systems and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
|
@ -8,6 +8,7 @@
|
|||
* Contributors:
|
||||
* Wind River Systems - initial API and implementation
|
||||
* Ericsson - Modified for handling of multiple execution contexts
|
||||
* Marc Khouzam (Ericsson) - Show return value of the method when doing a step-return (Bug 341731)
|
||||
*******************************************************************************/
|
||||
package org.eclipse.cdt.dsf.mi.service;
|
||||
|
||||
|
@ -29,6 +30,9 @@ import org.eclipse.cdt.dsf.datamodel.DMContexts;
|
|||
import org.eclipse.cdt.dsf.datamodel.IDMContext;
|
||||
import org.eclipse.cdt.dsf.debug.service.ICachingService;
|
||||
import org.eclipse.cdt.dsf.debug.service.IRunControl;
|
||||
import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext;
|
||||
import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerResumedDMEvent;
|
||||
import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerSuspendedDMEvent;
|
||||
import org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext;
|
||||
import org.eclipse.cdt.dsf.debug.service.IRunControl.IResumedDMEvent;
|
||||
import org.eclipse.cdt.dsf.debug.service.IRunControl.ISuspendedDMEvent;
|
||||
|
@ -42,6 +46,7 @@ import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
|
|||
import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl.ITraceRecordSelectedChangedDMEvent;
|
||||
import org.eclipse.cdt.dsf.mi.service.command.CommandFactory;
|
||||
import org.eclipse.cdt.dsf.mi.service.command.events.IMIDMEvent;
|
||||
import org.eclipse.cdt.dsf.mi.service.command.events.MIFunctionFinishedEvent;
|
||||
import org.eclipse.cdt.dsf.mi.service.command.events.MIStoppedEvent;
|
||||
import org.eclipse.cdt.dsf.mi.service.command.output.MIArg;
|
||||
import org.eclipse.cdt.dsf.mi.service.command.output.MIFrame;
|
||||
|
@ -92,7 +97,7 @@ public class MIStack extends AbstractDsfService
|
|||
protected static class MIVariableDMC extends AbstractDMContext
|
||||
implements IVariableDMContext
|
||||
{
|
||||
public enum Type { ARGUMENT, LOCAL }
|
||||
public enum Type { ARGUMENT, LOCAL, /** @since 4.4 */RETURN_VALUES }
|
||||
final private Type fType;
|
||||
final private int fIndex;
|
||||
|
||||
|
@ -117,6 +122,7 @@ public class MIStack extends AbstractDsfService
|
|||
int typeFactor = 0;
|
||||
if (fType == Type.LOCAL) typeFactor = 2;
|
||||
else if (fType == Type.ARGUMENT) typeFactor = 3;
|
||||
else if (fType == Type.RETURN_VALUES) typeFactor = 4;
|
||||
return super.baseHashCode() ^ typeFactor ^ fIndex;
|
||||
}
|
||||
|
||||
|
@ -155,6 +161,33 @@ public class MIStack extends AbstractDsfService
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as with frame objects, this is a base class for the IVariableDMData object that uses an MIArg object to
|
||||
* provide the data. Sub-classes must supply the MIArg object.
|
||||
*/
|
||||
private class VariableData implements IVariableDMData {
|
||||
private MIArg fMIArg;
|
||||
|
||||
public VariableData(MIArg arg){
|
||||
fMIArg = arg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return fMIArg.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return fMIArg.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return fMIArg.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private CommandCache fMICommandCache;
|
||||
private CommandFactory fCommandFactory;
|
||||
|
@ -176,6 +209,13 @@ public class MIStack extends AbstractDsfService
|
|||
*/
|
||||
private boolean fTraceVisualization;
|
||||
|
||||
/**
|
||||
* A Map of a return value for each thread.
|
||||
* A return value is stored when the user performs a step-return,
|
||||
* and it cleared as soon as that thread executes again.
|
||||
*/
|
||||
private Map<IMIExecutionDMContext, VariableData> fThreadToReturnVariable = new HashMap<IMIExecutionDMContext, VariableData>();
|
||||
|
||||
public MIStack(DsfSession session)
|
||||
{
|
||||
super(session);
|
||||
|
@ -614,23 +654,6 @@ public class MIStack extends AbstractDsfService
|
|||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as with frame objects, this is a base class for the IVariableDMData object that uses an MIArg object to
|
||||
* provide the data. Sub-classes must supply the MIArg object.
|
||||
*/
|
||||
class VariableData implements IVariableDMData {
|
||||
private MIArg dsfMIArg;
|
||||
VariableData(MIArg arg){
|
||||
dsfMIArg = arg;
|
||||
}
|
||||
@Override
|
||||
public String getName() { return dsfMIArg.getName(); }
|
||||
@Override
|
||||
public String getValue() { return dsfMIArg.getValue(); }
|
||||
@Override
|
||||
public String toString() { return dsfMIArg.toString(); }
|
||||
}
|
||||
|
||||
// Check if the stopped event can be used to extract the variable value.
|
||||
if (execDmc != null && miVariableDmc.fType == MIVariableDMC.Type.ARGUMENT &&
|
||||
frameDmc.fLevel == 0 && fCachedStoppedEvent != null && fCachedStoppedEvent.getFrame() != null &&
|
||||
|
@ -694,8 +717,7 @@ public class MIStack extends AbstractDsfService
|
|||
});
|
||||
}
|
||||
});
|
||||
}//if
|
||||
if (miVariableDmc.fType == MIVariableDMC.Type.LOCAL){
|
||||
} else if (miVariableDmc.fType == MIVariableDMC.Type.LOCAL){
|
||||
fMICommandCache.execute(
|
||||
// Don't ask for value when we are visualizing trace data, since some
|
||||
// data will not be there, and the command will fail
|
||||
|
@ -735,7 +757,17 @@ public class MIStack extends AbstractDsfService
|
|||
});
|
||||
}
|
||||
});
|
||||
}//if
|
||||
} else if (miVariableDmc.fType == MIVariableDMC.Type.RETURN_VALUES) {
|
||||
VariableData var = fThreadToReturnVariable.get(execDmc);
|
||||
if (var != null) {
|
||||
rm.setData(var);
|
||||
} else {
|
||||
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Return value not found", null)); //$NON-NLS-1$
|
||||
}
|
||||
rm.done();
|
||||
} else {
|
||||
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Invalid variable type " + miVariableDmc.fType, null)); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -767,7 +799,24 @@ public class MIStack extends AbstractDsfService
|
|||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves variables which are used to store the return values of functions.
|
||||
*/
|
||||
private void getReturnValues(IFrameDMContext frameDmc, DataRequestMonitor<IVariableDMContext[]> rm) {
|
||||
IVariableDMContext[] values = new IVariableDMContext[0];
|
||||
|
||||
// Return values are only relevant for the top stack-frame
|
||||
if (!fTraceVisualization && frameDmc.getLevel() == 0) {
|
||||
IMIExecutionDMContext threadDmc = DMContexts.getAncestorOfType(frameDmc, IMIExecutionDMContext.class);
|
||||
VariableData var = fThreadToReturnVariable.get(threadDmc);
|
||||
if (var != null) {
|
||||
values = new IVariableDMContext[1];
|
||||
values[0] = new MIVariableDMC(this, frameDmc, MIVariableDMC.Type.RETURN_VALUES, 0);
|
||||
}
|
||||
}
|
||||
rm.done(values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getLocals(final IFrameDMContext frameDmc, final DataRequestMonitor<IVariableDMContext[]> rm) {
|
||||
|
||||
|
@ -780,8 +829,20 @@ public class MIStack extends AbstractDsfService
|
|||
rm.done();
|
||||
}
|
||||
};
|
||||
countingRm.setDoneCount(2);
|
||||
countingRm.setDoneCount(3);
|
||||
|
||||
// First show any return values of methods
|
||||
getReturnValues(
|
||||
frameDmc,
|
||||
new DataRequestMonitor<IVariableDMContext[]>(getExecutor(), countingRm) {
|
||||
@Override
|
||||
protected void handleSuccess() {
|
||||
localsList.addAll( Arrays.asList(getData()) );
|
||||
countingRm.done();
|
||||
}
|
||||
});
|
||||
|
||||
// Then show arguments
|
||||
getArguments(
|
||||
frameDmc,
|
||||
new DataRequestMonitor<IVariableDMContext[]>(getExecutor(), countingRm) {
|
||||
|
@ -792,6 +853,7 @@ public class MIStack extends AbstractDsfService
|
|||
}
|
||||
});
|
||||
|
||||
// Finally get the local variables
|
||||
fMICommandCache.execute(
|
||||
// We don't actually need to ask for the values in this case, but since
|
||||
// we will ask for them right after, it is more efficient to ask for them now
|
||||
|
@ -914,6 +976,29 @@ public class MIStack extends AbstractDsfService
|
|||
fMICommandCache.reset();
|
||||
fStackDepthCache.clear();
|
||||
}
|
||||
|
||||
handleReturnValues(e);
|
||||
}
|
||||
|
||||
private void handleReturnValues(IResumedDMEvent e) {
|
||||
// Whenever the execution resumes, we can clear any
|
||||
// return values of previous methods for the resuming
|
||||
// thread context. For all-stop mode, we get a container event here,
|
||||
// and we can clear the entire list, which should contain at most one
|
||||
// value for all-stop.
|
||||
if (e instanceof IContainerResumedDMEvent) {
|
||||
// All-stop mode
|
||||
assert fThreadToReturnVariable.size() <= 1;
|
||||
fThreadToReturnVariable.clear();
|
||||
} else {
|
||||
// Non-stop mode
|
||||
IDMContext ctx = e.getDMContext();
|
||||
if (ctx instanceof IMIExecutionDMContext) {
|
||||
fThreadToReturnVariable.remove(ctx);
|
||||
} else if (ctx instanceof IContainerDMContext) {
|
||||
fThreadToReturnVariable.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -926,8 +1011,47 @@ public class MIStack extends AbstractDsfService
|
|||
fMICommandCache.setContextAvailable(e.getDMContext(), true);
|
||||
fMICommandCache.reset();
|
||||
fStackDepthCache.clear();
|
||||
|
||||
handleReturnValues(e);
|
||||
}
|
||||
|
||||
private void handleReturnValues(ISuspendedDMEvent e) {
|
||||
// Process MIFunctionFinishedEvent from within the ISuspendedDMEvent
|
||||
// instead of MIStoppedEvent.
|
||||
// This avoids a race conditions where the actual MIFunctionFinishedEvent
|
||||
// can arrive here, faster that a preceding IResumedDMEvent
|
||||
if (e instanceof IMIDMEvent) {
|
||||
Object miEvent = ((IMIDMEvent)e).getMIEvent();
|
||||
if (miEvent instanceof MIFunctionFinishedEvent) {
|
||||
// When returning out of a function, we want to show the return value
|
||||
// for the thread that finished the call. To do that, we store
|
||||
// the variable in which GDB stores that return value, and we do
|
||||
// that for the proper thread.
|
||||
|
||||
IMIExecutionDMContext finishedEventThread = null;
|
||||
if (e instanceof IContainerSuspendedDMEvent) {
|
||||
// All-stop mode
|
||||
IExecutionDMContext[] triggerContexts = ((IContainerSuspendedDMEvent)e).getTriggeringContexts();
|
||||
if (triggerContexts.length != 0 && triggerContexts[0] instanceof IMIExecutionDMContext) {
|
||||
finishedEventThread = (IMIExecutionDMContext)triggerContexts[0];
|
||||
}
|
||||
} else {
|
||||
// Non-stop mode
|
||||
IDMContext ctx = e.getDMContext();
|
||||
if (ctx instanceof IMIExecutionDMContext) {
|
||||
finishedEventThread = (IMIExecutionDMContext)ctx;
|
||||
}
|
||||
}
|
||||
|
||||
if (finishedEventThread != null) {
|
||||
String name = ((MIFunctionFinishedEvent)miEvent).getGDBResultVar();
|
||||
String value = ((MIFunctionFinishedEvent)miEvent).getReturnValue();
|
||||
|
||||
fThreadToReturnVariable.put(finishedEventThread, new VariableData(new MIArg(name, value)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @nooverride This method is not intended to be re-implemented or extended by clients.
|
||||
|
|
|
@ -21,6 +21,7 @@ class Messages extends NLS {
|
|||
public static String Breakpoint_attribute_problem;
|
||||
public static String Breakpoint_installation_failed;
|
||||
public static String MIExpressions_NotAvailableBecauseChildOfDynamicVarobj;
|
||||
public static String MIExpressions_ReturnValueAlias;
|
||||
|
||||
static {
|
||||
// initialize resource bundle
|
||||
|
|
|
@ -14,3 +14,4 @@ Breakpoint_attribute_problem=Breakpoint attribute problem: {0}
|
|||
Breakpoint_installation_failed=installation failed
|
||||
|
||||
MIExpressions_NotAvailableBecauseChildOfDynamicVarobj=N/A (child of pretty-printed object)
|
||||
MIExpressions_ReturnValueAlias={0} returned
|
||||
|
|
|
@ -21,14 +21,24 @@ char *gCharPtr2 = (char*)0x4321;
|
|||
bool *gBoolPtr2 = (bool*)0x12ABCDEF;
|
||||
|
||||
class bar {
|
||||
public:
|
||||
public:
|
||||
bar() {
|
||||
d = 8;
|
||||
e[0] = 18;
|
||||
e[1] = 28;
|
||||
}
|
||||
int d;
|
||||
private:
|
||||
int e[2];
|
||||
};
|
||||
|
||||
class bar2 {
|
||||
public:
|
||||
public:
|
||||
bar2() {
|
||||
f = 318;
|
||||
g[0] = 228;
|
||||
g[1] = 138;
|
||||
}
|
||||
int f;
|
||||
private:
|
||||
int g[2];
|
||||
|
@ -36,6 +46,11 @@ private:
|
|||
|
||||
class foo: public bar, bar2 {
|
||||
public:
|
||||
foo() {
|
||||
c = 8;
|
||||
a[0] = 1000;
|
||||
a[1] = 23;
|
||||
}
|
||||
int a[2];
|
||||
bar b;
|
||||
private:
|
||||
|
@ -350,6 +365,28 @@ int testRTTI() {
|
|||
}
|
||||
// End of bug 376901 RTTI tests
|
||||
|
||||
int testSimpleReturn(int a) {
|
||||
int b = 0;
|
||||
b = a;
|
||||
return b;
|
||||
}
|
||||
|
||||
foo testComplexReturn() {
|
||||
foo f;
|
||||
int a = 8;
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
void testReturn() {
|
||||
int a = 10;
|
||||
bool b = false;
|
||||
|
||||
testSimpleReturn(6);
|
||||
testComplexReturn();
|
||||
a = 0;;
|
||||
}
|
||||
|
||||
int main() {
|
||||
printf("Running ExpressionTest App\n");
|
||||
|
||||
|
@ -377,10 +414,12 @@ int main() {
|
|||
testArrays();
|
||||
testRTTI();
|
||||
testCasting();
|
||||
testReturn();
|
||||
|
||||
// For bug 320277
|
||||
BaseTest b; b.test();
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2007, 2010 Ericsson and others.
|
||||
* Copyright (c) 2007, 2013 Ericsson and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
|
@ -12,15 +12,21 @@ package org.eclipse.cdt.tests.dsf.gdb.framework;
|
|||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import junit.framework.Assert;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.eclipse.cdt.dsf.concurrent.CountingRequestMonitor;
|
||||
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
|
||||
import org.eclipse.cdt.dsf.concurrent.ImmediateDataRequestMonitor;
|
||||
import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor;
|
||||
import org.eclipse.cdt.dsf.concurrent.Query;
|
||||
import org.eclipse.cdt.dsf.concurrent.ThreadSafeAndProhibitedFromDsfExecutor;
|
||||
import org.eclipse.cdt.dsf.datamodel.DMContexts;
|
||||
|
@ -39,6 +45,8 @@ import org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext;
|
|||
import org.eclipse.cdt.dsf.debug.service.IRunControl.StepType;
|
||||
import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMContext;
|
||||
import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMData;
|
||||
import org.eclipse.cdt.dsf.debug.service.IStack.IVariableDMContext;
|
||||
import org.eclipse.cdt.dsf.debug.service.IStack.IVariableDMData;
|
||||
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
|
||||
import org.eclipse.cdt.dsf.gdb.launching.GdbLaunch;
|
||||
import org.eclipse.cdt.dsf.gdb.service.IGDBProcesses;
|
||||
|
@ -607,10 +615,10 @@ public class SyncUtil {
|
|||
protected void handleCompleted() {
|
||||
if (isSuccess()) {
|
||||
IDMContext[] contexts = getData();
|
||||
Assert.assertNotNull("invalid return value from service", contexts);
|
||||
Assert.assertEquals("unexpected number of processes", 1, contexts.length);
|
||||
assertNotNull("invalid return value from service", contexts);
|
||||
assertEquals("unexpected number of processes", 1, contexts.length);
|
||||
IDMContext context = contexts[0];
|
||||
Assert.assertNotNull("unexpected process context type ", context);
|
||||
assertNotNull("unexpected process context type ", context);
|
||||
rm.done((IContainerDMContext)context);
|
||||
} else {
|
||||
rm.done(getStatus());
|
||||
|
@ -648,7 +656,7 @@ public class SyncUtil {
|
|||
protected void handleCompleted() {
|
||||
if (isSuccess()) {
|
||||
IDMContext[] threads = getData();
|
||||
Assert.assertNotNull("invalid return value from service", threads);
|
||||
assertNotNull("invalid return value from service", threads);
|
||||
rm.setData((IMIExecutionDMContext[])threads);
|
||||
} else {
|
||||
rm.setStatus(getStatus());
|
||||
|
@ -673,8 +681,8 @@ public class SyncUtil {
|
|||
@ThreadSafeAndProhibitedFromDsfExecutor("fSession.getExecutor()")
|
||||
public static IMIExecutionDMContext getExecutionContext(int threadIndex) throws InterruptedException {
|
||||
IMIExecutionDMContext[] threads = getExecutionContexts();
|
||||
Assert.assertTrue("unexpected number of threads", threadIndex < threads.length);
|
||||
Assert.assertNotNull("unexpected thread context type ", threads[threadIndex]);
|
||||
assertTrue("unexpected number of threads", threadIndex < threads.length);
|
||||
assertNotNull("unexpected thread context type ", threads[threadIndex]);
|
||||
return threads[threadIndex];
|
||||
}
|
||||
|
||||
|
@ -748,4 +756,45 @@ public class SyncUtil {
|
|||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
public static IVariableDMData[] getLocals(final IFrameDMContext frameDmc) throws Throwable {
|
||||
Query<IVariableDMData[]> query = new Query<IVariableDMData[]>() {
|
||||
@Override
|
||||
protected void execute(final DataRequestMonitor<IVariableDMData[]> rm) {
|
||||
fStack.getLocals(frameDmc, new ImmediateDataRequestMonitor<IVariableDMContext[]>() {
|
||||
@Override
|
||||
protected void handleCompleted() {
|
||||
if (isSuccess()) {
|
||||
IVariableDMContext[] varDmcs = getData();
|
||||
final List<IVariableDMData> localsDMData = new ArrayList<IVariableDMData>();
|
||||
final CountingRequestMonitor crm = new CountingRequestMonitor(ImmediateExecutor.getInstance(), rm) {
|
||||
@Override
|
||||
protected void handleSuccess() {
|
||||
rm.done(localsDMData.toArray(new IVariableDMData[localsDMData.size()]));
|
||||
};
|
||||
};
|
||||
|
||||
for (IVariableDMContext varDmc : varDmcs) {
|
||||
fStack.getVariableData(varDmc,
|
||||
new ImmediateDataRequestMonitor<IVariableDMData>(crm) {
|
||||
@Override
|
||||
public void handleSuccess() {
|
||||
localsDMData.add(getData());
|
||||
crm.done();
|
||||
}
|
||||
});
|
||||
}
|
||||
crm.setDoneCount(varDmcs.length);
|
||||
} else {
|
||||
rm.done();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
fSession.getExecutor().execute(query);
|
||||
IVariableDMData[] result = query.get(500, TimeUnit.MILLISECONDS);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2007, 2010 Ericsson and others.
|
||||
* Copyright (c) 2007, 2013 Ericsson and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
|
@ -40,6 +40,7 @@ import org.eclipse.cdt.dsf.debug.service.IFormattedValues.FormattedValueDMContex
|
|||
import org.eclipse.cdt.dsf.debug.service.IFormattedValues.FormattedValueDMData;
|
||||
import org.eclipse.cdt.dsf.debug.service.IRunControl.StepType;
|
||||
import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMContext;
|
||||
import org.eclipse.cdt.dsf.debug.service.IStack.IVariableDMData;
|
||||
import org.eclipse.cdt.dsf.mi.service.ClassAccessor.MIExpressionDMCAccessor;
|
||||
import org.eclipse.cdt.dsf.mi.service.MIExpressions;
|
||||
import org.eclipse.cdt.dsf.mi.service.command.events.MIStoppedEvent;
|
||||
|
@ -310,11 +311,13 @@ public class MIExpressionsTest extends BaseTestCase {
|
|||
|
||||
// Get the children of some variables
|
||||
MIStoppedEvent stoppedEvent = SyncUtil.runToLocation("testChildren");
|
||||
doTestChildren(stoppedEvent);
|
||||
IFrameDMContext frameDmc = SyncUtil.getStackFrame(stoppedEvent.getDMContext(), 0);
|
||||
IExpressionDMContext exprDMC = SyncUtil.createExpression(frameDmc, "f");
|
||||
doTestChildren(exprDMC);
|
||||
|
||||
// Now do a step and get the children again, to test the internal cache
|
||||
stoppedEvent = SyncUtil.step(1, StepType.STEP_OVER);
|
||||
doTestChildren(stoppedEvent);
|
||||
SyncUtil.step(1, StepType.STEP_OVER);
|
||||
doTestChildren(exprDMC);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3188,12 +3191,8 @@ public class MIExpressionsTest extends BaseTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
private void doTestChildren(MIStoppedEvent stoppedEvent) throws Throwable {
|
||||
|
||||
final IFrameDMContext frameDmc = SyncUtil.getStackFrame(stoppedEvent.getDMContext(), 0);
|
||||
|
||||
final IExpressionDMContext exprDMC = SyncUtil.createExpression(frameDmc, "f");
|
||||
|
||||
private void doTestChildren(IExpressionDMContext exprDMC) throws Throwable
|
||||
{
|
||||
IExpressionDMContext[] children =
|
||||
getChildren(exprDMC, new String[] {"bar", "bar2", "a", "b", "c"});
|
||||
|
||||
|
@ -3598,20 +3597,15 @@ public class MIExpressionsTest extends BaseTestCase {
|
|||
Query<String> query = new Query<String>() {
|
||||
@Override
|
||||
protected void execute(final DataRequestMonitor<String> rm) {
|
||||
fExpService.getExecutor().submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
fExpService.getFormattedExpressionValue(
|
||||
fExpService.getFormattedValueContext(children[0], IFormattedValues.NATURAL_FORMAT),
|
||||
new ImmediateDataRequestMonitor<FormattedValueDMData>(rm) {
|
||||
@Override
|
||||
protected void handleCompleted() {
|
||||
rm.done(getData().getFormattedValue());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
fExpService.getFormattedExpressionValue(
|
||||
fExpService.getFormattedValueContext(children[0], IFormattedValues.NATURAL_FORMAT),
|
||||
new ImmediateDataRequestMonitor<FormattedValueDMData>(rm) {
|
||||
@Override
|
||||
protected void handleCompleted() {
|
||||
rm.done(getData().getFormattedValue());
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
fSession.getExecutor().execute(query);
|
||||
|
@ -3622,20 +3616,15 @@ public class MIExpressionsTest extends BaseTestCase {
|
|||
query = new Query<String>() {
|
||||
@Override
|
||||
protected void execute(final DataRequestMonitor<String> rm) {
|
||||
fExpService.getExecutor().submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
fExpService.getFormattedExpressionValue(
|
||||
fExpService.getFormattedValueContext(castChildren[0], IFormattedValues.NATURAL_FORMAT),
|
||||
new ImmediateDataRequestMonitor<FormattedValueDMData>(rm) {
|
||||
@Override
|
||||
protected void handleCompleted() {
|
||||
rm.done(getData().getFormattedValue());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
fExpService.getFormattedExpressionValue(
|
||||
fExpService.getFormattedValueContext(castChildren[0], IFormattedValues.NATURAL_FORMAT),
|
||||
new ImmediateDataRequestMonitor<FormattedValueDMData>(rm) {
|
||||
@Override
|
||||
protected void handleCompleted() {
|
||||
rm.done(getData().getFormattedValue());
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
fSession.getExecutor().execute(query);
|
||||
value = query.get(500, TimeUnit.MILLISECONDS);
|
||||
|
@ -3675,20 +3664,15 @@ public class MIExpressionsTest extends BaseTestCase {
|
|||
Query<String> query = new Query<String>() {
|
||||
@Override
|
||||
protected void execute(final DataRequestMonitor<String> rm) {
|
||||
fExpService.getExecutor().submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
fExpService.getFormattedExpressionValue(
|
||||
fExpService.getFormattedValueContext(child, IFormattedValues.NATURAL_FORMAT),
|
||||
new ImmediateDataRequestMonitor<FormattedValueDMData>(rm) {
|
||||
@Override
|
||||
protected void handleCompleted() {
|
||||
rm.done(getData().getFormattedValue());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
fExpService.getFormattedExpressionValue(
|
||||
fExpService.getFormattedValueContext(child, IFormattedValues.NATURAL_FORMAT),
|
||||
new ImmediateDataRequestMonitor<FormattedValueDMData>(rm) {
|
||||
@Override
|
||||
protected void handleCompleted() {
|
||||
rm.done(getData().getFormattedValue());
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
fSession.getExecutor().execute(query);
|
||||
|
@ -3738,20 +3722,15 @@ public class MIExpressionsTest extends BaseTestCase {
|
|||
Query<String> query = new Query<String>() {
|
||||
@Override
|
||||
protected void execute(final DataRequestMonitor<String> rm) {
|
||||
fExpService.getExecutor().submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
fExpService.getFormattedExpressionValue(
|
||||
fExpService.getFormattedValueContext(child, IFormattedValues.NATURAL_FORMAT),
|
||||
new ImmediateDataRequestMonitor<FormattedValueDMData>(rm) {
|
||||
@Override
|
||||
protected void handleCompleted() {
|
||||
rm.done(getData().getFormattedValue());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
fExpService.getFormattedExpressionValue(
|
||||
fExpService.getFormattedValueContext(child, IFormattedValues.NATURAL_FORMAT),
|
||||
new ImmediateDataRequestMonitor<FormattedValueDMData>(rm) {
|
||||
@Override
|
||||
protected void handleCompleted() {
|
||||
rm.done(getData().getFormattedValue());
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
fSession.getExecutor().execute(query);
|
||||
|
@ -3801,20 +3780,15 @@ public class MIExpressionsTest extends BaseTestCase {
|
|||
Query<String> query = new Query<String>() {
|
||||
@Override
|
||||
protected void execute(final DataRequestMonitor<String> rm) {
|
||||
fExpService.getExecutor().submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
fExpService.getFormattedExpressionValue(
|
||||
fExpService.getFormattedValueContext(child, IFormattedValues.NATURAL_FORMAT),
|
||||
new ImmediateDataRequestMonitor<FormattedValueDMData>(rm) {
|
||||
@Override
|
||||
protected void handleCompleted() {
|
||||
rm.done(getData().getFormattedValue());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
fExpService.getFormattedExpressionValue(
|
||||
fExpService.getFormattedValueContext(child, IFormattedValues.NATURAL_FORMAT),
|
||||
new ImmediateDataRequestMonitor<FormattedValueDMData>(rm) {
|
||||
@Override
|
||||
protected void handleCompleted() {
|
||||
rm.done(getData().getFormattedValue());
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
fSession.getExecutor().execute(query);
|
||||
|
@ -3871,20 +3845,15 @@ public class MIExpressionsTest extends BaseTestCase {
|
|||
Query<String> query = new Query<String>() {
|
||||
@Override
|
||||
protected void execute(final DataRequestMonitor<String> rm) {
|
||||
fExpService.getExecutor().submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
fExpService.getFormattedExpressionValue(
|
||||
fExpService.getFormattedValueContext(child, IFormattedValues.NATURAL_FORMAT),
|
||||
new ImmediateDataRequestMonitor<FormattedValueDMData>(rm) {
|
||||
@Override
|
||||
protected void handleCompleted() {
|
||||
rm.done(getData().getFormattedValue());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
fExpService.getFormattedExpressionValue(
|
||||
fExpService.getFormattedValueContext(child, IFormattedValues.NATURAL_FORMAT),
|
||||
new ImmediateDataRequestMonitor<FormattedValueDMData>(rm) {
|
||||
@Override
|
||||
protected void handleCompleted() {
|
||||
rm.done(getData().getFormattedValue());
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
fSession.getExecutor().execute(query);
|
||||
|
@ -3947,20 +3916,15 @@ public class MIExpressionsTest extends BaseTestCase {
|
|||
Query<String> query = new Query<String>() {
|
||||
@Override
|
||||
protected void execute(final DataRequestMonitor<String> rm) {
|
||||
fExpService.getExecutor().submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
fExpService.getFormattedExpressionValue(
|
||||
fExpService.getFormattedValueContext(child, IFormattedValues.NATURAL_FORMAT),
|
||||
new ImmediateDataRequestMonitor<FormattedValueDMData>(rm) {
|
||||
@Override
|
||||
protected void handleCompleted() {
|
||||
rm.done(getData().getFormattedValue());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
fExpService.getFormattedExpressionValue(
|
||||
fExpService.getFormattedValueContext(child, IFormattedValues.NATURAL_FORMAT),
|
||||
new ImmediateDataRequestMonitor<FormattedValueDMData>(rm) {
|
||||
@Override
|
||||
protected void handleCompleted() {
|
||||
rm.done(getData().getFormattedValue());
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
fSession.getExecutor().execute(query);
|
||||
|
@ -4021,20 +3985,15 @@ public class MIExpressionsTest extends BaseTestCase {
|
|||
Query<String> query = new Query<String>() {
|
||||
@Override
|
||||
protected void execute(final DataRequestMonitor<String> rm) {
|
||||
fExpService.getExecutor().submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
fExpService.getFormattedExpressionValue(
|
||||
fExpService.getFormattedValueContext(child, IFormattedValues.NATURAL_FORMAT),
|
||||
new ImmediateDataRequestMonitor<FormattedValueDMData>(rm) {
|
||||
@Override
|
||||
protected void handleCompleted() {
|
||||
rm.done(getData().getFormattedValue());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
fExpService.getFormattedExpressionValue(
|
||||
fExpService.getFormattedValueContext(child, IFormattedValues.NATURAL_FORMAT),
|
||||
new ImmediateDataRequestMonitor<FormattedValueDMData>(rm) {
|
||||
@Override
|
||||
protected void handleCompleted() {
|
||||
rm.done(getData().getFormattedValue());
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
fSession.getExecutor().execute(query);
|
||||
|
@ -4046,6 +4005,105 @@ public class MIExpressionsTest extends BaseTestCase {
|
|||
assertEquals(castExprDmc.getParents()[0], exprDmc);
|
||||
}
|
||||
|
||||
/**
|
||||
* This test verifies that we display the simple return value of a method after
|
||||
* a step-return operation, but only for the first stack frame.
|
||||
*/
|
||||
@Test
|
||||
public void testDisplaySimpleReturnValueForStepReturn() throws Throwable {
|
||||
SyncUtil.runToLocation("testSimpleReturn");
|
||||
MIStoppedEvent stoppedEvent = SyncUtil.step(1, StepType.STEP_RETURN);
|
||||
|
||||
// Check the return value is shown when looking at the first frame
|
||||
final IFrameDMContext frameDmc = SyncUtil.getStackFrame(stoppedEvent.getDMContext(), 0);
|
||||
IVariableDMData[] result = SyncUtil.getLocals(frameDmc);
|
||||
|
||||
assertEquals(3, result.length); // Two variables and one return value
|
||||
|
||||
// Return value
|
||||
assertEquals("$1", result[0].getName());
|
||||
assertEquals("6", result[0].getValue());
|
||||
// first variable
|
||||
assertEquals("a", result[1].getName());
|
||||
assertEquals("10", result[1].getValue());
|
||||
// Second variable
|
||||
assertEquals("b", result[2].getName());
|
||||
assertEquals("false", result[2].getValue());
|
||||
|
||||
// Now check how the return value will be displayed to the user
|
||||
final IExpressionDMContext returnExprDmc = SyncUtil.createExpression(frameDmc, "$1");
|
||||
Query<IExpressionDMData> query = new Query<IExpressionDMData>() {
|
||||
@Override
|
||||
protected void execute(final DataRequestMonitor<IExpressionDMData> rm) {
|
||||
fExpService.getExpressionData(returnExprDmc, rm);
|
||||
}
|
||||
};
|
||||
fSession.getExecutor().execute(query);
|
||||
IExpressionDMData data = query.get(500, TimeUnit.MILLISECONDS);
|
||||
assertEquals("testSimpleReturn() returned", data.getName());
|
||||
|
||||
// Now check the actual value using the expression service
|
||||
String value = SyncUtil.getExpressionValue(returnExprDmc, IFormattedValues.DECIMAL_FORMAT);
|
||||
assertEquals("6", value);
|
||||
|
||||
// Now make sure we don't show the return value for another frame
|
||||
final IFrameDMContext frameDmc2 = SyncUtil.getStackFrame(stoppedEvent.getDMContext(), 1);
|
||||
result = SyncUtil.getLocals(frameDmc2);
|
||||
|
||||
// only one variable
|
||||
assertEquals(1, result.length);
|
||||
assertEquals("b", result[0].getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* This test verifies that we display the complex return value of a method after
|
||||
* a step-return operation, but only for the first stack frame.
|
||||
*/
|
||||
@Test
|
||||
public void testDisplayComplexReturnValueForStepReturn() throws Throwable {
|
||||
SyncUtil.runToLocation("testComplexReturn");
|
||||
MIStoppedEvent stoppedEvent = SyncUtil.step(1, StepType.STEP_RETURN);
|
||||
|
||||
// Check the return value is show when looking at the first frame
|
||||
final IFrameDMContext frameDmc = SyncUtil.getStackFrame(stoppedEvent.getDMContext(), 0);
|
||||
IVariableDMData[] result = SyncUtil.getLocals(frameDmc);
|
||||
|
||||
assertEquals(3, result.length); // Two variables and one return value
|
||||
|
||||
// Return value
|
||||
assertEquals("$1", result[0].getName());
|
||||
|
||||
// first variable
|
||||
assertEquals("a", result[1].getName());
|
||||
assertEquals("10", result[1].getValue());
|
||||
// Second variable
|
||||
assertEquals("b", result[2].getName());
|
||||
assertEquals("false", result[2].getValue());
|
||||
|
||||
// Now check how the return value will be displayed to the user
|
||||
final IExpressionDMContext returnExprDmc = SyncUtil.createExpression(frameDmc, "$1");
|
||||
Query<IExpressionDMData> query = new Query<IExpressionDMData>() {
|
||||
@Override
|
||||
protected void execute(final DataRequestMonitor<IExpressionDMData> rm) {
|
||||
fExpService.getExpressionData(returnExprDmc, rm);
|
||||
}
|
||||
};
|
||||
fSession.getExecutor().execute(query);
|
||||
IExpressionDMData data = query.get(500, TimeUnit.MILLISECONDS);
|
||||
assertEquals("testComplexReturn() returned", data.getName());
|
||||
|
||||
// Now check the content of the complex return expression
|
||||
doTestChildren(returnExprDmc);
|
||||
|
||||
// Now make sure we don't show the return value for another frame
|
||||
IFrameDMContext frameDmc2 = SyncUtil.getStackFrame(stoppedEvent.getDMContext(), 1);
|
||||
result = SyncUtil.getLocals(frameDmc2);
|
||||
|
||||
// only one variable
|
||||
assertEquals(1, result.length);
|
||||
assertEquals("b", result[0].getName());
|
||||
}
|
||||
|
||||
protected int getChildrenCount(final IExpressionDMContext parentDmc, final int expectedCount) throws Throwable {
|
||||
|
||||
final AsyncCompletionWaitor wait = new AsyncCompletionWaitor();
|
||||
|
@ -4098,20 +4156,15 @@ public class MIExpressionsTest extends BaseTestCase {
|
|||
Query<String> query = new Query<String>() {
|
||||
@Override
|
||||
protected void execute(final DataRequestMonitor<String> rm) {
|
||||
fExpService.getExecutor().submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
fExpService.getExpressionData(
|
||||
exprDmc,
|
||||
new ImmediateDataRequestMonitor<IExpressionDMData>(rm) {
|
||||
@Override
|
||||
protected void handleCompleted() {
|
||||
rm.done(getData().getTypeName());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
fExpService.getExpressionData(
|
||||
exprDmc,
|
||||
new ImmediateDataRequestMonitor<IExpressionDMData>(rm) {
|
||||
@Override
|
||||
protected void handleCompleted() {
|
||||
rm.done(getData().getTypeName());
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
fSession.getExecutor().execute(query);
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2013 Ericsson and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Marc Khouzam (Ericsson) - Initial Implementation
|
||||
*******************************************************************************/
|
||||
package org.eclipse.cdt.tests.dsf.gdb.tests.tests_7_0;
|
||||
|
||||
import org.eclipse.cdt.dsf.gdb.IGDBLaunchConfigurationConstants;
|
||||
import org.eclipse.cdt.tests.dsf.gdb.framework.BackgroundRunner;
|
||||
import org.eclipse.cdt.tests.dsf.gdb.tests.ITestConstants;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@RunWith(BackgroundRunner.class)
|
||||
public class MIExpressionsNonStopTest_7_0 extends MIExpressionsTest_7_0 {
|
||||
@Override
|
||||
protected void setGdbVersion() {
|
||||
setGdbProgramNamesLaunchAttributes(ITestConstants.SUFFIX_GDB_7_0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setLaunchAttributes() {
|
||||
super.setLaunchAttributes();
|
||||
|
||||
setLaunchAttribute(IGDBLaunchConfigurationConstants.ATTR_DEBUGGER_NON_STOP, true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2013 Ericsson and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Marc Khouzam (Ericsson) - Initial Implementation
|
||||
*******************************************************************************/
|
||||
package org.eclipse.cdt.tests.dsf.gdb.tests.tests_7_1;
|
||||
|
||||
import org.eclipse.cdt.dsf.gdb.IGDBLaunchConfigurationConstants;
|
||||
import org.eclipse.cdt.tests.dsf.gdb.framework.BackgroundRunner;
|
||||
import org.eclipse.cdt.tests.dsf.gdb.tests.ITestConstants;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@RunWith(BackgroundRunner.class)
|
||||
public class MIExpressionsNonStopTest_7_1 extends MIExpressionsTest_7_1 {
|
||||
@Override
|
||||
protected void setGdbVersion() {
|
||||
setGdbProgramNamesLaunchAttributes(ITestConstants.SUFFIX_GDB_7_1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setLaunchAttributes() {
|
||||
super.setLaunchAttributes();
|
||||
|
||||
setLaunchAttribute(IGDBLaunchConfigurationConstants.ATTR_DEBUGGER_NON_STOP, true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2013 Ericsson and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Marc Khouzam (Ericsson) - Initial Implementation
|
||||
*******************************************************************************/
|
||||
package org.eclipse.cdt.tests.dsf.gdb.tests.tests_7_2;
|
||||
|
||||
import org.eclipse.cdt.dsf.gdb.IGDBLaunchConfigurationConstants;
|
||||
import org.eclipse.cdt.tests.dsf.gdb.framework.BackgroundRunner;
|
||||
import org.eclipse.cdt.tests.dsf.gdb.tests.ITestConstants;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@RunWith(BackgroundRunner.class)
|
||||
public class MIExpressionsNonStopTest_7_2 extends MIExpressionsTest_7_2 {
|
||||
@Override
|
||||
protected void setGdbVersion() {
|
||||
setGdbProgramNamesLaunchAttributes(ITestConstants.SUFFIX_GDB_7_2);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setLaunchAttributes() {
|
||||
super.setLaunchAttributes();
|
||||
|
||||
setLaunchAttribute(IGDBLaunchConfigurationConstants.ATTR_DEBUGGER_NON_STOP, true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2013 Ericsson and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Marc Khouzam (Ericsson) - Initial Implementation
|
||||
*******************************************************************************/
|
||||
package org.eclipse.cdt.tests.dsf.gdb.tests.tests_7_3;
|
||||
|
||||
import org.eclipse.cdt.dsf.gdb.IGDBLaunchConfigurationConstants;
|
||||
import org.eclipse.cdt.tests.dsf.gdb.framework.BackgroundRunner;
|
||||
import org.eclipse.cdt.tests.dsf.gdb.tests.ITestConstants;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@RunWith(BackgroundRunner.class)
|
||||
public class MIExpressionsNonStopTest_7_3 extends MIExpressionsTest_7_3 {
|
||||
@Override
|
||||
protected void setGdbVersion() {
|
||||
setGdbProgramNamesLaunchAttributes(ITestConstants.SUFFIX_GDB_7_3);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setLaunchAttributes() {
|
||||
super.setLaunchAttributes();
|
||||
|
||||
setLaunchAttribute(IGDBLaunchConfigurationConstants.ATTR_DEBUGGER_NON_STOP, true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2013 Ericsson and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Marc Khouzam (Ericsson) - Initial Implementation
|
||||
*******************************************************************************/
|
||||
package org.eclipse.cdt.tests.dsf.gdb.tests.tests_7_4;
|
||||
|
||||
import org.eclipse.cdt.dsf.gdb.IGDBLaunchConfigurationConstants;
|
||||
import org.eclipse.cdt.tests.dsf.gdb.framework.BackgroundRunner;
|
||||
import org.eclipse.cdt.tests.dsf.gdb.tests.ITestConstants;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@RunWith(BackgroundRunner.class)
|
||||
public class MIExpressionsNonStopTest_7_4 extends MIExpressionsTest_7_4 {
|
||||
@Override
|
||||
protected void setGdbVersion() {
|
||||
setGdbProgramNamesLaunchAttributes(ITestConstants.SUFFIX_GDB_7_4);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setLaunchAttributes() {
|
||||
super.setLaunchAttributes();
|
||||
|
||||
setLaunchAttribute(IGDBLaunchConfigurationConstants.ATTR_DEBUGGER_NON_STOP, true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2013 Ericsson and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Marc Khouzam (Ericsson) - Initial Implementation
|
||||
*******************************************************************************/
|
||||
package org.eclipse.cdt.tests.dsf.gdb.tests.tests_7_5;
|
||||
|
||||
import org.eclipse.cdt.dsf.gdb.IGDBLaunchConfigurationConstants;
|
||||
import org.eclipse.cdt.tests.dsf.gdb.framework.BackgroundRunner;
|
||||
import org.eclipse.cdt.tests.dsf.gdb.tests.ITestConstants;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@RunWith(BackgroundRunner.class)
|
||||
public class MIExpressionsNonStopTest_7_5 extends MIExpressionsTest_7_5 {
|
||||
@Override
|
||||
protected void setGdbVersion() {
|
||||
setGdbProgramNamesLaunchAttributes(ITestConstants.SUFFIX_GDB_7_5);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setLaunchAttributes() {
|
||||
super.setLaunchAttributes();
|
||||
|
||||
setLaunchAttribute(IGDBLaunchConfigurationConstants.ATTR_DEBUGGER_NON_STOP, true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2013 Ericsson and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Marc Khouzam (Ericsson) - Initial Implementation
|
||||
*******************************************************************************/
|
||||
package org.eclipse.cdt.tests.dsf.gdb.tests.tests_7_6;
|
||||
|
||||
import org.eclipse.cdt.dsf.gdb.IGDBLaunchConfigurationConstants;
|
||||
import org.eclipse.cdt.tests.dsf.gdb.framework.BackgroundRunner;
|
||||
import org.eclipse.cdt.tests.dsf.gdb.tests.ITestConstants;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@RunWith(BackgroundRunner.class)
|
||||
public class MIExpressionsNonStopTest_7_6 extends MIExpressionsTest_7_6 {
|
||||
@Override
|
||||
protected void setGdbVersion() {
|
||||
setGdbProgramNamesLaunchAttributes(ITestConstants.SUFFIX_GDB_7_6);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setLaunchAttributes() {
|
||||
super.setLaunchAttributes();
|
||||
|
||||
setLaunchAttribute(IGDBLaunchConfigurationConstants.ATTR_DEBUGGER_NON_STOP, true);
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue