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

Caught uncaught exceptions #198

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
6b658ce
Add support for breaking on caught or uncaught exceptions
Christian-Holbrook Nov 7, 2024
0dfdf64
WIP, displaying the caught and uncaught exceptions
Christian-Holbrook Nov 7, 2024
d4d669f
Add unit tests for the new exceptions breakpoints
Christian-Holbrook Nov 8, 2024
ce4728e
Piped through to pass exceptions filters to the debug client
Christian-Holbrook Nov 12, 2024
39a12ec
Some fixes and tweaks
TwitchBronBron Nov 12, 2024
964b4e9
Fix lint issues
TwitchBronBron Nov 13, 2024
b9c7a34
Lift some responses to vars for better debugging
TwitchBronBron Nov 14, 2024
abb9238
Guard agaist setting exception breakpoints when not supported on the …
Christian-Holbrook Nov 14, 2024
edec32d
Fix incorrect init flow for exception breakpoints
TwitchBronBron Nov 15, 2024
ecd206a
Fix some lint issues
TwitchBronBron Nov 15, 2024
dc49429
Merge branch 'caught-uncaught-exceptions' of https://github.com/rokuc…
TwitchBronBron Nov 15, 2024
3e2aaa8
Add unit tests, and state to track when a debug session is persists b…
Christian-Holbrook Nov 18, 2024
70e5231
Rename any "exceptionsBreakpoints" to exceptionBreakpoints
Christian-Holbrook Nov 19, 2024
4551d78
Add more unit tests
Christian-Holbrook Nov 20, 2024
de0b916
Revert some `file:/` package dependencies
TwitchBronBron Nov 20, 2024
3fcca83
Add eslint as a recommended extension
TwitchBronBron Nov 20, 2024
035f87b
Fix lint issues
TwitchBronBron Nov 20, 2024
b19bfaa
Fix heap overflow issue
TwitchBronBron Nov 20, 2024
c747e3e
Fix most of the broken unit tests
Christian-Holbrook Nov 20, 2024
1bdc4b3
Fix unit test
Christian-Holbrook Nov 21, 2024
ee3f80b
Try increasing heap allocation
Christian-Holbrook Nov 21, 2024
ce17dda
Revert "Try increasing heap allocation"
Christian-Holbrook Nov 23, 2024
fa5aca2
Roll back all package.json changes, deleted node_modules/ and ran $ n…
Christian-Holbrook Nov 23, 2024
353f5eb
Revert package.json to master
Christian-Holbrook Nov 23, 2024
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
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- run: npm ci
- run: npm run build
- run: npm run lint
- run: npm run test
- run: npx cross-env NODE_OPTIONS="--max-old-space-size=4096" npm run test
Copy link
Member

@TwitchBronBron TwitchBronBron Nov 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't do this. The problem is most likely that you upgraded something in the package-lock (did you perhaps run npm audit fix? That does it sometimes).

There's a package in there somewhere that isn't happy with the current node version.

Can you roll back the package-lock, delete node_modules, and run npm install again?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to "manually" revert back to the original package.json and commit the newly generated package-lock.json. This caused some other errors where the dependencies were missing some exported functions.

I ended up just running git checkout master -- package.json package-lock.json and committed that. That should roll back all the changes on this branch to master for any package*.json changes.

The first run of checks failed because 1 unit tests did not pass on the ubuntu CI. I reran it and all the tests pass.

Have you had trouble with the test show in the attached screenshot?
Screenshot 2024-11-23 at 3 13 30 AM

#- run: npm run publish-coverage
npm-release:
#only run this task if a tag starting with 'v' was used to trigger this (i.e. a tagged release)
Expand Down
4 changes: 3 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{
"recommendations": []
"recommendations": [
"dbaeumer.vscode-eslint"
]
}
561 changes: 219 additions & 342 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
"@types/fs-extra": "^9.0.13",
"@types/glob": "^7.2.0",
"@types/mocha": "^9.0.0",
"@types/node": "^16.11.6",
"@types/node": "^16.18.119",
"@types/request": "^2.48.8",
"@types/semver": "^7.3.9",
"@types/sinon": "^10.0.6",
Expand Down
43 changes: 34 additions & 9 deletions src/adapters/DebugProtocolAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { CompileErrorProcessor } from '../CompileErrorProcessor';
import type { RendezvousHistory, RendezvousTracker } from '../RendezvousTracker';
import type { ChanperfData } from '../ChanperfTracker';
import { ChanperfTracker } from '../ChanperfTracker';
import type { SourceLocation } from '../managers/LocationManager';
import { ErrorCode, PROTOCOL_ERROR_CODES, UpdateType } from '../debugProtocol/Constants';
import { defer, util } from '../util';
import { logger } from '../logging';
Expand All @@ -21,6 +20,7 @@ import { VariableType } from '../debugProtocol/events/responses/VariablesRespons
import type { TelnetAdapter } from './TelnetAdapter';
import type { DeviceInfo } from 'roku-deploy';
import type { ThreadsResponse } from '../debugProtocol/events/responses/ThreadsResponse';
import type { ExceptionBreakpointFilter } from '../debugProtocol/events/requests/SetExceptionBreakpointsRequest';

/**
* A class that connects to a Roku device over telnet debugger port and provides a standardized way of interacting with it.
Expand Down Expand Up @@ -185,7 +185,7 @@ export class DebugProtocolAdapter {
return new Promise((resolve) => {
let callCount = -1;

function handler() {
function handler(data: Buffer) {
callCount++;
let myCallCount = callCount;
setTimeout(() => {
Expand All @@ -199,23 +199,38 @@ export class DebugProtocolAdapter {

client.addListener(name, handler);
//call the handler immediately so we have a timeout
handler();
handler(Buffer.from([]));
});
}

public get isAtDebuggerPrompt() {
return this.client?.isStopped ?? false;
}

private firstConnectDeferred = defer<void>();

/**
* Resolves when the first connection to the client is established
*/
public onReady() {
return this.firstConnectDeferred.promise;
}

/**
* Connect to the telnet session. This should be called before the channel is launched.
*/
public async connect() {
public async connect(): Promise<void> {
//Start processing telnet output to look for compile errors or the debugger prompt
await this.processTelnetOutput();

this.on('waiting-for-debugger', () => {
void this.createDebugProtocolClient();
this.on('waiting-for-debugger', async () => { // eslint-disable-line @typescript-eslint/no-misused-promises
await this.createDebugProtocolClient();

//if this is the first time we are connecting, resolve the promise.
//(future events fire for "reconnect" situations, we don't need to resolve again for those)
if (!this.firstConnectDeferred.isCompleted) {
this.firstConnectDeferred.resolve();
}
});
}

Expand Down Expand Up @@ -515,7 +530,7 @@ export class DebugProtocolAdapter {
let thread = await this.getThreadByThreadId(threadIndex);
let frames: StackFrame[] = [];
let stackTraceData = await this.client.getStackTrace(threadIndex);
for (let i = 0; i < stackTraceData?.data?.entries?.length ?? 0; i++) {
for (let i = 0; i < (stackTraceData?.data?.entries?.length ?? 0); i++) {
let frameData = stackTraceData.data.entries[i];
let stackFrame: StackFrame = {
frameId: this.nextFrameId++,
Expand Down Expand Up @@ -736,7 +751,7 @@ export class DebugProtocolAdapter {
return [];
}

for (let i = 0; i < threadsResponse.data?.threads?.length ?? 0; i++) {
for (let i = 0; i < (threadsResponse.data?.threads?.length ?? 0); i++) {
let threadInfo = threadsResponse.data.threads[i];
let thread = <Thread>{
// NOTE: On THREAD_ATTACHED events the threads request is marking the wrong thread as primary.
Expand Down Expand Up @@ -829,6 +844,16 @@ export class DebugProtocolAdapter {
this.chanperfTracker.clearHistory();
}

public async setExceptionBreakpoints(filters: ExceptionBreakpointFilter[]) {
if (this.client?.supportsExceptionBreakpointFilters) {
//tell the client to set the exception breakpoints
const response = await this.client?.setExceptionBreakpoints(filters);
console.log(response);
return response;
}
return undefined;
}

private syncBreakpointsPromise = Promise.resolve();
public async syncBreakpoints() {
this.logger.log('syncBreakpoints()');
Expand Down Expand Up @@ -902,7 +927,7 @@ export class DebugProtocolAdapter {

//if the response was successful, and we have the correct number of breakpoints in the response
if (response.data.errorCode === ErrorCode.OK && response?.data?.breakpoints?.length === breakpoints.length) {
for (let i = 0; i < response?.data?.breakpoints?.length ?? 0; i++) {
for (let i = 0; i < (response?.data?.breakpoints?.length ?? 0); i++) {
const deviceBreakpoint = response.data.breakpoints[i];

if (typeof deviceBreakpoint?.id === 'number') {
Expand Down
13 changes: 13 additions & 0 deletions src/adapters/TelnetAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type { AdapterOptions, RokuAdapterEvaluateResponse } from '../interfaces'
import { HighLevelType } from '../interfaces';
import { TelnetRequestPipeline } from './TelnetRequestPipeline';
import type { DebugProtocolAdapter } from './DebugProtocolAdapter';
import type { ExceptionBreakpointFilter } from '../debugProtocol/events/requests/SetExceptionBreakpointsRequest';

/**
* A class that connects to a Roku device over telnet debugger port and provides a standardized way of interacting with it.
Expand Down Expand Up @@ -71,6 +72,7 @@ export class TelnetAdapter {
public supportsExecute = true;

public once(eventName: 'app-ready'): Promise<void>;
public once(eventName: 'connected'): Promise<boolean>;
public once(eventName: string) {
return new Promise((resolve) => {
const disconnect = this.on(eventName as Parameters<DebugProtocolAdapter['on']>[0], (...args) => {
Expand Down Expand Up @@ -226,6 +228,12 @@ export class TelnetAdapter {
}
}

private firstConnectDeferred = defer<void>();

public onReady() {
return this.firstConnectDeferred.promise;
}

/**
* Connect to the telnet session. This should be called before the channel is launched.
*/
Expand Down Expand Up @@ -356,6 +364,7 @@ export class TelnetAdapter {
} catch (e) {
deferred.reject(e);
}
this.firstConnectDeferred.resolve();
return deferred.promise;
}

Expand Down Expand Up @@ -1076,6 +1085,10 @@ export class TelnetAdapter {
this.chanperfTracker.clearHistory();
}

public async setExceptionBreakpoints(filters: ExceptionBreakpointFilter[]) {
//we can't send dynamic breakpoints to the server...so just do nothing
}

public async syncBreakpoints() {
//we can't send dynamic breakpoints to the server...so just do nothing
}
Expand Down
45 changes: 37 additions & 8 deletions src/debugProtocol/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ export enum Command {
* @since protocol 3.1.0 (Roku OS 11.5)
*/
AddConditionalBreakpoints = 'AddConditionalBreakpoints',
/**
* Adds a exception breakpoint.
*
* @since protocol TBD
*/
SetExceptionBreakpoints = 'SetExceptionBreakpoints',
/**
*
*/
Expand All @@ -85,6 +91,7 @@ export enum CommandCode {
RemoveBreakpoints = 9,
Execute = 10,
AddConditionalBreakpoints = 11,
SetExceptionBreakpoints = 12,
ExitChannel = 122
}

Expand Down Expand Up @@ -113,7 +120,9 @@ export enum ErrorCode {
UNDEFINED_COMMAND = 2,
CANT_CONTINUE = 3,
NOT_STOPPED = 4,
INVALID_ARGS = 5
INVALID_ARGS = 5,
THREAD_DETACHED = 6,
EXECUTION_TIMEOUT = 7
}

export enum ErrorFlags {
Expand Down Expand Up @@ -198,7 +207,18 @@ export enum UpdateType {
* Breakpoints were successfully verified
* @since protocol 3.2
*/
BreakpointVerified = 'BreakpointVerified'
BreakpointVerified = 'BreakpointVerified',
/**
* An unrecoverable error has occurred on the protocol stream. As a result, the debug target is terminated.
* @since Roku OS 12.0
*/
ProtocolError = 'ProtocolError',
/**
* ExceptionBreakpointError updates will be sent for compile and runtime errors for exception breakpoint conditions.
* These updates will be sent every time the condition fails to compile/run while throwing an exception.
* @since protocol 3.3 (OS 14.1.4 or greater)
*/
ExceptionBreakpointError = 'ExceptionBreakpointError'
}
/**
* The integer values for `UPDATE_TYPE`. Only used for serializing/deserializing over the debug protocol. Use `UpdateType` in your code.
Expand All @@ -210,10 +230,19 @@ export enum UpdateTypeCode {
ThreadAttached = 3,
BreakpointError = 4,
CompileError = 5,
// /**
// * Breakpoints were successfully verified
// * @since protocol 3.2
// */
BreakpointVerified = 6
/**
* Breakpoints were successfully verified
* @since protocol 3.2
*/
BreakpointVerified = 6,
/**
* An unrecoverable error has occurred on the protocol stream. As a result, the debug target is terminated.
*/
ProtocolError = 7,
/**
* ExceptionBreakpointError updates will be sent for compile and runtime errors for exception breakpoint conditions.
* These updates will be sent every time the condition fails to compile/run while throwing an exception.
* @since protocol 3.2
*/
ExceptionBreakpointError = 8
}

23 changes: 23 additions & 0 deletions src/debugProtocol/client/DebugProtocolClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,29 @@ describe('DebugProtocolClient', () => {
).to.be.true;
});

it('knows when to enable exception breakpoint filters', () => {
//only supported on version 3.3.0 and above
client.protocolVersion = '1.0.0';
expect(
client['supportsExceptionBreakpointFilters']
).to.be.false;

client.protocolVersion = '3.0.0';
expect(
client['supportsExceptionBreakpointFilters']
).to.be.false;

client.protocolVersion = '3.3.0';
expect(
client['supportsExceptionBreakpointFilters']
).to.be.true;

client.protocolVersion = '4.0.0';
expect(
client['supportsExceptionBreakpointFilters']
).to.be.true;
});

it('handles v3 handshake', async () => {
//these are false by default
expect(client.watchPacketLength).to.be.equal(false);
Expand Down
Loading
Loading