| /******************************************************************************* |
| * Copyright (c) 2008, 2015 Monta Vista and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * 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) |
| * Anders Dahlberg (Ericsson) - Need additional API to extend support for memory spaces (Bug 431627) |
| * Alvaro Sanchez-Leon (Ericsson) - Need additional API to extend support for memory spaces (Bug 431627) |
| * Martin Schreiber - Bug 435606 - write unsigned variables (UINT32 and UINT64) in the binary format |
| * Vladimir Prus (Mentor Graphics) - add MIVariableObject.getRawFields |
| *******************************************************************************/ |
| package org.eclipse.cdt.dsf.mi.service; |
| |
| import java.math.BigInteger; |
| 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.MITuple; |
| 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 fLocked = false; |
| |
| // This id is the one used to search for this object in our hash-map |
| private final VariableObjectId fInternalId; |
| |
| /** The raw MI value for this variable object |
| * @since 4.6 |
| */ |
| private MITuple fRaw; |
| |
| // This is the name of the variable object, as given by GDB (e.g., var1 or var1.public.x) |
| private String fGdbName = null; |
| // The current format of this variable object, within GDB |
| private String fFormat = 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 fExprInfo; |
| private String fType = null; |
| private GDBType fGdbType; |
| // 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 fNumChildrenHint = 0; |
| private Boolean fEditable = null; |
| |
| // The current values of the expression for each format. (null if not known yet) |
| private Map<String, String> fValueMap = null; |
| |
| // A queue of request monitors waiting for this object to be ready |
| private LinkedList<RequestMonitor> fOperationsPending; |
| |
| // 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[] fChildren = 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> fFakeChildren = new ArrayList<>(3); |
| private boolean fHasMore = false; |
| private MIDisplayHint fDisplayHint = MIDisplayHint.NONE; |
| |
| // The parent of this variable object within GDB. Null if this object has no parent |
| private MIVariableObject fParent = 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 fRootToUpdate = null; |
| |
| protected boolean outOfScope = false; |
| |
| private boolean fFetchingChildren = 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 fHasCastToBaseClassWorkaround = 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; |
| |
| fOperationsPending = new LinkedList<>(); |
| updatesPending = new LinkedList<>(); |
| fetchChildrenPending = new LinkedList<>(); |
| |
| fInternalId = id; |
| setParent(parentObj); |
| |
| // No values are available yet |
| fValueMap = new HashMap<>(); |
| resetValues(); |
| } |
| |
| /** Return the raw fields reported via MI. |
| * |
| * This method can be used to obtain information from debugger that |
| * is too special for a particular debugger or architecture to |
| * be represented as fields or methods in this class. |
| * @since 4.7 |
| */ |
| public MITuple getRawFields() { |
| return fRaw; |
| } |
| |
| public VariableObjectId getInternalId() { |
| return fInternalId; |
| } |
| |
| public String getGdbName() { |
| return fGdbName; |
| } |
| |
| public String getCurrentFormat() { |
| return fFormat; |
| } |
| |
| public MIVariableObject getParent() { |
| return fParent; |
| } |
| |
| public MIRootVariableObject getRootToUpdate() { |
| return fRootToUpdate; |
| } |
| |
| public String getExpression() { |
| return fExprInfo.getFullExpr(); |
| } |
| |
| public String getType() { |
| return fType; |
| } |
| |
| /** |
| * @since 4.0 |
| */ |
| public ExpressionInfo getExpressionInfo() { |
| return fExprInfo; |
| } |
| |
| /** |
| * @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 fExprInfo.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 fHasMore; |
| } |
| |
| /** |
| * @since 4.0 |
| */ |
| public MIDisplayHint getDisplayHint() { |
| return fDisplayHint; |
| } |
| |
| /** |
| * @since 4.3 |
| */ |
| public void setDisplayHint(MIDisplayHint displayHint) { |
| this.fDisplayHint = displayHint; |
| } |
| |
| /** |
| * @since 4.0 |
| */ |
| public boolean hasChildren() { |
| return (getNumChildrenHint() != 0 || hasMore()); |
| } |
| |
| /** @since 3.0 */ |
| public GDBType getGDBType() { |
| return fGdbType; |
| } |
| |
| /** |
| * 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 #fHasMore} == true</code>. |
| * |
| * @since 3.0 */ |
| public int getNumChildrenHint() { |
| return fNumChildrenHint; |
| } |
| |
| /** |
| * 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 fValueMap.get(format); |
| } |
| |
| public ExpressionInfo[] getChildren() { |
| return fChildren; |
| } |
| |
| 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() { |
| // Since we can't reliably determine whether a variable is complex |
| // (see bug 399494), we err on the safe side and assume they are |
| // not. In the end we'll end up asking GDB a bit more things, but we |
| // should at least get correct results. |
| return false; |
| } |
| |
| /** |
| * @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) { |
| fGdbName = n; |
| } |
| |
| public void setCurrentFormat(String f) { |
| fFormat = f; |
| } |
| |
| /** |
| * @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) { |
| fExprInfo = info; |
| setType(typeName); |
| fNumChildrenHint = num; |
| this.fHasMore = hasMore; |
| } |
| |
| /** |
| * @since 4.1 |
| */ |
| public void setType(String newTypeName) { |
| fType = newTypeName; |
| fGdbType = getGDBTypeParser().parse(newTypeName); |
| } |
| |
| public void setValue(String format, String val) { |
| fValueMap.put(format, val); |
| } |
| |
| public void resetValues(String valueInCurrentFormat) { |
| resetValues(); |
| setValue(getCurrentFormat(), valueInCurrentFormat); |
| } |
| |
| public void resetValues() { |
| fValueMap.put(IFormattedValues.NATURAL_FORMAT, null); |
| fValueMap.put(IFormattedValues.BINARY_FORMAT, null); |
| fValueMap.put(IFormattedValues.HEX_FORMAT, null); |
| fValueMap.put(IFormattedValues.OCTAL_FORMAT, null); |
| fValueMap.put(IFormattedValues.DECIMAL_FORMAT, null); |
| } |
| |
| /** |
| * @param c |
| * The new children, or null in order to force fetching |
| * children anew. |
| */ |
| public void setChildren(ExpressionInfo[] c) { |
| fChildren = c; |
| if (fChildren != null) { |
| fNumChildrenHint = fChildren.length; |
| } |
| |
| if (fChildren != null) { |
| for (ExpressionInfo child : fChildren) { |
| assert (child != null); |
| } |
| } |
| } |
| |
| /** |
| * @param newChildren |
| * |
| * @since 4.0 |
| */ |
| public void addChildren(ExpressionInfo[] newChildren) { |
| if (fChildren == null) { |
| fChildren = new ExpressionInfo[newChildren.length]; |
| System.arraycopy(newChildren, 0, fChildren, 0, newChildren.length); |
| } else { |
| ExpressionInfo[] oldChildren = fChildren; |
| |
| fChildren = new ExpressionInfo[fChildren.length + newChildren.length]; |
| |
| System.arraycopy(oldChildren, 0, fChildren, 0, oldChildren.length); |
| System.arraycopy(newChildren, 0, fChildren, oldChildren.length, newChildren.length); |
| } |
| |
| fNumChildrenHint = fChildren.length; |
| |
| for (ExpressionInfo child : fChildren) { |
| 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 (fChildren != null) { |
| ExpressionInfo[] oldChildren = fChildren; |
| for (int i = oldChildren.length - 1; i >= newNumChildren; --i) { |
| String childFullExpression = fChildren[i].getFullExpr(); |
| |
| VariableObjectId childId = new VariableObjectId(); |
| childId.generateId(childFullExpression, getInternalId()); |
| lruVariableList.remove(childId); |
| } |
| |
| fChildren = new ExpressionInfo[newNumChildren]; |
| System.arraycopy(oldChildren, 0, fChildren, 0, newNumChildren); |
| } |
| |
| fNumChildrenHint = 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() { |
| fHasMore = false; |
| if (fChildren != null) { |
| for (ExpressionInfo child : fChildren) { |
| cleanupChild(child); |
| } |
| fChildren = null; |
| fNumChildrenHint = 0; |
| } |
| for (ExpressionInfo fakeChild : fFakeChildren) { |
| cleanupChild(fakeChild); |
| } |
| fFakeChildren.clear(); |
| } |
| |
| public void setParent(MIVariableObject p) { |
| fParent = p; |
| if (p == null) { |
| fRootToUpdate = (this instanceof MIRootVariableObject) ? (MIRootVariableObject) this : null; |
| } else { |
| fRootToUpdate = p.getRootToUpdate(); |
| } |
| } |
| |
| public void executeWhenNotUpdating(RequestMonitor rm) { |
| getRootToUpdate().executeWhenNotUpdating(rm); |
| } |
| |
| private void lock() { |
| fLocked = true; |
| } |
| |
| private void unlock() { |
| fLocked = false; |
| |
| while (!fOperationsPending.isEmpty()) { |
| fOperationsPending.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.isEmpty()) { |
| 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.isEmpty()) { |
| 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.isEmpty()) { |
| DataRequestMonitor<Boolean> pendingRm = updatesPending.poll(); |
| pendingRm.setData(false); |
| pendingRm.done(); |
| } |
| } else { |
| rm.setStatus(getStatus()); |
| rm.done(); |
| |
| while (!updatesPending.isEmpty()) { |
| 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(); |
| fEditable = 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()); |
| fExprInfo.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 = (fChildren != 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 (fChildren != null) { |
| // Remove those children that don't exist any longer. |
| if (fChildren.length > update.getNewNumChildren()) { |
| shrinkChildrenTo(update.getNewNumChildren()); |
| } |
| } else { |
| // Just update the child count. |
| fNumChildrenHint = update.getNewNumChildren(); |
| } |
| |
| // Add the new children. |
| if ((addedChildren != null) && (addedChildren.length != 0)) { |
| addChildren(addedChildren); |
| } |
| } |
| |
| assert ((fChildren == null) || (fChildren.length == fNumChildrenHint)); |
| |
| fHasMore = 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() && (fNumChildrenHint == 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.fExprInfo; |
| } |
| } 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.fExprInfo; |
| } |
| } |
| |
| 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.fExprInfo; |
| } |
| } |
| |
| ++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 (fEditable != null) { |
| rm.setData(fEditable); |
| rm.done(); |
| } else if (isComplex()) { |
| fEditable = false; |
| rm.setData(fEditable); |
| rm.done(); |
| } else { |
| fCommandControl.queueCommand( |
| fCommandFactory.createMIVarShowAttributes(getRootToUpdate().getControlDMContext(), |
| getGdbName()), |
| new DataRequestMonitor<MIVarShowAttributesInfo>(fSession.getExecutor(), rm) { |
| @Override |
| protected void handleSuccess() { |
| fEditable = getData().isEditable(); |
| |
| rm.setData(fEditable); |
| 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 we are dealing with an array, in which case, we want to get the address of it. |
| // When dealing with a complex value, we want to query the back-end to correctly |
| // address the case of a typedefed pointer (see bug 293832). |
| 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); |
| |
| } |
| }); |
| return; |
| } |
| |
| if (fLocked) { |
| fOperationsPending.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 (fFetchingChildren) { |
| // 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 { |
| |
| fFetchingChildren = true; |
| |
| fetchChildren(exprDmc, clientNumChildrenLimit, |
| new DataRequestMonitor<ChildrenInfo>(fSession.getExecutor(), rm) { |
| |
| @Override |
| protected void handleCompleted() { |
| fFetchingChildren = false; |
| |
| if (isSuccess()) { |
| rm.setData(getData()); |
| rm.done(); |
| |
| while (!fetchChildrenPending.isEmpty()) { |
| DataRequestMonitor<ChildrenInfo> pendingRm = fetchChildrenPending.poll(); |
| pendingRm.setData(getData()); |
| pendingRm.done(); |
| } |
| } else { |
| rm.setStatus(getStatus()); |
| rm.done(); |
| |
| while (!fetchChildrenPending.isEmpty()) { |
| 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, fHasMore)); |
| 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(), fHasMore)); |
| 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, fExprInfo, i); |
| } |
| |
| // First store these children, for the next time |
| setChildren(childrenOfArray); |
| fHasMore = false; |
| rm.setData(new ChildrenInfo(getChildren(), fHasMore)); |
| 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 && (fChildren != null)) ? getNumChildrenHint() : 0; |
| final int to = Math.max(newNumChildrenLimit, fExprInfo.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); |
| fHasMore = localHasMore; |
| rm.setData(new ChildrenInfo(getChildren(), fHasMore)); |
| |
| 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 = fHasCastToBaseClassWorkaround |
| || 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.fHasCastToBaseClassWorkaround = childHasCastToBaseClassWorkaround; |
| |
| if (fakeChild) { |
| if (!isSuccess()) { |
| fFakeChildren.add(var.fExprInfo); |
| } |
| addRealChildrenOfFake(var, exprDmc, realChildren, |
| arrayPosition, countingRm); |
| } else { |
| // This is a real child |
| realChildren[arrayPosition] = new ExpressionInfo[] { |
| var.fExprInfo }; |
| 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 { |
| // The child already exists so we can re-use it. |
| childVar.fHasCastToBaseClassWorkaround = 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, use it directly, however, we must |
| // make sure that its relative expression is expressed with respect |
| // to its parent, which may not be the case already, since that child |
| // might have been created directly (not through the parent). |
| // That is why we set the relative expression explicitly |
| // See bug 432888 |
| ExpressionInfo oldInfo = childVar.getExpressionInfo(); |
| realChildren[arrayPosition] = new ExpressionInfo[] { |
| new ExpressionInfo(oldInfo.getFullExpr(), child.getExp(), |
| oldInfo.isDynamic(), oldInfo.getParent(), |
| oldInfo.getIndexInParentExpression()) }; |
| countingRm.done(); |
| } |
| } |
| } |
| |
| if (childVar == null) { |
| childVar = createChild(childId, childFullExpression, indexInParent, child); |
| |
| childVar.fHasCastToBaseClassWorkaround = childHasCastToBaseClassWorkaround; |
| |
| if (fakeChild) { |
| fFakeChildren.add(childVar.fExprInfo); |
| addRealChildrenOfFake(childVar, exprDmc, realChildren, arrayPosition, |
| countingRm); |
| } else { |
| // This is a real child |
| realChildren[arrayPosition] = new ExpressionInfo[] { |
| childVar.fExprInfo }; |
| 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() || fExprInfo.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 (fHasCastToBaseClassWorkaround) { |
| // 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(), fExprInfo, 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() && (fExprInfo.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) { |
| fExprInfo.setChildCountLimit(calculateNewLimit(newLimit)); |
| return fExprInfo.getChildCountLimit(); |
| } |
| |
| private int calculateNewLimit(int clientLimit) { |
| int limit = fExprInfo.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.fExprInfo); |
| |
| // 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(fExprInfo.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() && !fExprInfo.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")) { //$NON-NLS-1$ |
| value = "0x" + value; //$NON-NLS-1$ |
| } |
| } else if (formatId.equals(IFormattedValues.OCTAL_FORMAT)) { |
| if (!value.startsWith("0")) { //$NON-NLS-1$ |
| value = "0" + value; //$NON-NLS-1$ |
| } |
| } else if (formatId.equals(IFormattedValues.BINARY_FORMAT)) { |
| // convert from binary to decimal |
| if (value.startsWith("0b")) { //$NON-NLS-1$ |
| value = value.substring(2, value.length()); |
| } |
| try { |
| value = new BigInteger(value, 2).toString(); |
| } 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 (fExprInfo.isDynamic() && (fExprInfo |
| .getChildCountLimit() != IMIExpressions.CHILD_COUNT_LIMIT_UNSPECIFIED)) { |
| // Restore the original update range. |
| fCommandControl.queueCommand( |
| fCommandFactory.createMIVarSetUpdateRange( |
| getRootToUpdate().getControlDMContext(), getGdbName(), 0, |
| fExprInfo.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)); |
| |
| assert miVar.getRawFields() != null; |
| fRaw = miVar.getRawFields(); |
| |
| 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<>(); |
| } |
| |
| 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()) { |
| 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()) { |
| // 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.isEmpty()) { |
| 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.isEmpty()) { |
| 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.isEmpty()) { |
| 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. |
| private String fExpression = null; |
| private 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 |
| private 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 = Integer.valueOf(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 static 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) |
| && 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; |
| } |
| } |
| |
| private GDBTypeParser fGDBTypeParser = null; |
| |
| 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<>(); |
| |
| /** 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<>(); |
| |
| /** |
| * 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; |
| } |
| |
| private GDBTypeParser getGDBTypeParser() { |
| if (fGDBTypeParser == null) { |
| fGDBTypeParser = createGDBTypeParser(); |
| } |
| return fGDBTypeParser; |
| } |
| |
| /** |
| * 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(); |
| // Don't use equals() below because we are concerned with other fields |
| // of ExpressionInfo that are not considered when doing equals(). |
| // Instead, only consider equal if both objects are the same one |
| if (ctxExprInfo != varExprInfo) { |
| // exprCtrx could just be created via IExpressions.createExpression, |
| // and thus the parent-child relationship is not yet set. |
| // Set that relationship while keeping the relative expression of |
| // the original context (see bug 393930) |
| miExprCtx.setExpressionInfo(new ExpressionInfo(miExprCtx.getExpression(), |
| miExprCtx.getRelativeExpression(), varExprInfo.isDynamic(), varExprInfo.getParent(), |
| varExprInfo.getIndexInParentExpression())); |
| } |
| |
| 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, |
| getData().getChildrenCount())); |
| drm.done(); |
| processCommandDone(token, drm.getData()); |
| } |
| }); |
| } else { |
| drm.setData(new ExprMetaGetVarInfo(exprCtx.getRelativeExpression(), varObj)); |
| 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; |
| } |
| |
| /** |
| * @since 4.4 |
| */ |
| protected GDBTypeParser createGDBTypeParser() { |
| return new GDBTypeParser(); |
| } |
| |
| } |