Skip to content

Add watchpoint support #710

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

Merged
merged 1 commit into from
Jul 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions src/debug/debugSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession {
supportsSetVariable: true,
supportsConditionalBreakpoints: false, // TODO
supportsStepBack: false,
supportsDataBreakpoints: true,
};

try {
Expand Down Expand Up @@ -315,6 +316,81 @@ export class ObjectScriptDebugSession extends LoggingDebugSession {
this.sendResponse(response);
}

protected dataBreakpointInfoRequest(
response: DebugProtocol.DataBreakpointInfoResponse,
args: DebugProtocol.DataBreakpointInfoArguments
): void {
if (args.variablesReference !== undefined && (args.variablesReference === 1 || args.variablesReference === 2)) {
// This is a private or public local variable
response.body = {
dataId: args.name,
description: args.name,
};
} else {
// This is an object property or array element, or args.variablesReference is undefined
response.body = {
dataId: null,
description: "Can only set a watchpoint on a local variable",
};
}

this.sendResponse(response);
}

protected async setDataBreakpointsRequest(
response: DebugProtocol.SetDataBreakpointsResponse,
args: DebugProtocol.SetDataBreakpointsArguments
): Promise<void> {
try {
await this._debugTargetSet.wait(1000);

const currentList = await this._connection.sendBreakpointListCommand();
currentList.breakpoints
.filter((breakpoint) => {
if (breakpoint instanceof xdebug.Watchpoint) {
return true;
}
return false;
})
.map((breakpoint) => {
this._connection.sendBreakpointRemoveCommand(breakpoint);
});

let xdebugWatchpoints: xdebug.Watchpoint[] = [];
xdebugWatchpoints = await Promise.all(
args.breakpoints.map(async (breakpoint) => {
return new xdebug.Watchpoint(breakpoint.dataId);
})
);

const vscodeWatchpoints: DebugProtocol.Breakpoint[] = [];
await Promise.all(
xdebugWatchpoints.map(async (breakpoint, index) => {
try {
await this._connection.sendBreakpointSetCommand(breakpoint);
vscodeWatchpoints[index] = { verified: true, instructionReference: breakpoint.variable };
} catch (error) {
vscodeWatchpoints[index] = {
verified: false,
instructionReference: breakpoint.variable,
message: error.message,
};
}
})
);

// send back the watchpoints
response.body = {
breakpoints: vscodeWatchpoints,
};
} catch (error) {
this.sendErrorResponse(response, error);
return;
}

this.sendResponse(response);
}

protected threadsRequest(response: DebugProtocol.ThreadsResponse): void {
// runtime supports now threads so just return a default thread.
response.body = {
Expand Down
36 changes: 35 additions & 1 deletion src/debug/xdebugConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ export abstract class Breakpoint {
case "conditional":
// eslint-disable-next-line @typescript-eslint/no-use-before-define
return new ConditionalBreakpoint(breakpointNode, connection);
case "watch":
// eslint-disable-next-line @typescript-eslint/no-use-before-define
return new Watchpoint(breakpointNode, connection);
default:
throw new Error(`Invalid type ${breakpointNode.getAttribute("type")}`);
}
Expand Down Expand Up @@ -260,6 +263,29 @@ export class ConditionalBreakpoint extends Breakpoint {
}
}

/** class for watch breakpoints. Returned from a breakpoint_list or passed to sendBreakpointSetCommand */
export class Watchpoint extends Breakpoint {
/** The variable to watch */
public variable: string;
/** Constructs a breakpoint object from an XML node from a XDebug response */
public constructor(breakpointNode: Element, connection: Connection);
/** Contructs a breakpoint object for passing to sendSetBreakpointCommand */
public constructor(variable: string);
public constructor(...rest: any[]) {
if (typeof rest[0] === "object") {
// from XML
const breakpointNode: Element = rest[0];
const connection: Connection = rest[1];
super(breakpointNode, connection);
this.variable = breakpointNode.getAttribute("expression"); // Base64 encoded?
} else {
// from arguments
super("watch");
this.variable = rest[0];
}
}
}

/** Response to a breakpoint_set command */
export class BreakpointSetResponse extends Response {
public breakpointId: number;
Expand Down Expand Up @@ -738,7 +764,7 @@ export class Connection extends DbgpConnection {

/**
* Sends a breakpoint_set command that sets a breakpoint.
* @param {Breakpoint} breakpoint - an instance of LineBreakpoint, ConditionalBreakpoint or ExceptionBreakpoint
* @param {Breakpoint} breakpoint - an instance of LineBreakpoint, ConditionalBreakpoint, ExceptionBreakpoint or Watchpoint
* @returns Promise.<BreakpointSetResponse>
*/
public async sendBreakpointSetCommand(breakpoint: Breakpoint): Promise<BreakpointSetResponse> {
Expand All @@ -760,6 +786,14 @@ export class Connection extends DbgpConnection {
args += ` -n ${breakpoint.line}`;
}
data = breakpoint.expression;
} else if (breakpoint instanceof Watchpoint) {
data = breakpoint.variable;

// These placeholders are needed due to a bug on the server
// They have no effect on the watchpoint functionality
args += ` -f PLACEHOLDER`;
args += ` -m PLACEHOLDER`;
args += ` -n PLACEHOLDER`;
}
return new BreakpointSetResponse(await this._enqueueCommand("breakpoint_set", args, data), this);
}
Expand Down