blob: be77fa06dd1058ec757402f2dccb4ad6eef1a459 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2013 Monta Vista 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:
* Monta Vista - initial API and implementation
* Ericsson - Modified for handling of multiple execution contexts
* Ericsson - Major updates for GDB/MI implementation
* Ericsson - Major re-factoring to deal with children
* 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)
* Axel Mueller - Workaround for GDB bug where -var-info-path-expression gives invalid result (Bug 320277)
* Anton Gorenkov - DSF-GDB should properly handle variable type change (based on RTTI) (Bug 376901)
*******************************************************************************/
package org.eclipse.cdt.dsf.mi.service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.eclipse.cdt.dsf.concurrent.CountingRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.debug.service.IExpressions;
import org.eclipse.cdt.dsf.debug.service.IExpressions.IExpressionDMContext;
import org.eclipse.cdt.dsf.debug.service.IExpressions.IIndexedPartitionDMContext;
import org.eclipse.cdt.dsf.debug.service.IExpressions2.ICastedExpressionDMContext;
import org.eclipse.cdt.dsf.debug.service.IFormattedValues;
import org.eclipse.cdt.dsf.debug.service.IFormattedValues.FormattedValueDMContext;
import org.eclipse.cdt.dsf.debug.service.IFormattedValues.FormattedValueDMData;
import org.eclipse.cdt.dsf.debug.service.IMemory.IMemoryChangedEvent;
import org.eclipse.cdt.dsf.debug.service.IRunControl;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext;
import org.eclipse.cdt.dsf.debug.service.IStack;
import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMContext;
import org.eclipse.cdt.dsf.debug.service.command.ICommand;
import org.eclipse.cdt.dsf.debug.service.command.ICommandControl;
import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlDMContext;
import org.eclipse.cdt.dsf.debug.service.command.ICommandListener;
import org.eclipse.cdt.dsf.debug.service.command.ICommandResult;
import org.eclipse.cdt.dsf.debug.service.command.ICommandToken;
import org.eclipse.cdt.dsf.debug.service.command.IEventListener;
import org.eclipse.cdt.dsf.gdb.GDBTypeParser;
import org.eclipse.cdt.dsf.gdb.GDBTypeParser.GDBType;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl.ITraceRecordSelectedChangedDMEvent;
import org.eclipse.cdt.dsf.mi.service.MIExpressions.ExpressionInfo;
import org.eclipse.cdt.dsf.mi.service.MIExpressions.MIExpressionDMC;
import org.eclipse.cdt.dsf.mi.service.command.CommandFactory;
import org.eclipse.cdt.dsf.mi.service.command.commands.ExprMetaGetAttributes;
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.commands.MIDataEvaluateExpression;
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.MIDisplayHint;
import org.eclipse.cdt.dsf.mi.service.command.output.MIDisplayHint.GdbDisplayHint;
import org.eclipse.cdt.dsf.mi.service.command.output.MIInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIVar;
import org.eclipse.cdt.dsf.mi.service.command.output.MIVarAssignInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIVarChange;
import org.eclipse.cdt.dsf.mi.service.command.output.MIVarCreateInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIVarDeleteInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIVarEvaluateExpressionInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIVarInfoPathExpressionInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIVarListChildrenInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIVarSetFormatInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIVarShowAttributesInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIVarUpdateInfo;
import org.eclipse.cdt.dsf.service.DsfServiceEventHandler;
import org.eclipse.cdt.dsf.service.DsfServicesTracker;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
/**
* Manages a list of variable objects as created through GDB/MI commands.
*
* This class is passed expression-meta-commands which have their own cache.
* Therefore, we don't use the standard MICommandCache in this class.
* In fact, we can't even use it, because many variableObject MI commands,
* should not be cached as they alter the state of the back-end.
*
* Design details:
* ==============
*
* GDB variable object information
* -------------------------------
* o Variable objects are recursively hierarchical, where children can be created through
* the parent.
* o A varObject created with -var-create is a ROOT
* o A varObject created with -var-list-children, is not a root
* o Only varObject with no children or varObjects that are pointers can change values
* and therefore
* those objects can be used with -var-assign
* o After a program stops, a varObject must be 'updated' before used
* o Only root varObject can be updated with -var-update, which will trigger all
* of the root's descendants to be updated.
* o Once updated, a varObject need not be updated until the program resumes again;
* this is true even after -var-assign is used (because it does an implicit -var-update)
* o -var-update will return the list of all modifiable descendants of the udpated root which
* have changed
* o -var-update will indicate if a root is out-of-scope (which implies that all
* its descendants are out-of-scope)
* o if a varObject is out-of-scope, another varObject may be valid for the same
* expression as the out-of-scope varObject
* o deleting a varObject will delete all its descendants, therefore, it is only
* necessary to delete roots
*
*
* Class details
* -------------
* - We have an MIVariableObject class which represents a variable object in GDB
*
* - MIVariableObject includes a buffered value for each allowed format.
*
* - We have an MIRootVariableObject class inheriting from MIVariableObject to describe
* root varObjects created with -var-create. Objects created with -var-list-children
* are MIVariableObjects only. The root class will keep track of if the root object
* needs to be updated, if the root object is out-of-scope, and of a list of all
* modifiable descendants of this root. The list of modifiable descendants is
* accessed using the gdb-given name to allow quick updates from the -var-update
* result (see below.)
*
* - we do not use -var-list-children for arrays, but create them manually
*
* - when the program stops, we should mark all roots as needing to be updated.
* To achieve this efficiently, we have a dedicated list of roots that are updated.
* When the program stops, we go through this list, remove each element and mark it
* as needing to be updated.
*
* - when a varObject is accessed, if its root must be updated, the var-update
* command shall be used. The result of that command will indicate all
* modifiable descendants that have changed. We also use --all-values with -var-update
* to get the new value (in the current format) for each modified descendant. Using the list of modifiable
* descendants of the root, we can quickly update the changed ones to invalidate their buffered
* values and store the new current format value.
*
* - all values of non-modifiable varObjects (except arrays) will be set to {...}
* without going to the back-end
*
* - requesting the value of an array varObject will trigger the creation of a new
* varObject for the array's address. Note that we must still use a variable
* object and not the command -data-evaluate-expression, because we still need to get
* the array address in multiple formats.
*
* - we keep an LRU (Least Recently Used) structure of all variable objects. This LRU
* will be bounded to a maximum allowed number of variable objects. Whenever we get an
* object from the LRU cleanup will be done if the maximum size has been reached.
* The LRU will not delete a parent varObject until all its children are deleted; this is
* achieved by touching each of the parents of an object whenever that object is put or get
*
* - It may happen that when accessing a varObject we find its root to be
* out-of-scope. The expression for which we are trying to access a varObject
* could still be valid, and therefore we should try to create a new varObject for
* that expression. This can happen for example if two methods use the same name
* for a variable. In the case when we find that a varObject is out-of-scope (when
* its root is out-of-scope) the following should be done:
* - replace the varObject in the LRU with a newly created one in GDB
* - if the old object was a root, delete it in GDB.
*
* - In GDB, -var-update will only report a change if -var-evaluate-expression has
* changed -- in the current format--. This means that situations like
* double z = 1.2;
* z = 1.4;
* Will not report a change if the format is anything else than natural.
* This is because 1.2 and 1.4 are both printed as 1, 0x1, etc
* Since we cache the values of every format, we must know if the value has
* change in -any- format, not just the current one.
* To solve this, we always keep the display format of variable objects (and their
* children) to the natural format; we believe that if the value changes in any
* format, it guarantees that it will change in the natural format.
* The simplest way to do this is that whenever we change the format
* of a variable object, we immediately set it back to natural with a second
* var-set-format command.
* Note that versions of GDB after 6.7 will allows to issue -var-evaluate-expression
* with a specified format, therefore allowing us to never use -var-set-format, and
* consequently, to easily keep the display format of all variable objects to natural.
*
* Notes on Dynamic Variable Objects (varobj)
* ------------------------------------------
* - with version 7.0, gdb support so-called pretty printers.
*
* - pretty printers are registered for certain types
*
* - if there is a pretty printer registered for the type of a variable,
* the pretty printer provides the value and the children of that variable
*
* - a varobj whose value and/or children are provided by a pretty printer,
* are referred to as dynamic variable objects
*
* - dynamic varobjs change the game: it's not wise to ask it about all its
* children, not even the number of children it has. The reason is that
* in order to find out about the number of children the pretty printer
* must fetch all the children from the inferiors memory. If the variable
* is not yet initialized, the set of children are random, and thus might
* be huge. Even worse, there are data structures where fetching all
* children may result in an endless loop. The Eclipse debugger then hangs.
*
* - In order to avoid this, we will always fetch up to a certain maximum
* number of children. Furthermore, it is possible to find out whether there
* are more children available. In the UI, all the currently fetched
* children are available. In addition, if there are more children
* available, a special node will be appended to indicate that there is
* more the user could fetch.
*
* - Dynamic varobjs can change their value, as leaf varobjs can do.
* Especially, children can be added or removed during an update.
*
* - There is no expression for children of dynamic varobjs (at least not
* yet, http://sourceware.org/bugzilla/show_bug.cgi?id=10252 would fix that).
* The reason -var-info-path-expression returns garbage for children of
* dynamic varobjs.
*
* - Because of this, the variable of an expression that is a child of
* a dynamic varobj cannot be created again using -var-create, once
* the LRU cache has deleted it. Instead, we track the parent and index
* within this parent for each non-root variable, and later use
* -var-list-children parent indexInParent (indexInParent + 1)
* in order to create the MI variable anew.
*
* - The fetching of children for dynamic varobjs becomes a bit more complicated.
* For the traditional varobjs, once children where requested, all children
* were fetched. For dynamic varobjs, we can no longer fetch all children.
* Instead, the client will provide a maximum number of children that
* is to be fetched. Every time the child count or children are requested,
* we must check whether there are additional children to be fetched,
* because the limit might have extended.
* Fetching additional children can only be done by one request monitor at a time.
* The serialization of the request monitors is done in getChildren by
* ensuring that fetchChildren is called for one request monitor at a time.
* fetchChildren in turn checks whether enough children are fetched, and
* if not, fetches the additional children.
*/
public class MIVariableManager implements ICommandControl {
/**
* Stores the information about children of a variable object.
*
* @since 4.0
*/
protected static class ChildrenInfo {
private final ExpressionInfo[] children;
private final boolean hasMore;
public ChildrenInfo(ExpressionInfo[] children, boolean hasMore) {
this.children = children;
this.hasMore = hasMore;
}
/**
* @return The currently fetched children. Ask {@link #hasMore()} in
* order to find out whether there are more to fetch.
*/
public ExpressionInfo[] getChildren() {
return children;
}
/**
* @return true, if there are more than just those returned by
* {@link #getChildren()}.
*/
public boolean hasMore() {
return hasMore;
}
}
/**
* Stores the information about the children count of a variable object.
*
* @since 4.0
*/
protected static class ChildrenCountInfo {
private final int childrenCount;
private final boolean hasMore;
public ChildrenCountInfo(int childrenCount, boolean hasMore) {
this.childrenCount = childrenCount;
this.hasMore = hasMore;
}
/**
* The number of children that we currently know of. Ask
* {@link #hasMore()} in order to find out whether there is at least one
* more.
*/
public int getChildrenCount() {
return childrenCount;
}
/**
* @return <code>true</code> if there are more children than actually
* returned by {@link #getChildrenCount()}.
*/
public boolean hasMore() {
return hasMore;
}
}
/**
* Utility class to track the progress and information of MI variable objects
*/
public class MIVariableObject {
// Don't use an enumeration to allow subclasses to extend this
protected static final int STATE_READY = 0;
protected static final int STATE_UPDATING = 1;
/** @since 4.0 */
protected static final int STATE_NOT_CREATED = 10;
/** @since 4.0 */
protected static final int STATE_CREATING = 11;
/** @since 4.0 */
protected static final int STATE_CREATION_FAILED = 12;
protected int currentState;
// This is the lock used when we must run multiple
// operations at once. This lock should be independent of the
// UPDATING state, which is why we don't make it part of the state
private boolean locked = false;
// This id is the one used to search for this object in our hash-map
private final VariableObjectId internalId;
// This is the name of the variable object, as given by GDB (e.g., var1 or var1.public.x)
private String gdbName = null;
// The current format of this variable object, within GDB
private String format = IFormattedValues.NATURAL_FORMAT;
// The full expression that can be used to characterize this object
// plus some other information that shall live longer than the
// MIVariableObject.
private ExpressionInfo exprInfo;
private String type = null;
private GDBType gdbType;
// A hint at the number of children. This value is obtained
// from -var-create or -var-list-children. It may not be right in the case
// of C++ structures, where GDB has a level of children for private/public/protected.
private int numChildrenHint = 0;
private Boolean editable = null;
// The current values of the expression for each format. (null if not known yet)
private Map<String, String> valueMap = null;
// A queue of request monitors waiting for this object to be ready
private LinkedList<RequestMonitor> operationsPending;
// A queue of request monitors that requested an update
protected LinkedList<DataRequestMonitor<Boolean>> updatesPending;
/** @since 4.0 */
protected LinkedList<DataRequestMonitor<ChildrenInfo>> fetchChildrenPending;
// The children of this variable, if any.
// Null means we didn't fetch them yet, while an empty array means no children
private ExpressionInfo[] children = null;
// we need to keep track of fake children because they are in the LRU and need to be removed in some cases.
private List<ExpressionInfo> fakeChildren = new ArrayList<ExpressionInfo>(3);
private boolean hasMore = false;
private MIDisplayHint displayHint = MIDisplayHint.NONE;
// The parent of this variable object within GDB. Null if this object has no parent
private MIVariableObject parent = null;
// The root parent that must be used to issue -var-update.
// If this object is a root, then the rootToUpdate is itself
private MIRootVariableObject rootToUpdate = null;
protected boolean outOfScope = false;
private boolean fetchingChildren = false;
/**
* In case of base class variables that are accessed in a derived class
* we cannot trust var-info-path-expression because of a bug in gdb.
* We have to use a workaround and apply it to the complete hierarchy of this varObject.
* Bug 320277
*/
private boolean hasCastToBaseClassWorkaround = false;
public MIVariableObject(VariableObjectId id, MIVariableObject parentObj) {
this(id, parentObj, false);
}
/** @since 4.0 */
public MIVariableObject(VariableObjectId id, MIVariableObject parentObj,
boolean needsCreation) {
currentState = needsCreation ? STATE_NOT_CREATED : STATE_READY;
operationsPending = new LinkedList<RequestMonitor>();
updatesPending = new LinkedList<DataRequestMonitor<Boolean>>();
fetchChildrenPending = new LinkedList<DataRequestMonitor<ChildrenInfo>>();
internalId = id;
setParent(parentObj);
// No values are available yet
valueMap = new HashMap<String, String>();
resetValues();
}
public VariableObjectId getInternalId() { return internalId; }
public String getGdbName() { return gdbName; }
public String getCurrentFormat() { return format; }
public MIVariableObject getParent() { return parent; }
public MIRootVariableObject getRootToUpdate() { return rootToUpdate; }
public String getExpression() { return exprInfo.getFullExpr(); }
public String getType() { return type; }
/**
* @since 4.0
*/
public ExpressionInfo getExpressionInfo() { return exprInfo; }
/**
* @return <code>true</code> if value and children of this varobj are
* currently provided by a pretty printer.
*
* @since 4.0
*/
public boolean isDynamic() { return exprInfo.isDynamic(); }
/**
* @return For dynamic varobjs ({@link #isDynamic() returns true}) this
* method returns whether there are children in addition to the
* currently fetched, i.e. whether there are more children than
* {@link #getNumChildrenHint()} returns.
*
* @since 4.0
*/
public boolean hasMore() { return hasMore; }
/**
* @since 4.0
*/
public MIDisplayHint getDisplayHint() { return displayHint; };
/**
* @since 4.3
*/
public void setDisplayHint(MIDisplayHint displayHint) {
this.displayHint = displayHint;
};
/**
* @since 4.0
*/
public boolean hasChildren() { return (getNumChildrenHint() != 0 || hasMore()); }
/** @since 3.0 */
public GDBType getGDBType() { return gdbType; }
/**
* Returns a hint to the number of children. This hint is often correct,
* except when we are dealing with C++ complex structures where
* GDB has 'private/public/protected' as children.
*
* Use <code>isNumChildrenHintTrustworthy()</code> to know if the
* hint can be trusted.
*
* Note that a hint of 0 children can always be trusted, except for
* <code>{@link #hasMore} == true</code>.
*
* @since 3.0 */
public int getNumChildrenHint() { return numChildrenHint; }
/**
* Returns whether the number of children hint can be
* trusted for this variable object.
*
* @since 3.0
*/
public boolean isNumChildrenHintTrustworthy() {
// We cannot trust the hint about the number of children when we are
// dealing with a complex structure that could have the
// 'protected/public/private' children types.
// Note that a pointer could have such children, if it points to
// a complex structure.
//
// This is only valid for C++, so we should even check for it using
// -var-info-expression. Do we have to use -var-info-expression for each
// variable object, or can we do it one time only for the whole program?
// Right now, we always assume we could be using C++
return ((getNumChildrenHint() == 0 && ! hasMore()) || isArray());
}
public String getValue(String format) { return valueMap.get(format); }
public ExpressionInfo[] getChildren() { return children; }
public boolean isArray() { return (getGDBType() == null) ? false : getGDBType().getType() == GDBType.ARRAY; }
public boolean isPointer() { return (getGDBType() == null) ? false : getGDBType().getType() == GDBType.POINTER; }
public boolean isMethod() { return (getGDBType() == null) ? false : getGDBType().getType() == GDBType.FUNCTION; }
// A complex variable is one with children. However, it must not be a pointer since a pointer
// does have children, but is still a 'simple' variable, as it can be modified.
// A reference can be modified too, because it can be a reference to the base class before initialization
// and after initialization it can become a reference to the derived class (if gdb shows the value type and
// children taking into account RTTI ("set print object on")).
// Note that the numChildrenHint can be trusted when asking if the number of children is 0 or not
public boolean isComplex() {
return (getGDBType() == null) ? false
: getGDBType().getType() != GDBType.POINTER && getGDBType().getType() != GDBType.REFERENCE
&& (getNumChildrenHint() > 0
|| hasMore() || getDisplayHint().isCollectionHint());
}
/**
* @return Whether this varobj can safely be asked for all its children.
*
* @since 4.0
*/
public boolean isSafeToAskForAllChildren() {
GdbDisplayHint displayHint = getDisplayHint().getGdbDisplayHint();
// Here we balance usability against a slight risk of instability:
//
// Usability: if you have a class/struct-like pretty printer
// all children are fetched, regardless of any limit. This
// should be safe from gdb side.
//
// Risk: If somebody provides a pretty printer for a collection,
// but forgets to implement the display_hint method, viewing
// the collection while it is uninitiliazed may cause gdb
// to never return.
//
// => The risk seams reasonable, so we require a limit only
// for collections.
boolean isDynamicButSafe = (displayHint == GdbDisplayHint.GDB_DISPLAY_HINT_STRING)
|| (displayHint == GdbDisplayHint.GDB_DISPLAY_HINT_NONE);
return !isDynamic() || isDynamicButSafe;
}
public void setGdbName(String n) { gdbName = n; }
public void setCurrentFormat(String f) { format = f; }
/**
* @param fullExpression
* @param t
* @param num
*
* @deprecated Use
* {@link #setExpressionData(ExpressionInfo, String, int, boolean)}
* instead.
*/
@Deprecated
public void setExpressionData(String fullExpression, String t, int num) {
new ExpressionInfo(fullExpression, fullExpression);
}
/**
* @param info
* @param typeName
* @param num
* If the correspinding MI variable is dynamic, the number of
* children currently fetched by gdb.
* @param hasMore
* Whether their are more children to fetch.
*
* @since 4.0
*/
public void setExpressionData(ExpressionInfo info, String typeName, int num, boolean hasMore) {
exprInfo = info;
setType(typeName);
numChildrenHint = num;
this.hasMore = hasMore;
}
/**
* @since 4.1
*/
public void setType(String newTypeName) {
type = newTypeName;
gdbType = fGDBTypeParser.parse(newTypeName);
}
public void setValue(String format, String val) { valueMap.put(format, val); }
public void resetValues(String valueInCurrentFormat) {
resetValues();
setValue(getCurrentFormat(), valueInCurrentFormat);
}
public void resetValues() {
valueMap.put(IFormattedValues.NATURAL_FORMAT, null);
valueMap.put(IFormattedValues.BINARY_FORMAT, null);
valueMap.put(IFormattedValues.HEX_FORMAT, null);
valueMap.put(IFormattedValues.OCTAL_FORMAT, null);
valueMap.put(IFormattedValues.DECIMAL_FORMAT, null);
}
/**
* @param c
* The new children, or null in order to force fetching
* children anew.
*/
public void setChildren(ExpressionInfo[] c) {
children = c;
if (children != null) {
numChildrenHint = children.length;
}
if (children != null) {
for (ExpressionInfo child : children) {
assert (child != null);
}
}
}
/**
* @param newChildren
*
* @since 4.0
*/
public void addChildren(ExpressionInfo[] newChildren) {
if (children == null) {
children = new ExpressionInfo[newChildren.length];
System.arraycopy(newChildren, 0, children, 0, newChildren.length);
} else {
ExpressionInfo[] oldChildren = children;
children = new ExpressionInfo[children.length + newChildren.length];
System.arraycopy(oldChildren, 0, children, 0, oldChildren.length);
System.arraycopy(newChildren, 0, children, oldChildren.length, newChildren.length);
}
numChildrenHint = children.length;
for (ExpressionInfo child : children) {
assert (child != null);
}
}
/**
* @param newChildren
*
* @since 4.0
*/
protected void addChildren(ExpressionInfo[][] newChildren) {
int requiredSize = 0;
for (ExpressionInfo[] subArray : newChildren) {
requiredSize += subArray.length;
}
ExpressionInfo[] plainChildren = new ExpressionInfo[requiredSize];
int i = 0;
for (ExpressionInfo[] subArray : newChildren) {
System.arraycopy(subArray, 0, plainChildren, i, subArray.length);
i += subArray.length;
}
addChildren(plainChildren);
}
/**
* @param newNumChildren
*
* @since 4.0
*/
public void shrinkChildrenTo(int newNumChildren) {
if (children != null) {
ExpressionInfo[] oldChildren = children;
for (int i = oldChildren.length - 1; i >= newNumChildren; --i) {
String childFullExpression = children[i].getFullExpr();
VariableObjectId childId = new VariableObjectId();
childId.generateId(childFullExpression, getInternalId());
lruVariableList.remove(childId);
}
children = new ExpressionInfo[newNumChildren];
System.arraycopy(oldChildren, 0, children, 0, newNumChildren);
}
numChildrenHint = newNumChildren;
}
/**
* Removes the specified child from LRU and makes the cleanup on its
* children (if any).
*
* @since 4.3
*/
public void cleanupChild(ExpressionInfo child) {
String childFullExpression = child.getFullExpr();
VariableObjectId childId = new VariableObjectId();
childId.generateId(childFullExpression, getInternalId());
MIVariableObject varobjOfChild = lruVariableList.remove(childId);
if (varobjOfChild != null) {
// Remove it from the list of modifiable descendants
getRootToUpdate().removeModifiableDescendant(varobjOfChild.getGdbName());
// Remove children recursively
varobjOfChild.cleanupChildren();
}
}
/**
* Removes all the children (real and fake) of the variable object from
* LRU cache and from variable object itself. It is used when the type
* of variable was changed.
*
* @since 4.1
*/
public void cleanupChildren() {
hasMore = false;
if (children != null) {
for (ExpressionInfo child : children) {
cleanupChild(child);
}
children = null;
numChildrenHint = 0;
}
for (ExpressionInfo fakeChild : fakeChildren) {
cleanupChild(fakeChild);
}
fakeChildren.clear();
}
public void setParent(MIVariableObject p) {
parent = p;
if (p == null) {
rootToUpdate = (this instanceof MIRootVariableObject) ? (MIRootVariableObject) this
: null;
} else {
rootToUpdate = p.getRootToUpdate();
}
}
public void executeWhenNotUpdating(RequestMonitor rm) {
getRootToUpdate().executeWhenNotUpdating(rm);
}
private void lock() {
locked = true;
}
private void unlock() {
locked = false;
while (operationsPending.size() > 0) {
operationsPending.poll().done();
}
}
public boolean isOutOfScope() { return outOfScope; }
/**
* @param success
*
* @since 4.0
*/
protected void creationCompleted(boolean success) {
// A creation completed we must be up-to-date, so we
// can tell any pending monitors that updates are done
if (success) {
currentState = STATE_READY;
while (updatesPending.size() > 0) {
DataRequestMonitor<Boolean> rm = updatesPending.poll();
// Nothing to be re-created
rm.setData(false);
rm.done();
}
} else {
currentState = STATE_CREATION_FAILED;
// Creation failed, inform anyone waiting.
while (updatesPending.size() > 0) {
RequestMonitor rm = updatesPending.poll();
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.INVALID_HANDLE,
"Unable to create variable object", null)); //$NON-NLS-1$
rm.done();
}
}
}
/**
* This method updates the variable object.
* Updating a variable object is done by updating its root.
*/
public void update(final DataRequestMonitor<Boolean> rm) {
// We check to see if we are already out-of-scope
// We must do this to avoid the risk of re-creating this object
// twice, due to race conditions
if (isOutOfScope()) {
rm.setData(false);
rm.done();
} else if (currentState != STATE_READY) {
// If we were already updating, we just queue the request monitor
// until the on-going update finishes.
updatesPending.add(rm);
} else {
currentState = STATE_UPDATING;
getRootToUpdate().update(new DataRequestMonitor<Boolean>(fSession.getExecutor(), rm) {
@Override
protected void handleCompleted() {
currentState = STATE_READY;
if (isSuccess()) {
outOfScope = getRootToUpdate().isOutOfScope();
// This request monitor is the one that should re-create
// the variable object if the old one was out-of-scope
rm.setData(outOfScope);
rm.done();
// All the other request monitors must be notified but must
// not re-create the object, even if it is out-of-scope
while (updatesPending.size() > 0) {
DataRequestMonitor<Boolean> pendingRm = updatesPending.poll();
pendingRm.setData(false);
pendingRm.done();
}
} else {
rm.setStatus(getStatus());
rm.done();
while (updatesPending.size() > 0) {
DataRequestMonitor<Boolean> pendingRm = updatesPending.poll();
pendingRm.setStatus(getStatus());
pendingRm.done();
}
}
}
});
}
}
/**
* Process an update on this variable object.
*
* @param update What has changed.
*
* @since 4.0
*/
protected void processChange(final MIVarChange update, final RequestMonitor rm) {
// Handle the variable type change properly
if (update.isChanged()) {
setType(update.getNewType());
cleanupChildren();
editable = null;
updateLimit(IMIExpressions.CHILD_COUNT_LIMIT_UNSPECIFIED);
}
// These properties of the variable will probably not change,
// but if they are - we should handle it properly.
setDisplayHint(update.getDisplayHint());
exprInfo.setDynamic(update.isDynamic());
MIVar[] newChildren = update.getNewChildren();
// children == null means fetchChildren will happen later, so
// don't try to create a sparsely filled children array here.
final boolean addNewChildren = (children != null);
final ExpressionInfo[] addedChildren = (addNewChildren && (newChildren != null)) ? new ExpressionInfo[newChildren.length]
: null;
final CountingRequestMonitor countingRm = new CountingRequestMonitor(fSession.getExecutor(), rm) {
@Override
protected void handleCompleted() {
if (! isSuccess()) {
rm.setStatus(getStatus());
} else {
if (update.numChildrenChanged()) {
if (children != null) {
// Remove those children that don't exist any longer.
if (children.length > update.getNewNumChildren()) {
shrinkChildrenTo(update.getNewNumChildren());
}
} else {
// Just update the child count.
numChildrenHint = update.getNewNumChildren();
}
// Add the new children.
if ((addedChildren != null) && (addedChildren.length != 0)) {
addChildren(addedChildren);
}
}
assert((children == null) || (children.length == numChildrenHint));
hasMore = update.hasMore();
// If there was no -var-list-children yet for a varobj,
// the new children will not be reported by -var-update.
// Set the children to null such that the next time children
// are requested, they will be fetched.
if (hasMore() && (numChildrenHint == 0)) {
setChildren(null);
}
resetValues(update.getValue());
}
rm.done();
}
};
// Process all the child MIVariableObjects.
int pendingVariableCreationCount = 0;
if (newChildren != null && newChildren.length != 0) {
int i = update.getNewNumChildren() - newChildren.length;
int arrayPosition = 0;
for (final MIVar newChild : newChildren) {
// As long as http://sourceware.org/bugzilla/show_bug.cgi?id=10252
// is not fixed, it doesn't make sense to use
// -var-info-path-expression. New children can only
// be added during the update, if we are a child of a
// dynamic varobj, and in this case -var-info-path-expression
// won't work.
final String childFullExpression = buildChildExpression(
getExpression(), newChild.getExp());
// Now try to see if we already have this variable
// object in our Map
// Since our map names use the expression, and not the
// GDB given
// name, we must determine the correct map name from the
// varName
final VariableObjectId childId = new VariableObjectId();
childId.generateId(childFullExpression, getInternalId());
MIVariableObject childVar = lruVariableList.get(childId);
if (childVar != null) {
if ((childVar.currentState == STATE_CREATING)
|| (childVar.currentState == STATE_NOT_CREATED)) {
// We must wait until the child MIVariableObject is fully created.
// This might succeed, or fail. If it succeeds, we can reuse it as
// a child, otherwise we create a new MIVariableObject for the
// varobj just provided by gdb.
++pendingVariableCreationCount;
final int insertPosition = arrayPosition;
final MIVariableObject monitoredVar = childVar;
final int indexInParent = i;
// varobj is not fully created so add RequestMonitor to pending queue
childVar.updatesPending.add(new DataRequestMonitor<Boolean>(fSession.getExecutor(), countingRm) {
@Override
protected void handleCompleted() {
if (isSuccess()) {
if (addNewChildren && addedChildren != null) {
addedChildren[insertPosition] = monitoredVar.exprInfo;
}
} else {
// Create a fresh MIVariableObject for this child, using
// the new varobj provided by gdb.
MIVariableObject newVar = createChild(childId, childFullExpression,
indexInParent, newChild);
if (addNewChildren && addedChildren != null) {
addedChildren[insertPosition] = newVar.exprInfo;
}
}
countingRm.done();
}
});
} else if (childVar.currentState == STATE_CREATION_FAILED) {
// There has been an attempt the create a MIRootVariableObject for a full
// expression representing a child of a dynamic varobj. Such an attempt
// always fails. But here we can now create it (see below).
childVar = null;
}
// Note that we must check the root to know if it is out-of-scope.
// We cannot check the child as it has not be updated and its
// outOfScope variable is not updated either.
else if (childVar.getRootToUpdate().isOutOfScope()) {
childVar.deleteInGdb();
childVar = null;
}
}
// Note: we don't need to check for fake children (public, protected, private) here.
// We enter this code only if the children are provided by a pretty printer and
// they don't return such children.
if (childVar == null) {
// Create a fresh MIVariableObject for this child, using
// the new varobj provided by -var-update.
childVar = createChild(childId, childFullExpression, i, newChild);
if (addNewChildren && addedChildren != null) {
addedChildren[arrayPosition] = childVar.exprInfo;
}
}
++i;
++arrayPosition;
}
}
countingRm.setDoneCount(pendingVariableCreationCount);
}
/**
* Variable objects need not be deleted unless they are root.
* This method is specialized in the MIRootVariableObject class.
*/
public void deleteInGdb() {}
/**
* This method returns the value of the variable object attributes by
* using -var-show-attributes.
* Currently, the only attribute available is 'editable'.
*
* @param rm
* The data request monitor that will hold the value returned
*/
private void getAttributes(final DataRequestMonitor<Boolean> rm) {
if (editable != null) {
rm.setData(editable);
rm.done();
} else if (isComplex()) {
editable = false;
rm.setData(editable);
rm.done();
} else {
fCommandControl.queueCommand(
fCommandFactory.createMIVarShowAttributes(getRootToUpdate().getControlDMContext(), getGdbName()),
new DataRequestMonitor<MIVarShowAttributesInfo>(fSession.getExecutor(), rm) {
@Override
protected void handleSuccess() {
editable = getData().isEditable();
rm.setData(editable);
rm.done();
}
});
}
}
/**
* This method returns the value of the variable object.
* This operation translates to multiple MI commands which affect the state of the
* variable object in the back-end; therefore, we must make sure the object is not
* locked doing another operation, and we must lock the object once it is our turn
* to use it.
*
* @param dmc
* The context containing the format to be used for the evaluation
* @param rm
* The data request monitor that will hold the value returned
* @since 4.3
*/
protected void getValue(final FormattedValueDMContext dmc,
final DataRequestMonitor<FormattedValueDMData> rm) {
// We might already know the value
String value = getValue(dmc.getFormatID());
if (value != null) {
rm.setData(new FormattedValueDMData(value));
rm.done();
return;
}
// If the variable is a complex structure, there is no need to ask the back-end for a value,
// we can give it the {...} ourselves
// Unless we are dealing with an array, in which case, we want to get the address of it
if (isComplex() && ! isDynamic()) {
if (isArray()) {
// Figure out the address
IExpressionDMContext exprCxt = DMContexts.getAncestorOfType(dmc, IExpressionDMContext.class);
IExpressionDMContext addrCxt = fExpressionService.createExpression(exprCxt, "&(" + exprCxt.getExpression() + ")"); //$NON-NLS-1$//$NON-NLS-2$
final FormattedValueDMContext formatCxt = new FormattedValueDMContext(
fSession.getId(),
addrCxt,
dmc.getFormatID()
);
getVariable(
addrCxt,
new DataRequestMonitor<MIVariableObject>(fSession.getExecutor(), rm) {
@Override
protected void handleSuccess() {
getData().getValue(formatCxt, rm);
}
});
} else {
// Other complex structure
String complexValue = "{...}"; //$NON-NLS-1$
setValue(dmc.getFormatID(), complexValue);
rm.setData(new FormattedValueDMData(complexValue));
rm.done();
}
return;
}
if (locked) {
operationsPending.add(new RequestMonitor(fSession.getExecutor(), rm) {
@Override
protected void handleSuccess() {
getValue(dmc, rm);
}
});
} else {
lock();
// If the format is already the one set for this variable object,
// we don't need to set it again
if (dmc.getFormatID().equals(getCurrentFormat())) {
evaluate(rm);
} else {
// We must first set the new format and then evaluate the variable
fCommandControl.queueCommand(
fCommandFactory.createMIVarSetFormat(getRootToUpdate().getControlDMContext(), getGdbName(), dmc.getFormatID()),
new DataRequestMonitor<MIVarSetFormatInfo>(fSession.getExecutor(), rm) {
@Override
protected void handleCompleted() {
if (isSuccess()) {
setCurrentFormat(dmc.getFormatID());
// If set-format returned the value, no need to evaluate
// This is only valid after GDB 6.7
if (getData().getValue() != null) {
setValue(dmc.getFormatID(), getData().getValue());
rm.setData(new FormattedValueDMData(getData().getValue()));
rm.done();
// Unlock is done within this method
resetFormatToNatural();
} else {
evaluate(rm);
}
} else {
rm.setStatus(getStatus());
rm.done();
unlock();
}
}
});
}
}
}
/**
* This method evaluates a variable object
*/
private void evaluate(final DataRequestMonitor<FormattedValueDMData> rm) {
fCommandControl.queueCommand(
fCommandFactory.createMIVarEvaluateExpression(getRootToUpdate().getControlDMContext(), getGdbName()),
new DataRequestMonitor<MIVarEvaluateExpressionInfo>(fSession.getExecutor(), rm) {
@Override
protected void handleCompleted() {
if (isSuccess()) {
setValue(getCurrentFormat(), getData().getValue());
rm.setData(new FormattedValueDMData(getData().getValue()));
rm.done();
} else {
rm.setStatus(getStatus());
rm.done();
}
// Unlock is done within this method
resetFormatToNatural();
}
});
}
// In GDB, var-update will only report a change if -var-evaluate-expression has
// changed -- in the current format--. This means that situations like
// double z = 1.2;
// z = 1.4;
// Will not report a change if the format is anything else than natural.
// This is because 1.2 and 1.4 are both printed as 1, 0x1, etc
// Since we cache the values of every format, we must know if -any- format has
// changed, not just the current one.
// To solve this, we always do an update in the natural format; I am not aware
// of any case where the natural format would stay the same, but another format
// would change. However, since a var-update update all children as well,
// we must make sure these children are also in the natural format
// The simplest way to do this is that whenever we change the format
// of a variable object, we immediately set it back to natural with a second
// var-set-format command.
private void resetFormatToNatural() {
if (!getCurrentFormat().equals(IFormattedValues.NATURAL_FORMAT)) {
fCommandControl.queueCommand(
fCommandFactory.createMIVarSetFormat(getRootToUpdate().getControlDMContext(), getGdbName(), IFormattedValues.NATURAL_FORMAT),
new DataRequestMonitor<MIVarSetFormatInfo>(fSession.getExecutor(), null) {
@Override
protected void handleCompleted() {
if (isSuccess()) {
setCurrentFormat(IFormattedValues.NATURAL_FORMAT);
}
unlock();
}
});
} else {
unlock();
}
}
/**
* This method returns the list of children of the variable object
* passed as a parameter.
*
* @param exprDmc
*
* @param clientNumChildrenLimit
* If the current limit for the given expression is smaller,
* this limit will be applied.
* @param rm
* The data request monitor that will hold the children
* returned
*/
private void getChildren(final IExpressionDMContext exprDmc,
final int clientNumChildrenLimit, final DataRequestMonitor<ChildrenInfo> rm) {
if (fetchingChildren) {
// Only one request monitor can fetch children at a time.
fetchChildrenPending.add(new DataRequestMonitor<ChildrenInfo>(fSession.getExecutor(), rm) {
@Override
protected void handleSuccess() {
ChildrenInfo info = getData();
int numChildren = info.getChildren().length;
if (! info.hasMore() || numChildren >= clientNumChildrenLimit) {
// No need to fetch further children.
rm.setData(getData());
rm.done();
} else {
// Need to retry.
getChildren(exprDmc, clientNumChildrenLimit, rm);
}
}
});
} else {
fetchingChildren = true;
fetchChildren(exprDmc, clientNumChildrenLimit, new DataRequestMonitor<ChildrenInfo>(fSession.getExecutor(), rm) {
@Override
protected void handleCompleted() {
fetchingChildren = false;
if (isSuccess()) {
rm.setData(getData());
rm.done();
while (fetchChildrenPending.size() > 0) {
DataRequestMonitor<ChildrenInfo> pendingRm = fetchChildrenPending.poll();
pendingRm.setData(getData());
pendingRm.done();
}
} else {
rm.setStatus(getStatus());
rm.done();
while (fetchChildrenPending.size() > 0) {
DataRequestMonitor<ChildrenInfo> pendingRm = fetchChildrenPending.poll();
pendingRm.setStatus(getStatus());
pendingRm.done();
}
}
}
});
}
}
/**
* Fetch the out-standing children.
*
* @param exprDmc
*
* @param clientNumChildrenLimit
* If the current limit for the given expression is smaller,
* this limit will be applied.
* @param rm
* The data request monitor that will hold the children
* returned
* @since 4.3
*/
protected void fetchChildren(final IExpressionDMContext exprDmc,
int clientNumChildrenLimit, final DataRequestMonitor<ChildrenInfo> rm) {
final int newNumChildrenLimit = clientNumChildrenLimit != IMIExpressions.CHILD_COUNT_LIMIT_UNSPECIFIED ?
clientNumChildrenLimit : 1;
boolean addChildren = requiresAdditionalChildren(newNumChildrenLimit);
// If we already know the children, no need to go to the back-end
ExpressionInfo[] childrenArray = getChildren();
if (childrenArray != null && ! addChildren) {
rm.setData(new ChildrenInfo(childrenArray, hasMore));
rm.done();
return;
}
// If the variable does not have children, we can return an empty list right away
if (! hasChildren()) {
// First store the empty list, for the next time
setChildren(new ExpressionInfo[0]);
rm.setData(new ChildrenInfo(getChildren(), hasMore));
rm.done();
return;
}
// For arrays (which could be very large), we create the children ourselves. This is
// to avoid creating an enormous amount of children variable objects that the view may
// never need. Using -var-list-children will create a variable object for every child
// immediately, that is why we don't want to use it for arrays.
if (isArray()) {
ExpressionInfo[] childrenOfArray = new ExpressionInfo[getNumChildrenHint()];
String exprName = exprDmc.getExpression();
int castingIndex = 0;
// in case of casts, need to resolve that before dereferencing, to be safe
if (exprDmc instanceof ICastedExpressionDMContext) {
castingIndex = ((ICastedExpressionDMContext)exprDmc).getCastInfo().getArrayStartIndex();
}
if (exprDmc instanceof IIndexedPartitionDMContext) {
castingIndex = ((IIndexedPartitionDMContext)exprDmc).getIndex();
}
for (int i= 0; i < childrenOfArray.length; i++) {
String fullExpr = exprName + "[" + i + "]";//$NON-NLS-1$//$NON-NLS-2$
String relExpr;
if (exprDmc instanceof MIExpressionDMC) {
relExpr = ((MIExpressionDMC)exprDmc).getRelativeExpression();
} else {
// Unexpected, but avoid exception
relExpr = exprDmc.getExpression();
}
relExpr = relExpr + "[" + (castingIndex + i) + "]";//$NON-NLS-1$//$NON-NLS-2$
childrenOfArray[i] = new ExpressionInfo(fullExpr, relExpr, false, exprInfo, i);
}
// First store these children, for the next time
setChildren(childrenOfArray);
hasMore = false;
rm.setData(new ChildrenInfo(getChildren(), hasMore));
rm.done();
return;
}
// No need to wait for the object to be ready since listing children can be performed
// at any time, as long as the object is created, which we know it is, since we can only
// be called here with a fully created object.
// Also no need to lock the object, since getting the children won't affect other operations
final int from = (addChildren && (children != null)) ? getNumChildrenHint() : 0;
final int to = Math.max(newNumChildrenLimit, exprInfo.getChildCountLimit());
ICommand<MIVarListChildrenInfo> varListChildren = isSafeToAskForAllChildren() ?
fCommandFactory.createMIVarListChildren(getRootToUpdate().getControlDMContext(), getGdbName())
:fCommandFactory.createMIVarListChildren(getRootToUpdate().getControlDMContext(), getGdbName(), from, to);
fCommandControl.queueCommand(
varListChildren,
new DataRequestMonitor<MIVarListChildrenInfo>(fSession.getExecutor(), rm) {
@Override
protected void handleSuccess() {
MIVar[] children = getData().getMIVars();
final boolean localHasMore = getData().hasMore();
// The elements of this array normally are an ExpressionInfo, unless it corresponds to
// a fake child (public, protected, private). In this case it is an ExpressionInfo[]
// representing the children of the fake child.
// This in done in order to preserve the order (the index-in-parent information),
// when replacing a fake child by its real children.
final ExpressionInfo[][] realChildren = new ExpressionInfo[children.length][];
final CountingRequestMonitor countingRm = new CountingRequestMonitor(fSession.getExecutor(), rm) {
@Override
protected void handleSuccess() {
// Store the children in our variable object cache
addChildren(realChildren);
hasMore = localHasMore;
rm.setData(new ChildrenInfo(getChildren(), hasMore));
int updateLimit = updateLimit(to);
if (! isSafeToAskForAllChildren()) {
// Make sure the gdb will not hang, if later
// the varobj is updated, but the underlying
// data is still uninitialized.
fCommandControl.queueCommand(
fCommandFactory.createMIVarSetUpdateRange(getRootToUpdate().getControlDMContext(),
getGdbName(), 0, updateLimit), new DataRequestMonitor<MIInfo>(fSession.getExecutor(), rm));
} else {
rm.done();
}
}
};
int numSubRequests = 0;
for (int i = 0; i < children.length; ++i) {
final MIVar child = children[i];
final int indexInParent = from + i;
final int arrayPosition = i;
// These children get created automatically as variable objects in GDB, so we should
// add them to the LRU.
// Note that if this variable object already exists, we can be in three scenarios:
// 1- the existing object is the same variable object in GDB. In this case,
// the existing and new one are identical so we can keep either one.
// 2- the existing object is out-of-scope and should be replaced by the new one.
// This can happen if a root was found to be out-of-scope, but this child
// had not been accessed and therefore had not been removed.
// 3- the existing object is an in-scope root object representing the same expression.
// In this case, either object can be kept and the other one can be deleted.
// The existing root could currently be in-use by another operation and may
// not be deleted; but since we can only have one entry in the LRU, we are
// forced to keep the existing root. Note that we need not worry about
// the newly created child since it will automatically be deleted when
// its root is deleted.
numSubRequests++;
// Class to keep track of the child's full expression, but also
// if that child had to use the CastToBaseClassWorkaround,
// which needs to be propagated to its own children.
class ChildFullExpressionInfo {
private String childFullExpression;
private boolean childHasCastToBaseClassWorkaround;
public ChildFullExpressionInfo(String path) {
this(path, false);
}
public ChildFullExpressionInfo(String path, boolean hasWorkaround) {
childFullExpression = path == null ? "" : path; //$NON-NLS-1$
childHasCastToBaseClassWorkaround = hasWorkaround;
}
public String getChildPath() { return childFullExpression; }
public boolean getChildHasCastToBaseClassWorkaround() { return childHasCastToBaseClassWorkaround; }
};
final DataRequestMonitor<ChildFullExpressionInfo> childPathRm =
new DataRequestMonitor<ChildFullExpressionInfo>(fSession.getExecutor(), countingRm) {
@Override
protected void handleSuccess() {
final String childPath = getData().getChildPath();
// The child varObj we are about to create should have hasCastToBaseClassWorkaround
// set in two conditions:
// 1- if its parent was set (which is the current varObj)
// 2- if the workaround was used for the child itself, which is part of ChildFullExpressionInfo
final boolean childHasCastToBaseClassWorkaround =
hasCastToBaseClassWorkaround || getData().getChildHasCastToBaseClassWorkaround();
// For children that do not map to a real expression (such as f.public)
// GDB returns an empty string. In this case, we can use another unique
// name, such as the variable name
final boolean fakeChild = (childPath.length() == 0);
final String childFullExpression = fakeChild ? child.getVarName() : childPath;
// Now try to see if we already have this variable object in our Map
// Since our map names use the expression, and not the GDB given
// name, we must determine the correct map name from the varName
final VariableObjectId childId = new VariableObjectId();
childId.generateId(childFullExpression, getInternalId());
MIVariableObject childVar = lruVariableList.get(childId);
if (childVar != null) {
if ((childVar.currentState == STATE_CREATING)
|| (childVar.currentState == STATE_NOT_CREATED)) {
// We must wait until the child MIVariableObject is fully created.
// This might succeed, or fail. If it succeeds, we can reuse it as
// a child, otherwise we create a new MIVariableObject for the
// varobj just provided by gdb.
final MIVariableObject monitoredVar = childVar;
// childVar is not fully created so add a RequestMonitor to the queue
childVar.updatesPending.add(new DataRequestMonitor<Boolean>(fSession.getExecutor(), countingRm) {
@Override
protected void handleCompleted() {
MIVariableObject var = monitoredVar;
if (! isSuccess()) {
// Create a fresh MIVariableObject for this child, using
// the new varobj provided by gdb.
var = createChild(childId, childFullExpression, indexInParent, child);
}
var.hasCastToBaseClassWorkaround = childHasCastToBaseClassWorkaround;
if (fakeChild) {
if (! isSuccess()) {
fakeChildren.add(var.exprInfo);
}
addRealChildrenOfFake(var, exprDmc, realChildren,
arrayPosition, countingRm);
} else {
// This is a real child
realChildren[arrayPosition] = new ExpressionInfo[] { var.exprInfo };
countingRm.done();
}
}
});
} else if (childVar.currentState == STATE_CREATION_FAILED) {
// There has been an attempt the create a MIRootVariableObject for a full
// expression representing a child of a dynamic varobj. Such an attempt always
// fails. But here we can now create it (see below).
childVar = null;
}
// Note that we must check the root to know if it is out-of-scope.
// We cannot check the child as it has not be updated and its
// outOfScope variable is not updated either.
else if (childVar.getRootToUpdate().isOutOfScope()) {
childVar.deleteInGdb();
childVar = null;
} else {
childVar.hasCastToBaseClassWorkaround = childHasCastToBaseClassWorkaround;
if (fakeChild) {
// I don't think this should happen, but we put it just in case
addRealChildrenOfFake(childVar, exprDmc, realChildren,
arrayPosition, countingRm);
} else {
// This is a real child
realChildren[arrayPosition] = new ExpressionInfo[] { childVar.exprInfo };
countingRm.done();
}
}
}
if (childVar == null) {
childVar = createChild(childId, childFullExpression, indexInParent, child);
childVar.hasCastToBaseClassWorkaround = childHasCastToBaseClassWorkaround;
if (fakeChild) {
fakeChildren.add(childVar.exprInfo);
addRealChildrenOfFake(childVar, exprDmc, realChildren,
arrayPosition, countingRm);
} else {
// This is a real child
realChildren[arrayPosition] = new ExpressionInfo[] { childVar.exprInfo };
countingRm.done();
}
}
}
};
if (isAccessQualifier(child.getExp())) {
// This is just a qualifier level of C++, so we don't need
// to call -var-info-path-expression for real, but just pretend we did.
childPathRm.setData(new ChildFullExpressionInfo("")); //$NON-NLS-1$
childPathRm.done();
} else if (isDynamic() || exprInfo.hasDynamicAncestor()) {
// Equivalent to (which can't be implemented): child.hasDynamicAncestor
// The new child has a dynamic ancestor. Such children don't support
// var-info-path-expression. Build the expression ourselves.
childPathRm.setData(new ChildFullExpressionInfo(buildChildExpression(exprDmc.getExpression(), child.getExp())));
childPathRm.done();
} else if (hasCastToBaseClassWorkaround) {
// We had to use the "CastToBaseClass" workaround in the hierarchy, so we
// know -var-info-path-expression won't work in this case. We have to
// build the expression ourselves again to keep the workaround as part
// of the child's expression.
childPathRm.setData(new ChildFullExpressionInfo(buildChildExpression(exprDmc.getExpression(), child.getExp())));
childPathRm.done();
} else {
// To build the child id, we need the fully qualified expression which we
// can get from -var-info-path-expression starting from GDB 6.7
fCommandControl.queueCommand(
fCommandFactory.createMIVarInfoPathExpression(getRootToUpdate().getControlDMContext(), child.getVarName()),
new DataRequestMonitor<MIVarInfoPathExpressionInfo>(fSession.getExecutor(), childPathRm) {
@Override
protected void handleCompleted() {
if (isSuccess()) {
final String expression = getData().getFullExpression();
if (needFixForGDBBug320277() && child.getExp().equals(child.getType()) && !isAccessQualifier(getExpressionInfo().getRelExpr())) {
// Special handling for a derived class that is cast to its base class (see bug 320277)
//
// If the name of a child equals its type then it could be a base class.
// The exception is when the name of the actual variable is identical with the type name (bad coding style :-))
// The only way to tell the difference is to check if the parent is a fake (public/private/protected).
//
// What we could do instead, is make sure we are using C++ (using -var-info-expression). That would
// be safer. However, at this time, there does not seem to be a way to create a variable with the same
// name and type using plain C, so we are safe.
//
// When we know we are dealing with derived class that is cast to its base class
// -var-info-path-expression returns (*(testbase*) this) and in some cases
// this expression will fail when being evaluated in GDB because of a GDB bug.
// Instead, we need (*(struct testbase*) this).
//
// To check if GDB actually has this bug we call -data-evaluate-expression with the return value
// of -var-info-path-expression
IExpressionDMContext exprDmcMIData = fExpressionService.createExpression(exprDmc, expression);
fCommandControl.queueCommand(
fCommandFactory.createMIDataEvaluateExpression(exprDmcMIData),
new DataRequestMonitor<MIDataEvaluateExpressionInfo>(fSession.getExecutor(), childPathRm) {
@Override
protected void handleCompleted() {
if (isSuccess()) {
childPathRm.setData(new ChildFullExpressionInfo(expression));
childPathRm.done();
} else {
// We build the expression ourselves
// We must also indicate that this workaround has been used for this child
// so that we know to keep using it for further descendants.
childPathRm.setData(new ChildFullExpressionInfo(buildDerivedChildExpression(exprDmc.getExpression(), child.getExp()), true));
childPathRm.done();
}
}
});
} else {
childPathRm.setData(new ChildFullExpressionInfo(expression));
childPathRm.done();
}
} else {
// If we don't have var-info-path-expression
// build the expression ourselves
// Note that this does not work well yet
childPathRm.setData(new ChildFullExpressionInfo(buildChildExpression(exprDmc.getExpression(), child.getExp())));
childPathRm.done();
}
}
});
}
}
countingRm.setDoneCount(numSubRequests);
}
});
}
/**
* Create a child variable of this MIVariableObject and initialize
* it from the given MIVar data.
*
* @param childId
* @param childFullExpression
* @param indexInParent
* @param childData
*
* @return The new child.
*
* @since 4.0
*/
protected MIVariableObject createChild(final VariableObjectId childId,
final String childFullExpression, final int indexInParent,
final MIVar childData) {
MIVariableObject var = createVariableObject(childId, this);
ExpressionInfo childInfo = new ExpressionInfo(childFullExpression,
childData.getExp(), childData.isDynamic(), exprInfo,
indexInParent);
var.initFrom(childData, childInfo);
return var;
}
/**
* @param clientLimit
* @return True, if the client specified limit requires to check for
* further children.
*
* @since 4.0
*/
protected boolean requiresAdditionalChildren(int clientLimit) {
return !isSafeToAskForAllChildren()
&& (exprInfo.getChildCountLimit() < calculateNewLimit(clientLimit));
}
/**
* @param newLimit
* The new limit on the number of children being asked for or
* being updated.
*
* @return The new limit.
* @since 4.0
*/
protected int updateLimit(int newLimit) {
exprInfo.setChildCountLimit(calculateNewLimit(newLimit));
return exprInfo.getChildCountLimit();
}
private int calculateNewLimit(int clientLimit) {
int limit = exprInfo.getChildCountLimit();
if (!isSafeToAskForAllChildren() && clientLimit != IMIExpressions.CHILD_COUNT_LIMIT_UNSPECIFIED) {
if (limit == IMIExpressions.CHILD_COUNT_LIMIT_UNSPECIFIED) {
return clientLimit;
} else if (limit < clientLimit) {
return clientLimit;
}
}
return limit;
}
/**
* Obtain the children of the given fake child (public, protected, or
* private) and insert them into a list of children at a given position.
*
* @param fakeChild
* @param frameCtxProvider
* @param realChildren
* @param position
* @param rm The request monitor on which to call done upon completion.
*/
private void addRealChildrenOfFake(MIVariableObject fakeChild,
IExpressionDMContext frameCtxProvider,
final ExpressionInfo[][] realChildren, final int position,
final RequestMonitor rm) {
MIExpressionDMC fakeExprCtx = createExpressionCtx(frameCtxProvider,
fakeChild.exprInfo);
// This is just a qualifier level of C++, and we must get the
// children of this child to get the real children
fakeChild.getChildren(fakeExprCtx,
IMIExpressions.CHILD_COUNT_LIMIT_UNSPECIFIED,
new DataRequestMonitor<ChildrenInfo>(
fSession.getExecutor(), rm) {
@Override
protected void handleCompleted() {
if (isSuccess()) {
realChildren[position] = getData()
.getChildren();
// Fake nodes can never be dynamic varobjs, and because
// of this, hasMore is always false.
assert (!getData().hasMore());
}
rm.done();
}
});
}
/**
* Method performing special handling for a derived class that is cast to its base class (see bug 320277).
* The command '-var-info-path-expression' returns (*(testbase*) this) but we need (*(struct testbase*) this).
* Also, in case of a namespace this method adds additional backticks:
* (*(struct 'namespace::testbase'*) this)
*/
private String buildDerivedChildExpression(String parentExp, String childExpr) {
final String CAST_PREFIX = "struct "; //$NON-NLS-1$
// Before doing the cast, let's surround the child expression (base class name) with quotes
// if it contains a :: which indicates a namespace
String childNameForCast = childExpr.contains("::") ? "'" + childExpr + "'" : childExpr; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
String childFullExpression;
if (isPointer()) {
// casting to pointer base class requires a slightly different format
childFullExpression = "*(" + CAST_PREFIX + childNameForCast + "*)(" + parentExp + ")";//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
} else {
// casting to base class
childFullExpression = "(" + CAST_PREFIX + childNameForCast + ")" + parentExp;//$NON-NLS-1$ //$NON-NLS-2$
}
return childFullExpression;
}
/**
* This method builds a child expression based on its parent's expression.
* It is a fallback solution for when GDB doesn't support the var-info-path-expression.
*
* This method does not take care of inherited classes such as
* class foo : bar {
* ...
* }
* that case is hanlded by buildDerivedChildExpression
*/
private String buildChildExpression(String parentExp, String childExp) {
String childFullExpression;
// If the current varObj is a fake object, we obtain the proper parent
// expression from the parent of the varObj.
if (isAccessQualifier(exprInfo.getRelExpr())) {
parentExp = getParent().getExpression();
}
// For pointers, the child expression is already contained in the parent,
// so we must simply prefix with *
// See Bug219179 for more information.
if (!isDynamic() && !exprInfo.hasDynamicAncestor() && isPointer()) {
childFullExpression = "*("+parentExp+")"; //$NON-NLS-1$//$NON-NLS-2$
} else {
// We must surround the parentExp with parentheses because it
// may be a casted expression.
childFullExpression = "("+parentExp+")." + childExp; //$NON-NLS-1$ //$NON-NLS-2$
}
// No need for a special case for arrays since we deal with arrays differently
// and don't call this method for them
return childFullExpression;
}
/**
* This method returns the count of children of the variable object
* passed as a parameter.
*
* @param exprDmc
*
* @param numChildrenLimit
* No need to check for more than this number of children.
* However, it is legal to return a higher count if
* we already new from earlier call that there are more
* children.
*
* @param rm
* The data request monitor that will hold the count of
* children returned
* @since 4.3
*/
protected void getChildrenCount(IExpressionDMContext exprDmc, final int numChildrenLimit,
final DataRequestMonitor<ChildrenCountInfo> rm) {
if (isNumChildrenHintTrustworthy()){
rm.setData(new ChildrenCountInfo(getNumChildrenHint(), hasMore()));
rm.done();
return;
}
getChildren(exprDmc, numChildrenLimit,
new DataRequestMonitor<ChildrenInfo>(fSession.getExecutor(), rm) {
@Override
protected void handleSuccess() {
rm.setData(new ChildrenCountInfo(getData()
.getChildren().length, getData().hasMore()));
rm.done();
}
});
}
/**
* This method request the back-end to change the value of the variable object.
*
* @param value
* The new value.
* @param formatId
* The format the new value is specified in.
* @param rm
* The request monitor to indicate the operation is finished
*/
private void writeValue(String value, String formatId, final RequestMonitor rm) {
// If the variable is a complex structure (including an array), then we cannot write to it
if (isComplex()) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.REQUEST_FAILED,
"Cannot change the value of a complex expression", null)); //$NON-NLS-1$
rm.done();
return;
}
// First deal with the format. For GDB, the way to specify a format is to prefix the value with
// 0x for hex, 0 for octal etc So we need to make sure that 'value' has this prefix.
// Note that there is no way to specify a binary format for GDB up to and including
// GDB 6.7.1, so we convert 'value' into a decimal format.
// If the formatId is NATURAL, we do nothing for now because it is more complicated.
// For example for a bool, a value of "true" is correct and should be left as is,
// but for a pointer a value of 16 should be sent to GDB as 0x16. To figure this out,
// we need to know the type of the variable, which we don't have yet.
if (formatId.equals(IFormattedValues.HEX_FORMAT)) {
if (!value.startsWith("0x")) value = "0x" + value; //$NON-NLS-1$ //$NON-NLS-2$
}
else if (formatId.equals(IFormattedValues.OCTAL_FORMAT)) {
if (!value.startsWith("0")) value = "0" + value; //$NON-NLS-1$ //$NON-NLS-2$
}
else if (formatId.equals(IFormattedValues.BINARY_FORMAT)) {
// convert from binary to decimal
if (value.startsWith("0b")) value = value.substring(2, value.length()); //$NON-NLS-1$
try {
value = Integer.toString(Integer.parseInt(value, 2));
} catch (NumberFormatException e) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.INVALID_HANDLE,
"Invalid binary number: " + value, e)); //$NON-NLS-1$
rm.done();
return;
}
formatId = IFormattedValues.DECIMAL_FORMAT;
}
else if (formatId.equals(IFormattedValues.DECIMAL_FORMAT)) {
// nothing to do
}
else if (formatId.equals(IFormattedValues.NATURAL_FORMAT)) {
// we do nothing for now and let the user have put in the proper value
}
else {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.INVALID_HANDLE,
"Unknown format: " + formatId, null)); //$NON-NLS-1$
rm.done();
return;
}
// If the value has not changed, no need to set it.
// Return a warning status so that handleSuccess is not called and we don't send
// an ExpressionChanged event
if (value.equals(getValue(formatId))) {
rm.setStatus(new Status(IStatus.WARNING, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.NOT_SUPPORTED,
"Setting to the same value of: " + value, null)); //$NON-NLS-1$
rm.done();
return;
}
// No need to be in ready state or to lock the object
fCommandControl.queueCommand(
fCommandFactory.createMIVarAssign(getRootToUpdate().getControlDMContext(), getGdbName(), value),
new DataRequestMonitor<MIVarAssignInfo>(fSession.getExecutor(), rm) {
@Override
protected void handleSuccess() {
// We must also mark all variable objects
// as out-of-date. This is because some variable objects may be affected
// by this one having changed.
// e.g.,
// int i;
// int* pi = &i;
// Here, if 'i' is changed by the user, then 'pi' will also change
// Since there is no way to know this unless we keep track of all addresses,
// we must mark everything as out-of-date. See bug 213061
markAllOutOfDate();
// Useless since we just marked everything as out-of-date
// resetValues(getData().getValue());
rm.done();
}
});
}
private boolean isAccessQualifier(String str) {
return str.equals("private") || str.equals("public") || str.equals("protected"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
/**
* @return If true, this variable object can be reported as changed in
* a -var-update MI command.
*/
public boolean isModifiable() {
if (!isComplex() || isDynamic()) return true;
return false;
}
/**
* @param exprCtx
* @param rm
*
* @since 4.0
*/
public void create(final IExpressionDMContext exprCtx,
final RequestMonitor rm) {
if (currentState == STATE_NOT_CREATED) {
currentState = STATE_CREATING;
final MIExpressionDMC miExprCtx = (MIExpressionDMC) exprCtx;
final int indexInParent = miExprCtx.getExpressionInfo().getIndexInParentExpression();
fCommandControl.queueCommand(fCommandFactory.createMIVarListChildren(getParent().getRootToUpdate().getControlDMContext(),
getParent().getGdbName(), indexInParent, indexInParent + 1),
new DataRequestMonitor<MIVarListChildrenInfo>(fSession.getExecutor(), rm) {
@Override
protected void handleSuccess() {
if (getData().getMIVars().length == 1) {
MIVar miVar = getData().getMIVars()[0];
ExpressionInfo localExprInfo = miExprCtx.getExpressionInfo();
localExprInfo.setDynamic(miVar.isDynamic());
initFrom(miVar, localExprInfo);
if (exprInfo.isDynamic()
&& (exprInfo.getChildCountLimit() != IMIExpressions.CHILD_COUNT_LIMIT_UNSPECIFIED)) {
// Restore the original update range.
fCommandControl.queueCommand(fCommandFactory.createMIVarSetUpdateRange(
getRootToUpdate().getControlDMContext(),
getGdbName(), 0, exprInfo.getChildCountLimit()),
new DataRequestMonitor<MIInfo>(fSession.getExecutor(), rm));
} else {
rm.done();
}
} else {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.INTERNAL_ERROR,
"Unexpected return on -var-list-children", null)); //$NON-NLS-1$
rm.done();
}
}
});
} else {
assert false;
}
}
private void initFrom(MIVar miVar, ExpressionInfo newExprInfo) {
// Possible Optimization in GDB: In -var-list-children, the has_more
// field is missing for the children. As a workaround, we assume that
// if numChild is 0 and has_more is omitted, we have more children.
boolean newHasMore = miVar.hasMore()
|| (miVar.isDynamic() && (miVar.getNumChild() == 0));
setGdbName(miVar.getVarName());
setDisplayHint(miVar.getDisplayHint());
setExpressionData(
newExprInfo,
miVar.getType(),
miVar.getNumChild(),
newHasMore);
// This will replace any existing entry
lruVariableList.put(getInternalId(), this);
// Is this new child a modifiable descendant of the root?
if (isModifiable()) {
getRootToUpdate().addModifiableDescendant(miVar.getVarName(), this);
}
}
}
/**
* Method to allow to override the MIVariableObject creation
*
* @since 3.0
*/
protected MIVariableObject createVariableObject(VariableObjectId id, MIVariableObject parentObj) {
return new MIVariableObject(id, parentObj);
}
/**
* Method to allow to override the MIVariableObject creation
*
* @since 4.0
*/
protected MIVariableObject createVariableObject(VariableObjectId id,
MIVariableObject parentObj, boolean needsCreation) {
return new MIVariableObject(id, parentObj, needsCreation);
}
/**
* @since 3.0
*/
public class MIRootVariableObject extends MIVariableObject {
// The control context within which this variable object was created
// It only needs to be stored in the Root VarObj since any children
// will have the same control context
private ICommandControlDMContext fControlContext = null;
private boolean fOutOfDate = false;
/**
* A modifiable descendant is any variable object that is a descendant and
* for which the value (leaf variable objects and dynamic variable objects)
* or number of children (dynamic variable objects) can change.
*/
private Map<String, MIVariableObject> modifiableDescendants;
public MIRootVariableObject(VariableObjectId id) {
super(id, null);
currentState = STATE_NOT_CREATED;
modifiableDescendants = new HashMap<String, MIVariableObject>();
}
public ICommandControlDMContext getControlDMContext() { return fControlContext; }
public boolean isUpdating() { return currentState == STATE_UPDATING; }
public void setOutOfDate(boolean outOfDate) { fOutOfDate = outOfDate; }
public boolean getOutOfDate() { return fOutOfDate; }
// Remember that we must add ourself as a modifiable descendant if our value can change
public void addModifiableDescendant(String gdbName, MIVariableObject descendant) {
modifiableDescendants.put(gdbName, descendant);
}
/**
* Removes the descendant with the specified name from the collection of
* modifiable descendants. Does nothing if there is no child with such
* name.
*
* @since 4.1
*/
public void removeModifiableDescendant(String gdbName) {
modifiableDescendants.remove(gdbName);
}
/**
* @since 4.0
*/
public void processChanges(MIVarChange[] updates, RequestMonitor rm) {
CountingRequestMonitor countingRm = new CountingRequestMonitor(fSession.getExecutor(), rm);
countingRm.setDoneCount(updates.length);
for (MIVarChange update : updates) {
MIVariableObject descendant = modifiableDescendants.get(update.getVarName());
// Descendant should never be null, but just to be safe
if (descendant != null) {
descendant.processChange(update, countingRm);
} else {
// This can for instance happen if a child MIVariableObject is deleted
// from the cache. The corresponding varobj in gdb does still exist,
// and if it is changed, gdb reports it as changed in -var-update.
countingRm.done();
}
}
}
@Override
public void create(final IExpressionDMContext exprCtx,
final RequestMonitor rm) {
if (currentState == STATE_NOT_CREATED) {
currentState = STATE_CREATING;
fControlContext = DMContexts.getAncestorOfType(exprCtx, ICommandControlDMContext.class);
fCommandControl.queueCommand(
fCommandFactory.createMIVarCreate(exprCtx, exprCtx.getExpression()),
new DataRequestMonitor<MIVarCreateInfo>(fSession.getExecutor(), rm) {
@Override
protected void handleCompleted() {
if (isSuccess()) {
setGdbName(getData().getName());
setDisplayHint(getData().getDisplayHint());
MIExpressionDMC miExprCtx = (MIExpressionDMC) exprCtx;
ExpressionInfo localExprInfo = miExprCtx
.getExpressionInfo();
localExprInfo.setDynamic(getData()
.isDynamic());
// Do not initialize the parent or indexInParent, since they may
// already be set to something. This will happen for arrays.
// Their default values are ok for other cases i.e., null and -1
// bug 420366
setExpressionData(
localExprInfo,
getData().getType(),
getData().getNumChildren(),
getData().hasMore());
// Store the value returned at create (available in GDB 6.7)
// Don't store if it is an array, since we want to show
// the address of an array as its value
if (getData().getValue() != null && !isArray()) {
setValue(getCurrentFormat(), getData().getValue());
}
// If we are modifiable, we should be in our modifiable list
if (isModifiable()) {
addModifiableDescendant(getData().getName(), MIRootVariableObject.this);
}
if (localExprInfo.isDynamic()
&& (localExprInfo.getChildCountLimit() != IMIExpressions.CHILD_COUNT_LIMIT_UNSPECIFIED)) {
// Restore the original update range.
fCommandControl.queueCommand(fCommandFactory.createMIVarSetUpdateRange(
getRootToUpdate().getControlDMContext(),getGdbName(),
0,localExprInfo.getChildCountLimit()),
new DataRequestMonitor<MIInfo>(fSession.getExecutor(), rm));
} else {
rm.done();
}
} else {
rm.setStatus(getStatus());
rm.done();
}
}
});
} else {
assert false;
}
}
@Override
public void update(final DataRequestMonitor<Boolean> rm) {
if (isOutOfScope()) {
rm.setData(false);
rm.done();
} else if (currentState != STATE_READY) {
// Object is not fully created or is being updated
// so add RequestMonitor to pending queue
updatesPending.add(rm);
} else if (getOutOfDate() == false) {
rm.setData(false);
rm.done();
} else {
// Object needs to be updated in the back-end
currentState = STATE_UPDATING;
// In GDB, var-update will only report a change if -var-evaluate-expression has
// changed -- in the current format--. This means that situations like
// double z = 1.2;
// z = 1.4;
// Will not report a change if the format is anything else than natural.
// This is because 1.2 and 1.4 are both printed as 1, 0x1, etc
// Since we cache the values of every format, we must know if -any- format has
// changed, not just the current one.
// To solve this, we always do an update in the natural format; I am not aware
// of any case where the natural format would stay the same, but another format
// would change. However, since a var-update update all children as well,
// we must make sure these children are also in the natural format
// The simplest way to do this is that whenever we change the format
// of a variable object, we immediately set it back to natural with a second
// var-set-format command. This is done in the getValue() method
fCommandControl.queueCommand(
fCommandFactory.createMIVarUpdate(getRootToUpdate().getControlDMContext(), getGdbName()),
new DataRequestMonitor<MIVarUpdateInfo>(fSession.getExecutor(), rm) {
@Override
protected void handleCompleted() {
if (isSuccess()) {
setOutOfDate(false);
MIVarChange[] changes = getData().getMIVarChanges();
if (changes.length > 0 && changes[0].isInScope() == false) {
// Object is out-of-scope
currentState = STATE_READY;
outOfScope = true;
// We can delete this root in GDB right away. This is safe, even
// if the root has children, because they are also out-of-scope.
// We -must- also remove this entry from our LRU. If we don't
// we can end-up with a race condition that create this object
// twice, or have an infinite loop while never re-creating the object.
// The can happen if we update a child first then we request
// the root later,
lruVariableList.remove(getInternalId());
rm.setData(true);
rm.done();
while (updatesPending.size() > 0) {
DataRequestMonitor<Boolean> pendingRm = updatesPending.poll();
pendingRm.setData(false);
pendingRm.done();
}
} else {
// The root object is now up-to-date, we must parse the changes, if any.
processChanges(changes, new RequestMonitor(fSession.getExecutor(), rm) {
@Override
protected void handleCompleted() {
currentState = STATE_READY;
// We only mark this root as updated in our list if it is in-scope.
// For out-of-scope object, we don't ever need to re-update them so
// we don't need to add them to this list.
rootVariableUpdated(MIRootVariableObject.this);
if (isSuccess()) {
rm.setData(false);
} else {
rm.setStatus(getStatus());
}
rm.done();
while (updatesPending.size() > 0) {
DataRequestMonitor<Boolean> pendingRm = updatesPending.poll();
if (isSuccess()) {
pendingRm.setData(false);
} else {
pendingRm.setStatus(getStatus());
}
pendingRm.done();
}
};
});
}
} else {
// We were not able to update for some reason
currentState = STATE_READY;
rm.setData(false);
rm.done();
while (updatesPending.size() > 0) {
DataRequestMonitor<Boolean> pendingRm = updatesPending.poll();
pendingRm.setStatus(getStatus());
pendingRm.done();
}
}
}
});
}
}
/**
* This method request the back-end to delete a variable object.
* We check if the GDB name has been filled to confirm that this object
* was actually successfully created on the back-end.
* Only root variable objects are deleted, while children are left in GDB
* to be deleted automatically when their root is deleted.
*/
@Override
public void deleteInGdb() {
if (getGdbName() != null) {
fCommandControl.queueCommand(
fCommandFactory.createMIVarDelete(getRootToUpdate().getControlDMContext(), getGdbName()),
new DataRequestMonitor<MIVarDeleteInfo>(fSession.getExecutor(), null));
// Nothing to do in the requestMonitor, since the object was already
// removed from our list before calling this method.
// Set the GDB name to null to make sure we don't attempt to delete
// this variable a second time. This can happen if the LRU triggers
// an automatic delete.
setGdbName(null);
} else {
// Variable was never created or was already deleted, no need to do anything.
}
super.deleteInGdb();
}
}
/**
* Method to allow to override the MIRootVariableObject creation.
*
* @since 3.0
*/
protected MIRootVariableObject createRootVariableObject(VariableObjectId id) {
return new MIRootVariableObject(id);
}
/**
* This class represents an unique identifier for a variable object.
*
* The following must be considered to obtain a unique name:
* - the expression itself
* - the execution context
* - relative depth of frame based on the frame context and the total depth of the stack
*
* Note that if no frameContext is specified (only Execution, or even only Container), which can
* characterize a global variable for example, we will only use the available information.
*
* @since 3.0
*/
public class VariableObjectId {
// We don't use the expression context because it is not safe to compare them
// See bug 187718. So we store the expression itself, and it's parent execution context.
String fExpression = null;
IExecutionDMContext fExecContext = null;
// We need the depth of the frame. The frame level is not sufficient because
// the same frame will have a different level based on the current depth of the stack
Integer fFrameId = null;
public VariableObjectId() {
}
@Override
public boolean equals(Object other) {
if (other instanceof VariableObjectId) {
VariableObjectId otherId = (VariableObjectId) other;
return (fExpression == null ? otherId.fExpression == null : fExpression.equals(otherId.fExpression)) &&
(fExecContext == null ? otherId.fExecContext == null : fExecContext.equals(otherId.fExecContext)) &&
(fFrameId == null ? otherId.fFrameId == null : fFrameId.equals(otherId.fFrameId));
}
return false;
}
@Override
public int hashCode() {
return (fExpression == null ? 0 : fExpression.hashCode()) +
(fExecContext == null ? 0 : fExecContext.hashCode()) +
(fFrameId == null ? 0 : fFrameId.hashCode());
}
public void generateId(IExpressionDMContext exprCtx, final RequestMonitor rm) {
fExpression = exprCtx.getExpression();
fExecContext = DMContexts.getAncestorOfType(exprCtx, IExecutionDMContext.class);
if (fExecContext == null) {
rm.done();
return;
}
final IFrameDMContext frameCtx = DMContexts.getAncestorOfType(exprCtx, IFrameDMContext.class);
if (frameCtx == null) {
rm.done();
return;
}
// We need the current stack depth to be able to make a unique and reproducible name
// for this expression. This is pretty efficient since the stackDepth will be retrieved
// from the StackService command cache after the first time.
fStackService.getStackDepth(
fExecContext, 0,
new DataRequestMonitor<Integer>(fSession.getExecutor(), rm) {
@Override
protected void handleSuccess() {
fFrameId = new Integer(getData() - frameCtx.getLevel());
rm.done();
}
});
}
public void generateId(String childFullExp, VariableObjectId parentId) {
// The execution context and the frame depth are the same as the parent
fExecContext = parentId.fExecContext;
fFrameId = parentId.fFrameId;
// The expression here must be the one that is part of IExpressionContext for this child
// This will allow us to find a variable object directly
fExpression = childFullExp;
}
}
/**
* Method to allow to override the VariableObjectId creation.
*
* @since 3.0
*/
protected VariableObjectId createVariableObjectId() {
return new VariableObjectId();
}
/**
* This is the real work horse of managing our objects. Not only must every
* value be unique to get inserted, this also creates an LRU (least recently
* used). When we hit our size limitation, the LRUsed will be removed to
* make space. Removing means that a GDB request to delete the object is
* generated. We must also take into consideration the fact that GDB will
* automatically delete children of a variable object, when deleting the parent
* variable object. Our solution to that is to tweak the LRU to make sure that
* children are always older than their parents, to guarantee the children will
* always be delete before their parents.
*
*/
private class LRUVariableCache extends LinkedHashMap<VariableObjectId, MIVariableObject> {
public static final long serialVersionUID = 0;
// Maximum allowed concurrent variables
private static final int MAX_VARIABLE_LIST = 1000;
public LRUVariableCache() {
super(0, // Initial load capacity
0.75f, // Load factor as defined in JAVA 1.5
true); // Order is dictated by access, not insertion
}
// We never remove doing put operations. Instead, we rely on our get() operations
// to trigger the remove. See bug 200897
@Override
public boolean removeEldestEntry(Map.Entry<VariableObjectId, MIVariableObject> eldest) {
return false;
}
@Override
public MIVariableObject get(Object key) {
MIVariableObject varObj = super.get(key);
touchAncestors(varObj);
// If we're over our max size, attempt to remove eldest entry.
if (size() > MAX_VARIABLE_LIST) {
Map.Entry<VariableObjectId, MIVariableObject> eldest = entrySet().iterator().next();
// First make sure we are not deleting ourselves!
if (!eldest.getValue().equals(varObj)) {
if (eldest.getValue().currentState == MIVariableObject.STATE_READY) {
remove(eldest.getKey());
}
}
}
return varObj;
}
private void touchAncestors(MIVariableObject varObj) {
while (varObj != null) {
varObj = varObj.getParent();
// If there is a parent, touch it
if (varObj != null) super.get(varObj.getInternalId());
}
}
@Override
public MIVariableObject put(VariableObjectId key, MIVariableObject varObj) {
MIVariableObject retVal = super.put(key, varObj);
// Touch all parents of this element so as
// to guarantee they are not deleted before their children.
touchAncestors(varObj);
return retVal;
}
@Override
public MIVariableObject remove(Object key) {
MIVariableObject varObj = super.remove(key);
if (varObj != null) {
varObj.deleteInGdb();
}
return varObj;
}
}
/**
* @since 3.0
*/
private static final GDBTypeParser fGDBTypeParser = new GDBTypeParser();
private final DsfSession fSession;
/** Provides access to the GDB/MI back-end */
private final ICommandControl fCommandControl;
private CommandFactory fCommandFactory;
// The stack service needs to be used to get information such
// as the stack depth to differentiate between expressions that have the
// same name but refer to a different context
private final IStack fStackService;
private IExpressions fExpressionService;
// Typically, there will only be one listener, since only the ExpressionService will use this class
private final List<ICommandListener> fCommandProcessors = new ArrayList<ICommandListener>();
/** Our least recently used cache */
private final LRUVariableCache lruVariableList;
/** The list of root variable objects that have been updated */
private final LinkedList<MIRootVariableObject> updatedRootList = new LinkedList<MIRootVariableObject>();
/**
* MIVariableManager constructor
*
* @param session
* The session we are working with
* @param tracker
* The service tracker that can be used to find other services
*/
public MIVariableManager(DsfSession session, DsfServicesTracker tracker) {
fSession = session;
lruVariableList = new LRUVariableCache();
fCommandControl = tracker.getService(ICommandControl.class);
fStackService = tracker.getService(IStack.class);
fExpressionService = tracker.getService(IExpressions.class);
fCommandFactory = tracker.getService(IMICommandControl.class).getCommandFactory();
// Register to receive service events for this session.
fSession.addServiceEventListener(this, null);
}
public void dispose() {
fSession.removeServiceEventListener(this);
}
/**
* @since 3.0
*/
protected DsfSession getSession() {
return fSession;
}
/**
* @since 3.0
*/
protected ICommandControl getCommandControl() {
return fCommandControl;
}
/**
* @since 3.0
*/
protected void rootVariableUpdated(MIRootVariableObject rootObj) {
updatedRootList.add(rootObj);
}
/**
* @since 3.0
*/
protected Map<VariableObjectId, MIVariableObject> getLRUCache() {
return lruVariableList;
}
/**
* This method returns a variable object based on the specified
* ExpressionDMC, creating it in GDB if it was not created already.
* The method guarantees that the variable is finished creating and that
* is it not out-of-scope.
*
* @param exprCtx
* The expression context to which the variable object is applied to.
*
* @param rm
* The data request monitor that will contain the requested variable object
*/
private void getVariable(final IExpressionDMContext exprCtx,
final DataRequestMonitor<MIVariableObject> rm) {
// Generate an id for this expression so that we can determine if we already
// have a variable object tracking it. If we don't we'll need to create one.
final VariableObjectId id = createVariableObjectId();
id.generateId(
exprCtx,
new RequestMonitor(fSession.getExecutor(), rm) {
@Override
protected void handleSuccess() {
getVariable(id, exprCtx, rm);
}
});
}
private void getVariable(final VariableObjectId id,
final IExpressionDMContext exprCtx,
final DataRequestMonitor<MIVariableObject> rm) {
final MIVariableObject varObj = lruVariableList.get(id);
if (varObj == null) {
// We do not have this varObject, so we create it
createVariable(id, exprCtx, rm);
} else {
// We have found a varObject, but it may not be updated yet.
// Updating the object will also tell us if it is out-of-scope
// and if we should re-create it.
varObj.update(new DataRequestMonitor<Boolean>(fSession.getExecutor(), rm) {
@Override
protected void handleSuccess() {
boolean shouldCreateNew = getData().booleanValue();
if (varObj.isOutOfScope()) {
// The variable object is out-of-scope and we
// should not use it.
if (shouldCreateNew) {
/*
* It may happen that when accessing a varObject we find it to be
* out-of-scope. The expression for which we are trying to access a varObject
* could still be valid, and therefore we should try to create a new varObject for
* that expression. This can happen for example if two methods use the same name
* for a variable. In the case when we find that a varObject is out-of-scope (when
* its root is out-of-scope) the following should be done:
*
* - create a new varObject for the expression (as a root varObject) and insert it
* in the LRU. Make sure that when creating children of this new varObject, they
* will replace any old children with the same name in the LRU (this is ok since the
* children being replaced are also out-of-scope).
*/
createVariable(id, exprCtx, rm);
} else {
// Just request the variable object again
// We must use this call to handle the fact that
// the new object might be in the middle of being
// created.
getVariable(id, exprCtx, rm);
}
} else {
// The variable object is up-to-date and valid
MIExpressionDMC miExprCtx = (MIExpressionDMC) exprCtx;
ExpressionInfo ctxExprInfo = miExprCtx.getExpressionInfo();
ExpressionInfo varExprInfo = varObj.getExpressionInfo();
if (ctxExprInfo != varExprInfo) {
// exprCtrx could just be created via IExpressions.createExpression,
// and thus the parent-child relationship is not yet set.
miExprCtx.setExpressionInfo(varExprInfo);
}
rm.setData(varObj);
rm.done();
}
}
});
}
}
/**
* This method creates a variable object in GDB.
*/
private void createVariable(final VariableObjectId id,
final IExpressionDMContext exprCtx,
final DataRequestMonitor<MIVariableObject> rm) {
// If we have a dynamic variable object as ancestor, we cannot use
// -var-create, so we must create ourselves creating the root, and
// then use -var-list-children for each further ancestor.
final MIExpressionDMC miExprCtx =(MIExpressionDMC) exprCtx;
final ExpressionInfo parentInfo = miExprCtx.getExpressionInfo().getParent();
if ((parentInfo != null) && miExprCtx.getExpressionInfo().hasDynamicAncestor()) {
// Need to set parent when it is known.
final MIVariableObject newVarObj = createVariableObject(id, null, true);
// We must put this object in our map right away, in case it is
// requested again, before it completes its creation.
// Note that this will replace any old entry with the same id.
lruVariableList.put(id, newVarObj);
MIExpressionDMC parentExprCtx = createExpressionCtx(exprCtx,
parentInfo);
getVariable(parentExprCtx,
new DataRequestMonitor<MIVariableObject>(
fSession.getExecutor(), rm) {
@Override
protected void handleCompleted() {
if (isSuccess()) {
final MIVariableObject parentObj = getData();
newVarObj.setParent(parentObj);
newVarObj.create(miExprCtx, new RequestMonitor(fSession.getExecutor(), rm) {
@Override
protected void handleCompleted() {
if (isSuccess()) {
rm.setData(newVarObj);
newVarObj.creationCompleted(true);
} else {
// Object was not created, remove it from our list
lruVariableList.remove(id);
// We avoid this race condition by sending the notifications _after_ removing
// the object from the LRU, to avoid any new requests being queue.
// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=231655
newVarObj.creationCompleted(false);
rm.setStatus(getStatus());
}
rm.done();
}
});
} else {
// Object was not created, remove it from our list
lruVariableList.remove(id);
// We avoid this race condition by sending the notifications _after_ removing
// the object from the LRU, to avoid any new requests being queue.
// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=231655
newVarObj.creationCompleted(false);
rm.setStatus(getStatus());
rm.done();
}
}
});
return;
}
// Variable objects that are created directly like this, are considered ROOT variable objects
// in comparison to variable objects that are children of other variable objects.
final MIRootVariableObject newVarObj = createRootVariableObject(id);
// We must put this object in our map right away, in case it is
// requested again, before it completes its creation.
// Note that this will replace any old entry with the same id.
lruVariableList.put(id, newVarObj);
newVarObj.create(exprCtx, new RequestMonitor(fSession.getExecutor(), rm) {
@Override
protected void handleCompleted() {
if (isSuccess()) {
// Also store the object as a varObj that is up-to-date
rootVariableUpdated(newVarObj);
// VarObj can now be used by others
newVarObj.creationCompleted(true);
rm.setData(newVarObj);
rm.done();
} else {
// Object was not created, remove it from our list
lruVariableList.remove(id);
// Tell any waiting monitors that creation failed.
// It is important to do this call after we have removed the id
// from our LRU; this is to avoid the following:
// The same varObj is requested before it was removed from the LRU
// but after we called creationCompleted().
// In this case, the request for this varObj would be queued, but
// since creationCompleted() already sent the notifications
// the newly queue request will never get serviced.
// We avoid this race condition by sending the notifications _after_ removing
// the object from the LRU, to avoid any new requests being queue.
// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=231655
newVarObj.creationCompleted(false);
rm.setStatus(getStatus());
rm.done();
}
}
});
}
private MIExpressionDMC createExpressionCtx(
final IExpressionDMContext frameCtxProvider,
final ExpressionInfo exprInfo) {
IFrameDMContext frameCtx = DMContexts.getAncestorOfType(frameCtxProvider, IFrameDMContext.class);
MIExpressionDMC exprCtx = new MIExpressionDMC(
frameCtxProvider.getSessionId(), exprInfo, frameCtx);
return exprCtx;
}
/**
* This method requests the back-end to change the value of an expression.
*
* @param ctx
* The context of the expression we want to change
* @param expressionValue
* The new value of the expression
* @param formatId
* The format in which the expressionValue is specified in
* @param rm
* The request monitor to indicate the operation is finished
*/
// This method can be called directly from the ExpressionService, since it cannot be cached
public void writeValue(final IExpressionDMContext ctx, final String expressionValue,
final String formatId, final RequestMonitor rm) {
getVariable(
ctx,
new DataRequestMonitor<MIVariableObject>(fSession.getExecutor(), rm) {
@Override
protected void handleSuccess() {
getData().writeValue(expressionValue, formatId, rm);
}
});
}
@Override
public <V extends ICommandResult> ICommandToken queueCommand(final ICommand<V> command, DataRequestMonitor<V> rm) {
final ICommandToken token = new ICommandToken() {
@Override
public ICommand<? extends ICommandResult> getCommand() {
return command;
}
};
// The MIVariableManager does not buffer commands itself, but sends them directly to the real
// MICommandControl service. Therefore, we must immediately tell our calling cache that the command
// has been sent, since we can never cancel it. Note that this removes any option of coalescing,
// but coalescing was not applicable to variableObjects anyway.
processCommandSent(token);
if (command instanceof ExprMetaGetVar) {
@SuppressWarnings("unchecked")
final DataRequestMonitor<ExprMetaGetVarInfo> drm = (DataRequestMonitor<ExprMetaGetVarInfo>)rm;
final MIExpressionDMC exprCtx = (MIExpressionDMC)(command.getContext());
getVariable(
exprCtx,
new DataRequestMonitor<MIVariableObject>(fSession.getExecutor(), drm) {
@Override
protected void handleSuccess() {
final MIVariableObject varObj = getData();
if (varObj.isDynamic() && (varObj.getNumChildrenHint() == 0) && varObj.hasMore()) {
// Bug/feature in gdb? MI sometimes reports 0 number of children, and hasMore=1.
// This however, is not a safe indicator that there are children,
// unless there has been a -var-list-children before.
// The following call will
// 1) try to fetch at least one child, because isNumChildrenHintTrustworthy()
// returns false for this combination
// 2) result in the desired -var-list-children, unless it has happened already
// such that the number of children can at least be used to reliably
// tell whether there are children or not.
varObj.getChildrenCount(
exprCtx, 1,
new DataRequestMonitor<ChildrenCountInfo>(fSession.getExecutor(), drm) {
@Override
protected void handleSuccess() {
drm.setData(
new ExprMetaGetVarInfo(
exprCtx.getRelativeExpression(),
varObj.isSafeToAskForAllChildren(),
getData().getChildrenCount(),
varObj.getType(),
varObj.getGDBType(),
!varObj.isComplex(),
varObj.getDisplayHint().isCollectionHint()));
drm.done();
processCommandDone(token, drm.getData());
}
});
} else {
drm.setData(
new ExprMetaGetVarInfo(
exprCtx.getRelativeExpression(),
varObj.isSafeToAskForAllChildren(),
// We only provide the hint here. It will be used for hasChildren()
// To obtain the correct number of children, the user should use
// IExpressions#getSubExpressionCount()
varObj.getNumChildrenHint(),
varObj.getType(),
varObj.getGDBType(),
!varObj.isComplex(),
varObj.getDisplayHint().isCollectionHint()));
drm.done();
processCommandDone(token, drm.getData());
}
}
});
} else if (command instanceof ExprMetaGetAttributes) {
@SuppressWarnings("unchecked")
final DataRequestMonitor<ExprMetaGetAttributesInfo> drm = (DataRequestMonitor<ExprMetaGetAttributesInfo>)rm;
final IExpressionDMContext exprCtx = (IExpressionDMContext)(command.getContext());
getVariable(
exprCtx,
new DataRequestMonitor<MIVariableObject>(fSession.getExecutor(), drm) {
@Override
protected void handleSuccess() {
getData().getAttributes(
new DataRequestMonitor<Boolean>(fSession.getExecutor(), drm) {
@Override
protected void handleSuccess() {
drm.setData(new ExprMetaGetAttributesInfo(getData()));
drm.done();
processCommandDone(token, drm.getData());
}
});
}
});
} else if (command instanceof ExprMetaGetValue) {
@SuppressWarnings("unchecked")
final DataRequestMonitor<ExprMetaGetValueInfo> drm = (DataRequestMonitor<ExprMetaGetValueInfo>)rm;
final FormattedValueDMContext valueCtx = (FormattedValueDMContext)(command.getContext());
final IExpressionDMContext exprCtx = DMContexts.getAncestorOfType(valueCtx, IExpressionDMContext.class);
getVariable(
exprCtx,
new DataRequestMonitor<MIVariableObject>(fSession.getExecutor(), drm) {
@Override
protected void handleSuccess() {
getData().getValue(
valueCtx,
new DataRequestMonitor<FormattedValueDMData>(fSession.getExecutor(), drm) {
@Override
protected void handleSuccess() {
drm.setData(
new ExprMetaGetValueInfo(getData().getFormattedValue()));
drm.done();
processCommandDone(token, drm.getData());
}
});
}
});
} else if (command instanceof ExprMetaGetChildren) {
@SuppressWarnings("unchecked")
final DataRequestMonitor<ExprMetaGetChildrenInfo> drm = (DataRequestMonitor<ExprMetaGetChildrenInfo>)rm;
final MIExpressionDMC exprCtx = (MIExpressionDMC)(command.getContext());
getVariable(
exprCtx,
new DataRequestMonitor<MIVariableObject>(fSession.getExecutor(), drm) {
@Override
protected void handleSuccess() {
getData().getChildren(exprCtx, ((ExprMetaGetChildren)command).getNumChildLimit(),
new DataRequestMonitor<ChildrenInfo>(fSession.getExecutor(), drm) {
@Override
protected void handleSuccess() {
drm.setData(new ExprMetaGetChildrenInfo(
getData().getChildren()));
drm.done();
processCommandDone(token, drm.getData());
}
});
}
});
} else if (command instanceof ExprMetaGetChildCount) {
@SuppressWarnings("unchecked")
final DataRequestMonitor<ExprMetaGetChildCountInfo> drm = (DataRequestMonitor<ExprMetaGetChildCountInfo>)rm;
final MIExpressionDMC exprCtx = (MIExpressionDMC)(command.getContext());
getVariable(
exprCtx,
new DataRequestMonitor<MIVariableObject>(fSession.getExecutor(), drm) {
@Override
protected void handleSuccess() {
getData().getChildrenCount(
exprCtx, ((ExprMetaGetChildCount) command).getNumChildLimit(),
new DataRequestMonitor<ChildrenCountInfo>(fSession.getExecutor(), drm) {
@Override
protected void handleSuccess() {
drm.setData(new ExprMetaGetChildCountInfo(
getData().getChildrenCount()));
drm.done();
processCommandDone(token, drm.getData());
}
});
}
});
} else if (command instanceof MIDataEvaluateExpression<?>) {
// This does not use the variable objects but sends the command directly to the back-end
fCommandControl.queueCommand(command, rm);
} else {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.INTERNAL_ERROR,
"Unexpected Expression Meta command", null)); //$NON-NLS-1$
rm.done();
}
return token;
}
/*
* This is the command which allows the user to retract a previously issued command. The
* state of the command is that it is in the waiting queue and has not yet been handed
* to the back-end yet.
*
* (non-Javadoc)
* @see org.eclipse.cdt.dsf.mi.service.command.IDebuggerControl#removeCommand(org.eclipse.cdt.dsf.mi.service.command.commands.ICommand)
*/
@Override
public void removeCommand(ICommandToken token) {
// It is impossible to remove a command from the MIVariableManager.
// This should never be called, if we did things right.
assert false;
}
/*
* This command allows the user to try and cancel commands which have been handed off to the
* back-end. Some back-ends support this with extended GDB/MI commands. If the support is there
* then we will attempt it. Because of the bidirectional nature of the GDB/MI command stream
* there is no guarantee that this will work. The response to the command could be on its way
* back when the cancel command is being issued.
*
* (non-Javadoc)
* @see org.eclipse.cdt.dsf.mi.service.command.IDebuggerControl#cancelCommand(org.eclipse.cdt.dsf.mi.service.command.commands.ICommand)
*/
@Override
public void addCommandListener(ICommandListener processor) { fCommandProcessors.add(processor); }
@Override
public void removeCommandListener(ICommandListener processor) { fCommandProcessors.remove(processor); }
@Override
public void addEventListener(IEventListener processor) {}
@Override
public void removeEventListener(IEventListener processor) {}
private void processCommandSent(ICommandToken token) {
for (ICommandListener processor : fCommandProcessors) {
processor.commandSent(token);
}
}
private void processCommandDone(ICommandToken token, ICommandResult result) {
for (ICommandListener processor : fCommandProcessors) {
processor.commandDone(token, result);
}
}
/**
* @since 1.1
*/
public void markAllOutOfDate() {
MIRootVariableObject root;
while ((root = updatedRootList.poll()) != null) {
root.setOutOfDate(true);
}
}
@DsfServiceEventHandler
public void eventDispatched(IRunControl.IResumedDMEvent e) {
// Program has resumed, all variable objects need to be updated.
// Since only roots can actually be updated in GDB, we only need
// to deal with those. Also, to optimize this operation, we have
// a list of all roots that have been updated, so we only have to
// set those to needing to be updated.
markAllOutOfDate();
}
@DsfServiceEventHandler
public void eventDispatched(IRunControl.ISuspendedDMEvent e) {
}
@DsfServiceEventHandler
public void eventDispatched(IMemoryChangedEvent e) {
// Some memory has changed. We currently do not know the address
// of each of our variable objects, so there is no way to know
// which one is affected. Mark them all as out of date.
// The views will fully refresh on a MemoryChangedEvent
markAllOutOfDate();
}
/**
* @since 3.0
*/
@DsfServiceEventHandler
public void eventDispatched(ITraceRecordSelectedChangedDMEvent e) {
// We have a big limitation with tracepoints!
// GDB usually only reports a depth of 1, for every trace record, no
// matter where it occurred. This means that our naming scheme for VariableObjectId
// fails miserably because all objects will have the same depth and we will confuse
// them. Until we find a good solution, we have to clear our entire list of
// of variable objects (and delete them in GDB to avoid having too many).
Iterator<Map.Entry<VariableObjectId, MIVariableObject>> iterator = lruVariableList.entrySet().iterator();
while (iterator.hasNext()){
iterator.next();
iterator.remove();
}
}
/**
* GDB has a bug which makes -data-evaluate-expression fail when using
* the return value of -var-info-path-expression in the case of derived classes.
* To work around this bug, we don't use -var-info-path-expression for some derived
* classes and all their descendants.
*
* This method can be overridden to easily disable the workaround, for versions
* of GDB that no longer have the bug.
*
* See http://sourceware.org/bugzilla/show_bug.cgi?id=11912
* and Bug 320277.
*
* The bug was fixed in GDB 7.3.1.
*
* @since 4.1
*/
protected boolean needFixForGDBBug320277() {
return true;
}
}