Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Support object IDs #134

Open
isadorasophia opened this issue Aug 4, 2020 · 3 comments
Open

Proposal: Support object IDs #134

isadorasophia opened this issue Aug 4, 2020 · 3 comments
Labels
feature-request Request for new features or functionality

Comments

@isadorasophia
Copy link

isadorasophia commented Aug 4, 2020

Object IDs

When debugging managed code, users are able to track a certain variable outside its scope by creating an Object ID for it. An adapter can take this request and create an ID associated with the variable. It supports both variables created from an expression evaluation and an existing variable in the debuggee.

For expression evaluation results, we also extended EvaluateResponse with a reference identifier which can be used afterwards when referencing to the variable produced from that result. This is used when creating an ID for the evaluation result, but it can also be used with other features going forward.

See the documentation for Track an out-of-scope object for more details.

interface EvaluateResponse {
    // ...

    /**
     * Reference integer for the evaluated expression result. This can be used when referencing the result on further requests.
     * The value should be less than or equal to 2147483647 (2^31 - 1).
     */
    evaluateResponseReference?: number;

    // ...
}

interface Capabilities {
    // ...

    /** The debug adapter supports creating and destroying object IDs on variables. */
    supportsObjectId?: boolean;
}

/** 
 * Create an object ID for the target variable.
 * After issuing a 'createObjectId' request, the client should requery the value of all displayed variables, as debug adapters 
 * would likely want to include the created id for the object in the variable's value.
 * Clients should only call this request if the capability 'supportsObjectId' is true.
 */
interface CreateObjectIdRequest extends Request {
    // command: 'createObjectId'
    arguments: CreateObjectIdArguments;
}

/** Arguments for 'createObjectId' request */
interface CreateObjectIdArguments{
  /**
   * Optional value, should be used when referencing to a local variable.
   * The reference of the variable container for the variable used for creating the object ID.
   */
  variablesReference?: number;

  /**
   * Optional value, should be used when referencing to a local variable.
   * The name of the variable in the container to create the object ID.
   * Valid variables have a VariablePresentationHint attribute of 'canHaveObjectId', which indicates that the object can 
   * have an object ID created for it.
   * Valid variables do not have a VariablePresentationHint attribute of 'hasObjectId', which indicates that the object already has
   * an object ID associated with it.
   */
  name?: string;

  /**
   * Optional value, should be used when referencing to an evaluation result.
   * The reference to the evaluation result identifier to create the object ID.
   * Valid variables have a VariablePresentationHint attribute of 'canHaveObjectId', which indicates that the object can 
   * have an object ID created for it.
   * Valid variables do not have a VariablePresentationHint attribute of 'hasObjectId', which indicates that the object already has
   * an object ID associated with it.
   */
  evaluateResponseReference?: number;
}

/** Response for 'createObjectId' request */
interface CreateObjectIdResponse extends Response {
}

/** 
 * Destroy the object id associated with the target variable.
 * After issuing a 'destroyObjectId' request, the client should requery the value of all displayed variables, as debug adapters 
 * would likely want to remove the created id for the object in the variable's value.
 * Clients should only call this request if the capability 'supportsObjectId' is true.
 */
interface DestroyObjectIdRequest extends Request {
    // command: 'destroyObjectId'
    arguments: DestroyObjectIdArguments;
}

/** Arguments for 'destroyObjectId' request */
interface DestroyObjectIdArguments {
  /**
   * Optional value, should be used when referencing to a local variable.
   * The reference of the variable container for the variable used for destroying its object ID.
   */
  variablesReference?: number;

  /**
   * Optional value, should be used when referencing to a local variable.
   * The name of the variable in the container to destroy its object ID.
   * Valid variables have a VariablePresentationHint attribute of 'hasObjectId', which indicates that the object has
   * an object ID associated with it.
   */
  name?: string;

  /**
   * Optional value, should be used when referencing to an evaluation result.
   * The reference to the evaluation result identifier to destroy its object ID.
   * Valid variables have a VariablePresentationHint attribute of 'hasObjectId', which indicates that the object has
   * an object ID associated with it.
   */
  evaluateResponseReference?: number;
}

/** Response for 'destroyObjectId' request */
interface DestroyObjectIdResponse extends Response {
}

CC: @andrewcrawley, @weinand

@weinand weinand self-assigned this Aug 4, 2020
@weinand weinand added the feature-request Request for new features or functionality label Aug 4, 2020
@weinand
Copy link
Contributor

weinand commented Aug 30, 2020

@isadorasophia Thanks a lot for the new DAP feature proposal.

Here are some initial comments and questions:

  • "Tracking out-of-scope variables and expressions" makes some sense to me, but calling this functionality "creating an Object ID" does not sound intuitive because "Object ID" sounds more like an implementation detail used in the underlying runtime. Can we find a more abstract name? E.g. something that reflects the "Tracking out-of-scope variables and expressions" description.
  • The proposal does explain how a "Object ID" can be created from a variable or expression and later destroyed, but I could not find anything that explains where in the DAP the created "Object ID" plays a role. I would have expected that "Object IDs" are returned via the "variable" request as part of a special "Object ID" Scope. Or is an "Object ID" just a flag on a "watch expression" to tie it to its scope? If the "watch expression" is tied to its original scope, evaluation will always result in a value even if the current execution location is outside of the scope. And if the "watch expression" is not tied to the scope, evaluation will fail if the "watch expression" is not valid within the current execution location.
  • The current DAP spec already uses "canHaveObjectId" and "hasObjectId" in a comment but does not explain them in any way. The two values and their description should be included in this proposal (and they might need a better name).
  • In order to reference an expression the proposal introduces a evaluateResponseReference but for variables a pair consisting of the container's variablesReference and a name is used. This nonuniform way of referring to similar things is a bit inelegant. Would it make sense to return a trackingReference for both expression and variables?

/cc @gregg-miskelly

@isadorasophia
Copy link
Author

@weinand Thank you for the comments and suggestions! Here are my thoughts:

I could not find anything that explains where in the DAP the created "Object ID" plays a role.

The object ID is merely a way of the debugger to track the value of a variable internally. We will pin that value so it is not garbage collected and persist outside its scope. For example, in the given program:

class Foo
{
    private readonly int _id;

    public Foo(int id)
    {
        _id = id;
    }

    static void Main()
    {
        Foo foo = new Foo(0);

        Debugger.Break();
        bar();
    }

    static void bar()
    {
        Debugger.Break();
    }
}

If we start debugging and stop at the first breakpoint, we can create an object ID for foo. We suddenly have this new object represented by $1, which foo points to the same reference:

Name Value
$1 {ConsoleApp24.Foo} {$1}
foo {ConsoleApp24.Foo} {$1}

Afterwards, when stopping at bar(), $1 will persist at the locals window regardless of being out of scope where it was originally created.

This can be tied to any object that already exists in the debuggee (e.g. a local) or evaluated through a watch expression, which is the reason we also need a way to track objects created through expression evaluation: evaluateResponseReference.

Can we find a more abstract name? E.g. something that reflects the "Tracking out-of-scope variables and expressions" description.

Given what object IDs stand for, I am not sure whether it might add more confusion coming up with an alternate name. We are doing exactly what it describes: we are creating an object ID for that particular object, which will be persisted outside its scope as a reference to that object.

The current DAP spec already uses "canHaveObjectId" and "hasObjectId" in a comment but does not explain them in any way.

These are both already existing VariablePresentationHint properties, I can further refer them in this proposal.

Would it make sense to return a trackingReference for both expression and variables?

The trackingResponse value exists so we can further refer for evaluation expression results afterwards. This value exists because we internally track this value in the debugger through its reference. Local variables already exist within their local scope, so it would be redudant to create an extra reference to each child of all the locals variables. Instead, we can simply track them through their variable reference and child name.

variablesReference and evaluateResponseReference will point to quite different reference pools, so I am not sure whether we want to merge them into one value within the request either.

Please let me know if you have any thoughts on the comments above!

@gregg-miskelly
Copy link
Member

gregg-miskelly commented Sep 15, 2020

I think one thing that might not be obvious with this proposal is that the debug adapter directly communicates the created 'id' for the object to the user by modifying the displayed value. In Isa's example, the value of 'foo' goes from {ConsoleApp24.Foo} to {ConsoleApp24.Foo} {$1} where the $<some-number-here> is the syntax for object ids chosen by the C# debugger.

To try and clarify this, I would suggest adding something like the following to the documentation for CreateObjectIdRequest:

After issuing a createObjectId the client should requery the value of all displayed variables, as debug adapters would likely want to include the created id for the object in the variable's value.

We probably want something similar for DestroyObjectIdRequest as well.

In terms of how a debug adapter would like to present object's which have an associated id to the user: under this proposal, since variable windows should be refreshed after creating the object id, that is up to the debug adapter. A debug adapter could certainly do it by adding a new scope to list all of the objects that have an object id. The C# debugger does it my adding a new fake local (in all functions), and by making $1 (or whatever) available as an expression that can be used in all the evaluation requests (regardless of scope) (so you can evaluate $1.MyProperty for example).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature-request Request for new features or functionality
Projects
None yet
Development

No branches or pull requests

4 participants
@weinand @isadorasophia @gregg-miskelly and others