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 breakpoints binding in multiple locations #114

Open
auott opened this issue May 12, 2020 · 0 comments
Open

Proposal: Support breakpoints binding in multiple locations #114

auott opened this issue May 12, 2020 · 0 comments
Labels
feature-request Request for new features or functionality

Comments

@auott
Copy link
Member

auott commented May 12, 2020

Depending on the language and the type of breakpoint, there are scenarios where creating a single breakpoint from a client can end up with multiple breakpoints being created by a debugger. For example, adding a function breakpoint on a function using a function name that exists in multiple places or on multiple objects (ToString) or on a source line that is compiled into multiple modules or multiple times (a templated c++ function).

Allowing a debug adapter to report multiple breakpoint binds is relatively simple, as it can be done by extending the current Breakpoint object. However, allowing a client to manage the new breakpoint binds is difficult with the current requests, as there is not a clear way to indicate what individual breakpoints should have which state. Using the function breakpoint example, if a client adds a breakpoint on "ToString" and this results in two breakpoints "ClassA.ToString" and "ClassB.ToString", then there is no way for a client to remove or change a condition on one of those breakpoints but not the other. For this reason, this proposal also adds requests that operate on individual breakpoints (using the pre-existing Id field) to do additions, updates, and removals.

Client and DA capabilities

When a debug adapter supports multiple breakpoint binds, some of the semantics around Breakpoint objects changes (see the updates to Breakpoint). That means it's necessary for a client to advertise that it supports handling those semantics via the initialize request. The adapter also needs to be able to advertise the individual breakpoint operations capability, which is a requirement for supporting additional breakpoint binds. These are intended to be used in conjunction, though a client which does not support multiple breakpoint binds could still use the individual breakpoint operations if available via the adapter.

export interface Capabilities {
    // ...

    /** 
     * The debug adapter supports operations (add, update, remove) on individual breakpoints.
     * This is required if the adapter returns multiple breakpoint binds.
     */
    supportsIndividualBreakpointOperations?: boolean;
}

export interface InitializeRequestArguments {
    // ...

    /** 
     * The client supports receiving Breakpoints that use the boundBreakpoints property. If an adapter
     * can provide the BoundBreakpoints but the client does not support it, it should return the first
     * bound breakpoint as the Breakpoint for the response.
     */
     supportsAdditionalBreakpointBinds?: boolean;
}

Updates to existing breakpoint request objects

Currently only source breakpoints have a logMessage property, however there does not seem to be a reason why function or data breakpoints couldn't also support a log message. Data breakpoints are more constrained in what would be a valid message since the context in which they are hit may not be consistent, but they could still support some amount of logging.

Adding logMessage to all of the breakpoint objects allows for a more consistent API for individual operations. This same addition would apply to Instruction breakpoints if they are also taken in to the protocol.

export interface FunctionBreakpoint {
    // ...

    /**
     * If this attribute exists and is non-empty, the backend must not 'break' (stop)
     * but log the message instead. Expressions within {} are interpolated.
     * The attribute is only honored by a debug adapter if the capability 'supportsLogPoints' is true.
     */
    logMessage?: string;
}

export interface DataBreakpoint {
    // ...

    /**
     * If this attribute exists and is non-empty, the backend must not 'break' (stop)
     * but log the message instead. Expressions within {} are interpolated.
     * The attribute is only honored by a debug adapter if the capability 'supportsLogPoints' is true.
     */
    logMessage?: string;
}

Returning multiple breakpoint binds

If the client and adapter both support/handle additional breakpoint binds then the usage of the Breakpoint in responses changes.

First, the Breakpoint has an additional property "boundBreakpoints" which represents the locations where breakpoints were created. For the ToString example from the beginning of the proposal there would be two elements in the BoundBreakpoints array: one element for ClassA.ToString and one element for ClassB.ToString.

Second, the Breakpoint that is returned in a response always represents the requested breakpoint - not a location that the breakpoint bound. It should always have verified set to true and the id should represent the request.

For a set*BreakpointsResponse or AddBreakpointResponse the boundBreakpoints is the set of breakpoints that were initially able to bind and can be empty. The BreakpointEvent can be used to update the list of boundBreakpoints for a given request - additions, modifications, and removals.

export interface Breakpoint {
    // ...

    /**
     * Optional list used to support a breakpoint that can bind to more than one location. 
     * If client supports additional breakpoint binds and the adapter supports individual breakpoint operations then the parent breakpoint represents the request to set the breakpoint and the boundBreakpoints represent the set of breakpoints that were bound for this parent.
     * In this case, 'verified' should be true, 'id' should be the id to represent the request, and all other fields are ignored. 
     * This should only be sent if the client set supportsAdditionalBreakpointBinds.
     */
    boundBreakpoints?: Breakpoint[];
}

Individual Breakpoint Operations

The individual breakpoint operations are used to create, modify, and remove specific breakpoints, rather than in the groups handled by the set*BreakpointsRequest. If individual breakpoint operations are used, the set*BreakpointsRequests should only be used to set up the individual breakpoints as they would invalidate things done by the individual operations.

The update request also supports an "enabled" property in its arguments to support a recoverable remove of the breakpoint. Without it, the only way to bring back an individual breakpoint that was removed would be to recreate all the bound breakpoints by a new AddBreakpointRequest and attempting to reconcile the states of the previously bound breakpoints with the new bound breakpoints.

/** 
 * Creates a new breakpoint.
 * Clients should only call this request if the capability 'supportsIndividualBreakpointOperations' is true.
 */
export interface AddBreakpointRequest extends Request {
    // command: 'addBreakpoint'
    arguments: AddBreakpointArguments;
}

/**  
* Arguments for 'addBreakpoint' request
* Only one of sourceBreakpoint, dataBreakpoint, or functionBreakpoint should be set.
*/
export interface AddBreakpointArguments {
    /** If provided, the request is to create a data breakpoint and Source must also be provided. */
    sourceBreakpoint?: SourceBreakpoint;
    /** The source location of the breakpoints; either 'source.path' or 'source.reference' must be specified. */
    source?: Source;

    /** If provided, the request is to create a data breakpoint*/
    dataBreakpoint?: DataBreakpoint;

    /** If provided, the request is to create a function breakpoint*/
    functionBreakpoint?: DataBreakpoint;

    /**
     * An optional expression for conditional breakpoints.
     * It is only honored by a debug adapter if the capability 'supportsConditionalBreakpoints' is true.
     */
    condition?: string;

    /**
     * An optional expression that controls how many hits of the breakpoint are ignored.
     * The backend is expected to interpret the expression as needed.
     * The attribute is only honored by a debug adapter if the capability 
     * 'supportsHitConditionalBreakpoints' is true.
     */
    hitCondition?: string;

    /**
     * If this attribute exists and is non-empty, the backend must not 'break' (stop)
     * but log the message instead. Expressions within {} are interpolated.
     * The attribute is only honored by a debug adapter if the capability 'supportsLogPoints' is true.
     */
    logMessage?: string;
}

/** Response to 'addBreakpoint' request */
export interface AddBreakpointResponse extends Response {
    body: {
        /**
         * Information about the breakpoint.
         */
        breakpoint: Breakpoint;
    }
}

/** 
 * Updates an existing breakpoint.
 * This can be used to update the enabled/disabled state, condition, hitCondition, or logMessage of an existing Breakpoint.
 * If additional breakpoint binds are supported, the id can correspond to either the breakpoint request id, or the id of a
 * a breakpoint from the boundBreakpoints. If it corresponds to a request, the update applies to all bound breakpoints. 
 */
export interface UpdateBreakpointRequest extends Request {
    // command: 'updateBreakpoint'
    arguments: UpdateBreakpointArguments;
}

/** Arguments for 'updateBreakpoint' request */
export interface UpdateBreakpointArguments {
    /** The id of the breakpoint to update. */
    id: number;

    /** Whether the breakpoint should be enabled or disabled. */
    enabled: boolean;

    /**
     * An optional expression for conditional breakpoints.
     * It is only honored by a debug adapter if the capability 'supportsConditionalBreakpoints' is true.
     */
    condition?: string;

    /**
     * An optional expression that controls how many hits of the breakpoint are ignored.
     * The backend is expected to interpret the expression as needed.
     * The attribute is only honored by a debug adapter if the capability 
     * 'supportsHitConditionalBreakpoints' is true.
     */
    hitCondition?: string;

    /**
     * If this attribute exists and is non-empty, the backend must not 'break' (stop)
     * but log the message instead. Expressions within {} are interpolated.
     * The attribute is only honored by a debug adapter if the capability 'supportsLogPoints' is true.
     */
    logMessage?: string;
}

/** Response to 'updateBreakpoint' request. This is just an acknowledgement, so no body field is required. */
export interface UpdateBreakpointResponse extends Response {
}

/** 
 * Removes an existing breakpoint based on the id.
 * If the is a breakpoint request id then all bound breakpoints are also removed.
 */
export interface RemoveBreakpointRequest extends Request {
    // command: 'removeBreakpoint'
    arguments: RemoveBreakpointArguments
}

/** Arguments for 'removeBreakpoint' request */
export interface RemoveBreakpointArguments {
    /** The id of the breakpoint to remove. */
    int: number;
}

/** Response to 'removeBreakpoint' request. This is just an acknowledgement, so no body field is required. */
export interface RemoveBreakpointResponse extends Response {
}

CC: @andrewcrawley, @weinand

@weinand weinand self-assigned this May 13, 2020
@weinand weinand added the feature-request Request for new features or functionality label May 13, 2020
@weinand weinand added this to the On Deck milestone May 13, 2020
@weinand weinand removed this from the On Deck milestone Nov 24, 2022
@weinand weinand removed their assignment Nov 24, 2022
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

2 participants