Skip to content

Commit 4eda887

Browse files
authored
fix: fixed breakpoint and launch initialization order. (#608)
* Fixed breakpoint and launch initialization order. * Improve order or Launch, Initialize and breakpoint processing. * Optimize feature negotiation for known Xdebug version. Co-authored-by: Damjan Cvetko <damjan.cvetko@monotek.net>
1 parent 700113f commit 4eda887

File tree

5 files changed

+92
-46
lines changed

5 files changed

+92
-46
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).
66

7+
## [1.16.2]
8+
9+
## Fixed
10+
11+
- Fixed breakpoint and launch initialization order.
12+
- Optimize feature negotiation for known Xdebug version.
13+
714
## [1.16.1]
815

916
### Fixed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,4 +394,4 @@
394394
}
395395
]
396396
}
397-
}
397+
}

src/phpDebug.ts

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Terminal } from './terminal'
1212
import { convertClientPathToDebugger, convertDebuggerPathToClient } from './paths'
1313
import minimatch = require('minimatch')
1414
import { BreakpointManager, BreakpointAdapter } from './breakpoints'
15+
import * as semver from 'semver'
1516

1617
if (process.env['VSCODE_NLS_CONFIG']) {
1718
try {
@@ -147,6 +148,12 @@ class PhpDebugSession extends vscode.DebugSession {
147148
/** Breakpoint Adapters */
148149
private _breakpointAdapters = new Map<xdebug.Connection, BreakpointAdapter>()
149150

151+
/** the promise that gets resolved once we receive the done request */
152+
private _donePromise: Promise<void>
153+
154+
/** resolves the done promise */
155+
private _donePromiseResolveFn: () => any
156+
150157
public constructor() {
151158
super()
152159
this.setDebuggerColumnsStartAt1(true)
@@ -188,8 +195,6 @@ class PhpDebugSession extends vscode.DebugSession {
188195
supportTerminateDebuggee: true,
189196
}
190197
this.sendResponse(response)
191-
// request breakpoints right away
192-
this.sendEvent(new vscode.InitializedEvent())
193198
}
194199

195200
protected attachRequest(
@@ -210,6 +215,11 @@ class PhpDebugSession extends vscode.DebugSession {
210215
args.pathMappings = pathMappings
211216
}
212217
this._args = args
218+
219+
this._donePromise = new Promise<void>((resolve, reject) => {
220+
this._donePromiseResolveFn = resolve
221+
})
222+
213223
/** launches the script as CLI */
214224
const launchScript = async (port: number) => {
215225
// check if program exists
@@ -304,19 +314,30 @@ class PhpDebugSession extends vscode.DebugSession {
304314
this.sendEvent(new vscode.OutputEvent(log), true)
305315
}
306316
})
307-
await connection.waitForInitPacket()
317+
const initPacket = await connection.waitForInitPacket()
308318

309319
// support for breakpoints
310-
let feat_rb = await connection.sendFeatureGetCommand('resolved_breakpoints')
311-
if (feat_rb.supported === '1') {
320+
let feat: xdebug.FeatureGetResponse
321+
const supportedEngine =
322+
initPacket.engineName === 'Xdebug' && semver.gte(initPacket.engineVersion, '3.0.0')
323+
if (
324+
supportedEngine ||
325+
((feat = await connection.sendFeatureGetCommand('resolved_breakpoints')) &&
326+
feat.supported === '1')
327+
) {
312328
await connection.sendFeatureSetCommand('resolved_breakpoints', '1')
313329
}
314-
let feat_no = await connection.sendFeatureGetCommand('notify_ok')
315-
if (feat_no.supported === '1') {
330+
if (
331+
supportedEngine ||
332+
((feat = await connection.sendFeatureGetCommand('notify_ok')) && feat.supported === '1')
333+
) {
316334
await connection.sendFeatureSetCommand('notify_ok', '1')
317335
}
318-
let feat_ep = await connection.sendFeatureGetCommand('extended_properties')
319-
if (feat_ep.supported === '1') {
336+
if (
337+
supportedEngine ||
338+
((feat = await connection.sendFeatureGetCommand('extended_properties')) &&
339+
feat.supported === '1')
340+
) {
320341
await connection.sendFeatureSetCommand('extended_properties', '1')
321342
}
322343

@@ -336,6 +357,9 @@ class PhpDebugSession extends vscode.DebugSession {
336357

337358
this.sendEvent(new vscode.ThreadEvent('started', connection.id))
338359

360+
// wait for all breakpoints
361+
await this._donePromise
362+
339363
let bpa = new BreakpointAdapter(connection, this._breakpointManager)
340364
bpa.on('dapEvent', event => this.sendEvent(event))
341365
this._breakpointAdapters.set(connection, bpa)
@@ -382,6 +406,8 @@ class PhpDebugSession extends vscode.DebugSession {
382406
return
383407
}
384408
this.sendResponse(response)
409+
// request breakpoints
410+
this.sendEvent(new vscode.InitializedEvent())
385411
}
386412

387413
/**
@@ -550,6 +576,7 @@ class PhpDebugSession extends vscode.DebugSession {
550576
args: VSCodeDebugProtocol.ConfigurationDoneArguments
551577
) {
552578
this.sendResponse(response)
579+
this._donePromiseResolveFn()
553580
}
554581

555582
/** Executed after a successful launch or attach request and after a ThreadEvent */

src/test/adapter.ts

Lines changed: 44 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ describe('PHP Debug Adapter', () => {
3434
const program = path.join(TEST_PROJECT, 'hello_world.php')
3535

3636
it('should error on non-existing file', () =>
37-
assert.isRejected(client.launch({ program: 'thisfiledoesnotexist.php' })))
37+
assert.isRejected(
38+
Promise.all([client.launch({ program: 'thisfiledoesnotexist.php' }), client.configurationSequence()])
39+
))
3840

3941
it('should run program to the end', () =>
4042
Promise.all([
@@ -55,6 +57,7 @@ describe('PHP Debug Adapter', () => {
5557
it('should not stop if launched without debugging', () =>
5658
Promise.all([
5759
client.launch({ program, stopOnEntry: true, noDebug: true }),
60+
client.configurationSequence(),
5861
client.waitForEvent('terminated'),
5962
]))
6063
})
@@ -70,7 +73,7 @@ describe('PHP Debug Adapter', () => {
7073
it('should error on pause request', () => assert.isRejected(client.pauseRequest({ threadId: 1 })))
7174

7275
it('should handle disconnect', async () => {
73-
await Promise.all([client.launch({ program, stopOnEntry: true }), client.waitForEvent('initialized')])
76+
await Promise.all([client.launch({ program, stopOnEntry: true }), client.configurationSequence()])
7477
await client.disconnectRequest()
7578
})
7679
})
@@ -113,20 +116,18 @@ describe('PHP Debug Adapter', () => {
113116

114117
describe('line breakpoints', () => {
115118
async function testBreakpointHit(program: string, line: number): Promise<void> {
116-
await Promise.all([client.launch({ program }), client.waitForEvent('initialized')])
119+
client.launch({ program })
120+
await client.waitForEvent('initialized')
117121
const breakpoint = (
118122
await client.setBreakpointsRequest({
119123
breakpoints: [{ line }],
120124
source: { path: program },
121125
})
122126
).body.breakpoints[0]
123-
await waitForBreakpointUpdate(breakpoint)
127+
await client.configurationDoneRequest(), await waitForBreakpointUpdate(breakpoint)
124128
assert.isTrue(breakpoint.verified, 'breakpoint verification mismatch: verified')
125129
assert.equal(breakpoint.line, line, 'breakpoint verification mismatch: line')
126-
await Promise.all([
127-
client.configurationDoneRequest(),
128-
assertStoppedLocation('breakpoint', program, line),
129-
])
130+
await assertStoppedLocation('breakpoint', program, line)
130131
}
131132

132133
it('should stop on a breakpoint', () => testBreakpointHit(program, 4))
@@ -137,15 +138,16 @@ describe('PHP Debug Adapter', () => {
137138
it('should stop on a breakpoint identical to the entrypoint', () => testBreakpointHit(program, 3))
138139

139140
it('should support removing a breakpoint', async () => {
140-
await Promise.all([client.launch({ program }), client.waitForEvent('initialized')])
141+
client.launch({ program })
142+
await client.waitForEvent('initialized')
141143
// set two breakpoints
142144
let breakpoints = (
143145
await client.setBreakpointsRequest({
144146
breakpoints: [{ line: 3 }, { line: 5 }],
145147
source: { path: program },
146148
})
147149
).body.breakpoints
148-
await waitForBreakpointUpdate(breakpoints[0])
150+
await client.configurationDoneRequest(), await waitForBreakpointUpdate(breakpoints[0])
149151
await waitForBreakpointUpdate(breakpoints[1])
150152
assert.lengthOf(breakpoints, 2)
151153
assert.isTrue(breakpoints[0].verified, 'breakpoint verification mismatch: verified')
@@ -177,13 +179,15 @@ describe('PHP Debug Adapter', () => {
177179
const program = path.join(TEST_PROJECT, 'error.php')
178180

179181
it('should not break on anything if the file matches the ignore pattern', async () => {
180-
await Promise.all([client.launch({ program, ignore: ['**/*.*'] }), client.waitForEvent('initialized')])
182+
client.launch({ program, ignore: ['**/*.*'] })
183+
await client.waitForEvent('initialized')
181184
await client.setExceptionBreakpointsRequest({ filters: ['*'] })
182185
await Promise.all([client.configurationDoneRequest(), client.waitForEvent('terminated')])
183186
})
184187

185188
it('should support stopping only on a notice', async () => {
186-
await Promise.all([client.launch({ program }), client.waitForEvent('initialized')])
189+
client.launch({ program })
190+
await client.waitForEvent('initialized')
187191
await client.setExceptionBreakpointsRequest({ filters: ['Notice'] })
188192
const [, { threadId }] = await Promise.all([
189193
client.configurationDoneRequest(),
@@ -193,7 +197,8 @@ describe('PHP Debug Adapter', () => {
193197
})
194198

195199
it('should support stopping only on a warning', async () => {
196-
await Promise.all([client.launch({ program }), client.waitForEvent('initialized')])
200+
client.launch({ program })
201+
await client.waitForEvent('initialized')
197202
await client.setExceptionBreakpointsRequest({ filters: ['Warning'] })
198203
const [{ threadId }] = await Promise.all([
199204
assertStoppedLocation('exception', program, 9),
@@ -205,7 +210,8 @@ describe('PHP Debug Adapter', () => {
205210
it('should support stopping only on an error')
206211

207212
it('should support stopping only on an exception', async () => {
208-
await Promise.all([client.launch({ program }), client.waitForEvent('initialized')])
213+
client.launch({ program })
214+
await client.waitForEvent('initialized')
209215
await client.setExceptionBreakpointsRequest({ filters: ['Exception'] })
210216
const [, { threadId }] = await Promise.all([
211217
client.configurationDoneRequest(),
@@ -217,7 +223,8 @@ describe('PHP Debug Adapter', () => {
217223
// support for stopping on "*" was added in 2.3.0
218224
if (!process.env['XDEBUG_VERSION'] || semver.gte(process.env['XDEBUG_VERSION'], '2.3.0')) {
219225
it('should support stopping on everything', async () => {
220-
await Promise.all([client.launch({ program }), client.waitForEvent('initialized')])
226+
client.launch({ program })
227+
await client.waitForEvent('initialized')
221228
await client.setExceptionBreakpointsRequest({ filters: ['*'] })
222229
// Notice
223230
const [, { threadId }] = await Promise.all([
@@ -244,7 +251,8 @@ describe('PHP Debug Adapter', () => {
244251
}
245252

246253
it.skip('should report the error in a virtual error scope', async () => {
247-
await Promise.all([client.launch({ program }), client.waitForEvent('initialized')])
254+
client.launch({ program })
255+
await client.waitForEvent('initialized')
248256
await client.setExceptionBreakpointsRequest({ filters: ['Notice', 'Warning', 'Exception'] })
249257
const [
250258
{
@@ -324,20 +332,19 @@ describe('PHP Debug Adapter', () => {
324332
const program = path.join(TEST_PROJECT, 'variables.php')
325333

326334
it('should stop on a conditional breakpoint when condition is true', async () => {
327-
await Promise.all([client.launch({ program }), client.waitForEvent('initialized')])
335+
client.launch({ program })
336+
await client.waitForEvent('initialized')
328337
const bp = (
329338
await client.setBreakpointsRequest({
330339
breakpoints: [{ line: 10, condition: '$anInt === 123' }],
331340
source: { path: program },
332341
})
333342
).body.breakpoints[0]
343+
await client.configurationDoneRequest()
334344
await waitForBreakpointUpdate(bp)
335345
assert.equal(bp.verified, true, 'breakpoint verification mismatch: verified')
336346
assert.equal(bp.line, 10, 'breakpoint verification mismatch: line')
337-
const [, { frame }] = await Promise.all([
338-
client.configurationDoneRequest(),
339-
assertStoppedLocation('breakpoint', program, 10),
340-
])
347+
const { frame } = await assertStoppedLocation('breakpoint', program, 10)
341348
const result = (
342349
await client.evaluateRequest({
343350
context: 'watch',
@@ -349,33 +356,37 @@ describe('PHP Debug Adapter', () => {
349356
})
350357

351358
it('should not stop on a conditional breakpoint when condition is false', async () => {
352-
await Promise.all([client.launch({ program }), client.waitForEvent('initialized')])
359+
client.launch({ program })
360+
await client.waitForEvent('initialized')
353361
const bp = (
354362
await client.setBreakpointsRequest({
355363
breakpoints: [{ line: 10, condition: '$anInt !== 123' }],
356364
source: { path: program },
357365
})
358366
).body.breakpoints[0]
367+
await client.configurationDoneRequest()
359368
await waitForBreakpointUpdate(bp)
360369
assert.equal(bp.verified, true, 'breakpoint verification mismatch: verified')
361370
assert.equal(bp.line, 10, 'breakpoint verification mismatch: line')
362-
await Promise.all([client.configurationDoneRequest(), client.waitForEvent('terminated')])
371+
await client.waitForEvent('terminated')
363372
})
364373
})
365374

366375
describe('function breakpoints', () => {
367376
const program = path.join(TEST_PROJECT, 'function.php')
368377

369378
it('should stop on a function breakpoint', async () => {
370-
await Promise.all([client.launch({ program }), client.waitForEvent('initialized')])
379+
client.launch({ program })
380+
await client.waitForEvent('initialized')
371381
const breakpoint = (
372382
await client.setFunctionBreakpointsRequest({
373383
breakpoints: [{ name: 'a_function' }],
374384
})
375385
).body.breakpoints[0]
386+
await client.configurationDoneRequest()
376387
await waitForBreakpointUpdate(breakpoint)
377388
assert.strictEqual(breakpoint.verified, true)
378-
await Promise.all([client.configurationDoneRequest(), assertStoppedLocation('breakpoint', program, 5)])
389+
await assertStoppedLocation('breakpoint', program, 5)
379390
})
380391
})
381392
})
@@ -388,16 +399,14 @@ describe('PHP Debug Adapter', () => {
388399
let constantsScope: DebugProtocol.Scope | undefined
389400

390401
beforeEach(async () => {
391-
await Promise.all([
392-
client.launch({
393-
program,
394-
xdebugSettings: {
395-
max_data: 10000,
396-
max_children: 100,
397-
},
398-
}),
399-
client.waitForEvent('initialized'),
400-
])
402+
client.launch({
403+
program,
404+
xdebugSettings: {
405+
max_data: 10000,
406+
max_children: 100,
407+
},
408+
})
409+
await client.waitForEvent('initialized')
401410
await client.setBreakpointsRequest({ source: { path: program }, breakpoints: [{ line: 19 }] })
402411
const [, event] = await Promise.all([
403412
client.configurationDoneRequest(),

src/xdebugConnection.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ export class InitPacket {
1919
connection: Connection
2020
/** the version of Xdebug */
2121
engineVersion: string
22+
/** the name of the engine */
23+
engineName: string
2224
/**
2325
* @param {XMLDocument} document - An XML document to read from
2426
* @param {Connection} connection
@@ -30,6 +32,7 @@ export class InitPacket {
3032
this.protocolVersion = documentElement.getAttribute('protocol_version')!
3133
this.ideKey = documentElement.getAttribute('idekey')!
3234
this.engineVersion = documentElement.getElementsByTagName('engine')[0].getAttribute('version')!
35+
this.engineName = documentElement.getElementsByTagName('engine')[0].textContent ?? ''
3336
this.connection = connection
3437
}
3538
}
@@ -936,7 +939,7 @@ export class Connection extends DbgpConnection {
936939
)
937940
}
938941

939-
/** Sends a context_get comand */
942+
/** Sends a context_get command */
940943
public async sendContextGetCommand(context: Context): Promise<ContextGetResponse> {
941944
return new ContextGetResponse(
942945
await this._enqueueCommand('context_get', `-d ${context.stackFrame.level} -c ${context.id}`),

0 commit comments

Comments
 (0)