Skip to content

Commit 16804b5

Browse files
committed
New implementation of Exception Info with support for virtual $__EXCEPTION property
1 parent 6ce118f commit 16804b5

File tree

3 files changed

+106
-12
lines changed

3 files changed

+106
-12
lines changed

src/phpDebug.ts

Lines changed: 87 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { getConfiguredEnvironment } from './envfile'
2020
import { XdebugCloudConnection } from './cloud'
2121
import { shouldIgnoreException } from './ignore'
2222
import { varExportProperty } from './varExport'
23+
import { supportedEngine } from './xdebugUtils'
2324

2425
if (process.env['VSCODE_NLS_CONFIG']) {
2526
try {
@@ -249,6 +250,7 @@ class PhpDebugSession extends vscode.DebugSession {
249250
supportTerminateDebuggee: true,
250251
supportsDelayedStackTraceLoading: false,
251252
supportsClipboardContext: true,
253+
supportsExceptionInfoRequest: true,
252254
}
253255
this.sendResponse(response)
254256
}
@@ -522,29 +524,24 @@ class PhpDebugSession extends vscode.DebugSession {
522524

523525
// support for breakpoints
524526
let feat: xdebug.FeatureGetResponse
525-
const supportedEngine =
526-
initPacket.engineName === 'Xdebug' &&
527-
semver.valid(initPacket.engineVersion, { loose: true }) &&
528-
semver.gte(initPacket.engineVersion, '3.0.0', { loose: true })
529-
const supportedEngine32 =
530-
initPacket.engineName === 'Xdebug' &&
531-
semver.valid(initPacket.engineVersion, { loose: true }) &&
532-
semver.gte(initPacket.engineVersion, '3.2.0', { loose: true })
527+
const supportedEngine30 = supportedEngine(initPacket, '3.0.0')
528+
const supportedEngine32 = supportedEngine(initPacket, '3.2.0')
529+
const supportedEngine35 = supportedEngine(initPacket, '3.5.0')
533530
if (
534-
supportedEngine ||
531+
supportedEngine30 ||
535532
((feat = await connection.sendFeatureGetCommand('resolved_breakpoints')) && feat.supported === '1')
536533
) {
537534
await connection.sendFeatureSetCommand('resolved_breakpoints', '1')
538535
}
539536
if (
540-
supportedEngine ||
537+
supportedEngine30 ||
541538
((feat = await connection.sendFeatureGetCommand('notify_ok')) && feat.supported === '1')
542539
) {
543540
await connection.sendFeatureSetCommand('notify_ok', '1')
544541
connection.on('notify_user', (notify: xdebug.UserNotify) => this.handleUserNotify(notify, connection))
545542
}
546543
if (
547-
supportedEngine ||
544+
supportedEngine30 ||
548545
((feat = await connection.sendFeatureGetCommand('extended_properties')) && feat.supported === '1')
549546
) {
550547
await connection.sendFeatureSetCommand('extended_properties', '1')
@@ -556,6 +553,12 @@ class PhpDebugSession extends vscode.DebugSession {
556553
) {
557554
await connection.sendFeatureSetCommand('breakpoint_include_return_value', '1')
558555
}
556+
if (
557+
supportedEngine35 ||
558+
((feat = await connection.sendFeatureGetCommand('virtual_exception_value')) && feat.supported === '1')
559+
) {
560+
await connection.sendFeatureSetCommand('virtual_exception_value', '1')
561+
}
559562

560563
// override features from launch.json
561564
try {
@@ -1529,6 +1532,79 @@ class PhpDebugSession extends vscode.DebugSession {
15291532
this.sendResponse(response)
15301533
}
15311534
}
1535+
1536+
protected async exceptionInfoRequest(
1537+
response: VSCodeDebugProtocol.ExceptionInfoResponse,
1538+
args: VSCodeDebugProtocol.ExceptionInfoArguments
1539+
): Promise<void> {
1540+
const connection = this._connections.get(args.threadId)
1541+
if (!connection) {
1542+
throw new Error('Unknown thread ID')
1543+
}
1544+
1545+
const status = this._statuses.get(connection)!
1546+
1547+
let ex: VSCodeDebugProtocol.ExceptionDetails | undefined
1548+
1549+
if (connection.featureSet('virtual_exception_value')) {
1550+
let old_max_depth: number = 3
1551+
try {
1552+
const { stack } = await connection.sendStackGetCommand() // CACHE?
1553+
1554+
if (stack.length > 0) {
1555+
const ctx = await stack[0].getContexts() // CACHE
1556+
old_max_depth = <number>connection.featureSet('max_depth') ?? 1
1557+
if (old_max_depth < 3) {
1558+
await connection.sendFeatureSetCommand('max_depth', 3)
1559+
}
1560+
1561+
const res = await connection.sendPropertyGetNameCommand('$__EXCEPTION', ctx[0])
1562+
1563+
const s = res.property.children.find(
1564+
p => p.name === 'trace' || p.name === '*Exception*trace' || p.name === '*Error*trace'
1565+
)
1566+
1567+
const at = `Created at ${res.property.children.find(p => p.name == 'file')?.value ?? '**UNKNOWN**'}:${
1568+
res.property.children.find(p => p.name == 'line')?.value ?? '?'
1569+
}\n`
1570+
const st = s?.children
1571+
.map(
1572+
(t, i) =>
1573+
`at ${
1574+
t.children.find(p => p.name == 'class')?.value ?? ''}${
1575+
t.children.find(p => p.name == 'type')?.value ?? ''
1576+
}${t.children.find(p => p.name == 'function')?.value ?? '?'}() (${
1577+
t.children.find(p => p.name == 'file')?.value ?? '**UNKNOWN**'}:${
1578+
t.children.find(p => p.name == 'line')?.value ?? '?'
1579+
})`
1580+
)
1581+
.join('\n')
1582+
ex = {
1583+
message: res.property.children.find(p => p.name === 'message')?.value ?? undefined,
1584+
typeName: res.property.class,
1585+
fullTypeName: res.property.class,
1586+
evaluateName: '$__EXCEPTION',
1587+
stackTrace: `${at}${st}`,
1588+
// TODO process inner/previous exception
1589+
}
1590+
}
1591+
} catch {
1592+
// Probably $__EXCEPTION not present
1593+
} finally {
1594+
if (old_max_depth < 3) {
1595+
await connection.sendFeatureSetCommand('max_depth', old_max_depth)
1596+
}
1597+
}
1598+
}
1599+
1600+
response.body = {
1601+
exceptionId: status.exception.name,
1602+
breakMode: 'always',
1603+
description: status.exception.message,
1604+
details: ex,
1605+
}
1606+
this.sendResponse(response)
1607+
}
15321608
}
15331609

15341610
vscode.DebugSession.run(PhpDebugSession)

src/xdebugConnection.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,12 @@ export class Connection extends DbgpConnection {
845845
return this._pendingExecuteCommand
846846
}
847847

848+
private _featureSet = new Map<string, number|string>()
849+
850+
public featureSet(feature:string): number|string|undefined {
851+
return this._featureSet.get(feature)
852+
}
853+
848854
/** Constructs a new connection that uses the given socket to communicate with Xdebug. */
849855
constructor(socket: Transport) {
850856
super(socket)
@@ -999,7 +1005,9 @@ export class Connection extends DbgpConnection {
9991005
* - notify_ok
10001006
*/
10011007
public async sendFeatureSetCommand(feature: string, value: string | number): Promise<FeatureSetResponse> {
1002-
return new FeatureSetResponse(await this._enqueueCommand('feature_set', `-n ${feature} -v ${value}`), this)
1008+
const res = new FeatureSetResponse(await this._enqueueCommand('feature_set', `-n ${feature} -v ${value}`), this)
1009+
this._featureSet.set(feature, value)
1010+
return res
10031011
}
10041012

10051013
// ---------------------------- breakpoints ------------------------------------

src/xdebugUtils.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import * as semver from 'semver'
2+
import * as xdebug from './xdebugConnection'
3+
4+
export function supportedEngine(initPacket: xdebug.InitPacket, version: string): boolean {
5+
return (
6+
initPacket.engineName === 'Xdebug' &&
7+
semver.valid(initPacket.engineVersion.replace('-dev', ''), { loose: true }) !== null &&
8+
semver.gte(initPacket.engineVersion.replace('-dev', ''), version, { loose: true })
9+
)
10+
}

0 commit comments

Comments
 (0)