GeezerVison is showing github.com — Update to latest xdebug by carlos-granados · Pull Request #121 · php-debugger/php-debugger
Conversation
Updates php-debugger with all the changes added to Xdebug since our last sync:
- Brings over those commits that affect the debugger
- Ignores those commits that affect the parts of the extension that we have removed
When doing this, some new tests added by these commits found out that there was something that was not working correctly. If the debugger did not connect to a debugging client but we used the control socket, this will not work as expected. If we paused the debugger using the control socket, it would not have the right debugging information because the code would not have been instrumented. So when the control socket is active, we need to enable debugging instrumentation. This means that we need to change the control socket default so that by default it is not enabled and our code by default is not slowed down. If anyone needs to use the control socket they will need to enable it knowingly and pay the performance penalty, the same way we do with on demand debugging
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
…th not enough characters
…ng functions into their own files
…even when the limit was applied
claude[bot] reacted with eyes emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
claude
Bot
commented
Oct 4, 2026
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
|
Claude finished @carlos-granados's task in 4m 54s —— View job
Claude PR Review
Todo:
- Gather PR diff and understand scope
- Review control socket changes
- Review new/moved library files
- Review debugger protocol changes
- Review maps / cmd_parser changes
- Review tests
- Post final review
Reviewed the 33 commits. Most are an upstream Xdebug sync; the fork-specific delta is the four fix: commits that handle control-socket activation when no debugger is attached. Overall the changes are coherent and well-targeted. Specific findings below.
Fork-specific logic — looks correct
The core fix in 05540e8a (poll the control socket when no debugging client is attached) is right: when on_demand_debugging_enabled and control_socket_active are both false we skip instrumentation (observer disabled, early return); otherwise we still enable ZEND_COMPILE_EXTENDED_STMT so the statement handler fires. xdebug_statement_call in xdebug.c:692 runs xdebug_control_socket_dispatch() before checking statement_handler_enabled, so the control socket is still polled even though the heavier debugger logic is short-circuited. The pause handler in src/base/ctrl_socket.c:263-268 then re-enables both the statement handler and the observer, and xdebug_rebuild_stack() is called when the observer wasn't active — all the right pieces.
Observations and nits
-
Comment accuracy — minor in src/base/ctrl_socket.h:26-27, xdebug.c:617-621, and the CLAUDE.md note:
The control socket is only ever read from the statement handler
It's also dispatched from xdebug_execute_user_code_begin() in src/base/base.c:505. The statement handler is the dominant path (it fires per-statement, not per-function), but the one-liner undersells where the call lives. Low priority.
-
Behaviour divergence from Xdebug — c29beb4 changes xdebug.control_socket default from default to no. This is a deliberate trade-off (performance over drop-in parity) and is documented in CLAUDE.md, but CLAUDE.md:7 still asserts "drop-in compatible with Xdebug INI/functions/triggers". Worth a sentence there calling out that INI defaults diverge in this one case so the two statements agree.
-
socket_is_localhost and disable_tcp_syn_transmissions_for_localhost are not static (src/debugger/com.c:143, src/debugger/com.c:175). They're only used within com.c; marking them static matches the rest of the file and avoids leaking symbols. Fix this →
-
Macro missing parentheses in src/lib/maps/maps_private.h:32:
#define XDEBUG_PATH_MAP_EOF 1 << 30
Current call sites only use == and assignment so this works today, but any future use in an arithmetic context (x + XDEBUG_PATH_MAP_EOF) silently misbehaves due to << precedence. Trivial fix: #define XDEBUG_PATH_MAP_EOF (1 << 30). Upstream carry-over.
-
Pre-existing bug in named_pipe_listen() on Windows (src/base/ctrl_socket.c:335-348) — not introduced here, but surfaced while reviewing:
case ERROR_PIPE_CONNECTED:
if (!SetEvent(...)) { ... return false; }
/* fallthrough — missing break */
default:
xdebug_log_ex(...);
return false;
On the happy path (ERROR_PIPE_CONNECTED + successful SetEvent) control falls through to default: and the function returns false with an error log, so an actually-connected named pipe is torn down. This matches origin/main; not a regression from this PR, but it does negate the pause path on Windows under the "client already connected" case.
-
Possible mountinfo truncation in src/lib/system_utils.c:95-125 (introduced by the refactor from usefulstuff.c, so upstream behaviour is preserved): a fixed 8 KiB stack buffer with fread(buffer, 1, sizeof(buffer), ...) and no loop. On systems with many mounts (containers, snaps, long systemd units) mountinfo routinely exceeds 8 KiB; truncation can happen mid-line and xdebug_scan_mountinfo_for_private_tmp will then see a half-line without the matching /tmp token and either report nothing or report the wrong directory. Worth tracking upstream.
-
DBGp multiple_sessions handler (c8c6c38, src/debugger/handler_dbgp.c:1515-1519): the feature now reports supported="1" in feature_get but the handler was removed from feature_set. If a client calls feature_set -n multiple_sessions -v 1 it will fall through to the "unsupported feature" branch, which contradicts the supported="1" we just advertised. Upstream fix; may want a tracking note.
Tests
New tests look sensible — bug02267 cases cover both Windows and Unix for the TCP SYN retransmission fix, bug02421/02422/02424/02431/02435 give good coverage of the hardening work, and the ctrlSocketClient.php harness is a nice addition. The .phpt for bug02439 is self-contained and clear.
Summary
The fork-specific control-socket activation fix is correct and well-reasoned. The remaining items are a mix of inherited upstream oddities worth tracking and a couple of low-risk cleanups (static qualifiers, macro parens, CLAUDE.md consistency on the control_socket default). No blockers from my side.
• Branch: update-to-latest-xdebug
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
@claude which of the issues you raised were introduced by us and which come from upstream?
|
claude[bot] reacted with eyes emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
claude
Bot
commented
Oct 4, 2026
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
|
Claude finished @carlos-granados's task in 1m 32s —— View job
Classification of previous review findings
Split by source — fork-introduced in this PR vs. carried over from the upstream Xdebug sync (and one that predates both).
| # |
Finding |
Source |
Commit |
| 1 |
ctrl_socket.h:26-27 / xdebug.c:617-621 / CLAUDE.md claim "control socket is only ever read from the statement handler" |
Fork |
05540e8 + c29beb4 (both Carlos) |
| 2 |
xdebug.control_socket default no diverges from Xdebug while CLAUDE.md:7 still claims drop-in parity |
Fork |
c29beb4 |
| 3 |
socket_is_localhost / disable_tcp_syn_transmissions_for_localhost not static in com.c |
Upstream |
a612ba2 (Derick, upstream #2267) |
| 4 |
XDEBUG_PATH_MAP_EOF macro missing parentheses |
Upstream |
0c56972 (Derick, upstream #2398) |
| 5 |
named_pipe_listen() Windows fallthrough bug (ERROR_PIPE_CONNECTED → default tear-down) |
Pre-existing on origin/main — predates both this PR and the upstream sync window |
— |
| 6 |
system_utils.c fixed 8 KiB mountinfo read with no loop |
Upstream, pre-existing — the refactor from usefulstuff.c to system_utils.c happened in cfad3f3, but the 8 KiB fread was already there in usefulstuff.c on origin/main |
moved by cfad3f3 |
| 7 |
DBGp multiple_sessions advertised supported but handler removed from feature_set |
Upstream |
c8c6c38 (Derick, upstream #2435) |
Fork-introduced (worth addressing on this PR if you want them cleaned up before merge): #1, #2. These both came out of the control-socket activation work and are small doc / comment tightening.
Upstream carry-overs (worth tracking back to Xdebug rather than fixing here, since diverging from upstream increases future-sync friction): #3, #4, #7, and the file move in #6.
Pre-existing, unrelated to this PR: #5 and the underlying mountinfo-truncation behaviour in #6.
- Check
com.c static qualifiers — upstream a612ba2
- Check
maps_private.h macro — upstream 0c56972
- Check
system_utils.c mountinfo — upstream refactor cfad3f3, behaviour pre-existing
- Check
multiple_sessions handler — upstream c8c6c38
- Check comment accuracy claim — fork
05540e8
- Check
named_pipe_listen fallthrough — pre-existing on origin/main
- Check CLAUDE.md drop-in parity line — fork
c29beb4
• Branch: update-to-latest-xdebug
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
claude[bot] reacted with eyes emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
claude
Bot
commented
Oct 4, 2026
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
|
Claude finished @carlos-granados's task in 5m 37s —— View job
Claude PR Review — fresh pass
Todo:
- Pull the current diff against
origin/main
- Re-check fork-specific control-socket logic
- Re-check library refactors (
arg, set, stripcslashes, system_utils, cmd_parser, maps)
- Re-check DBGp handler changes
- Re-check new tests
- Post the review
Re-reviewed the PR at its current state (33 commits, latest 62c760f). The CLAUDE.md and README.md updates landed cleanly, the control-socket activation logic reads well, and the fork-specific cleanup commits are each tightly scoped. One new, significant finding this pass — a latent ownership bug that the fork's memory-leak fix in handle_command is now actively exposing.
New finding — exposed by this PR
src/base/ctrl_socket.c:216 — ps handler frees an interior zend_string pointer
Commit 442fcaa correctly adds xdebug_xml_node_dtor(retval) / xdebug_str_free(message) to handle_command() — the retval tree and message buffer were being leaked on every ctrl-socket command on origin/main. Good fix.
However, that destructor now reaches a latent bug in the ps handler:
file = xdebug_xml_node_init("fileuri");
if (fse && fse->filename) {
xdebug_xml_add_text(file, ZSTR_VAL(fse->filename)); // free_text=1
}
xdebug_xml_add_text() defaults to free_text=1 (xml.c:171), so xdebug_xml_text_node_dtor will call xdfree(node->text) → free(ZSTR_VAL(fse->filename)) at destruction time. ZSTR_VAL(zstr) is an interior pointer into a zend_string allocation (the inline val[1] flexible-array), not a standalone malloc'd pointer — free() on it is undefined behavior and will typically crash with free(): invalid pointer.
Why it doesn't crash in the new tests: the bug02424-* tests invoke ps with start_with_request=no and no debug client, so the observer is deactivated on first call (base.c:488-493) and XG_BASE(stack) is empty — the new if (fse && fse->filename) guard skips the problematic call entirely.
When it will crash: any scenario where the stack has user-defined frames when a ps command arrives — e.g. a user has xdebug.mode=debug + a connected IDE + control_socket enabled, triggers pause via ctrl-socket (which populates the stack via xdebug_rebuild_stack()), then issues a subsequent ps. Fse is populated, ZSTR_VAL gets stored for later free, destructor crashes.
Compare the sibling engine node two lines earlier, which does it correctly:
xdebug_xml_add_text(engine, xdstrdup(XDEBUG_NAME));
Fix: wrap in xdstrdup, same as engine:
xdebug_xml_add_text(file, xdstrdup(ZSTR_VAL(fse->filename)));
Was previously masked by the retval leak. Worth adding a regression test that exercises ps with a populated stack (connected debugger + pause → ps) so this doesn't regress silently again.
Fix this →
Resolved since the previous review
| # |
Previous finding |
Status |
| 1 |
Comment accuracy: "control socket is only ever read from the statement handler" |
✅ resolved — src/base/ctrl_socket.h:26-29 and xdebug.c:617-623 now explicitly list both the statement handler and the observer's xdebug_execute_user_code_begin() path. The new OPCACHE-CS log message in xdebug.c:642-645 carries forward the simpler wording ("polled from the statement handler"), but the user-facing text is close enough — fine to leave. |
| 2 |
CLAUDE.md drop-in parity line conflicting with the new default |
✅ resolved — CLAUDE.md:7 now adds "Where the two goals conflict, overhead wins and the divergence is documented", and a new paragraph at CLAUDE.md:108 explicitly calls out the xdebug.control_socket=no default. README.md adds a row to the compatibility table. |
Carrying over, upstream — same classification as last time
Additional small observations noticed this pass
-
src/lib/set.c:21 — MEM_SIZE macro missing parentheses:
#define MEM_SIZE(s) ((s) / 8) + (((s) % 8) != 0)
Current call sites (direct assignment and memset size argument) all parse correctly, so no bug today. Any future use in an arithmetic context (buf + MEM_SIZE(x)) would silently misbehave. Upstream carry-over.
-
src/debugger/handler_dbgp.c:2448 — DBGP_MAX_PACKET_SIZE = 64 KiB cap (upstream a9573c5, issue #2422). This is specifically for inbound DBGp commands from the IDE, not responses, so 64 KiB is comfortably more than any legitimate command. Just flagging the number for future reference in case anyone wonders. Looks right.
-
src/base/ctrl_socket.c:263-268 — pause handler re-enabling instrumentation:
if (!XG_BASE(observer_active)) {
xdebug_rebuild_stack();
}
XG_BASE(statement_handler_enabled) = true;
XG_BASE(observer_active) = true;
Correct. But note the asymmetry with base.c:488-493, which forces observer_active = 0 on the first user-code call when there's no debug connection — regardless of whether the control socket is active. That means if a user has control_socket=time + start_with_request=no + no IDE, the observer will be shut down at the first call, and only pause re-enables it. That's probably the intended behavior (we want zero-overhead until the user explicitly asks for a pause), but it also means ps will never see populated stack frames in that configuration — which, incidentally, is what hides the finding above in the current tests.
Tests
Reviewed tests/debugger/bug02267-{win,unix}.phpt, bug02421.phpt, bug02422.phpt, bug02424-01..04.phpt, bug02431.phpt, bug02435.phpt, bug02439.phpt, dbgp/ctrlSocketClient.php. New tests are self-contained and read clearly. The ctrlSocketClient.php harness is a reasonable mirror of the existing dbgpclient.php style. No complaints.
Summary
- One real finding this pass: the
ps handler's ZSTR_VAL ownership bug, newly exposed by the (correct) memory-leak fix. Would recommend addressing before merge, plus a regression test with a populated stack.
- Previous finding Bump actions/checkout from 4 to 6 #1 and Bump actions/download-artifact from 4 to 8 #2 both resolved cleanly.
- Everything else is upstream carry-over worth tracking back to Xdebug rather than fixing on this branch.
• Branch: update-to-latest-xdebug
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
claude[bot] reacted with eyes emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
claude
Bot
commented
Oct 6, 2026
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
|
Claude finished @carlos-granados's task in 3m 49s —— View job
Claude PR Review — fresh pass
Todo:
- Pull current diff against
origin/main
- Check if the
ps handler ZSTR_VAL ownership bug from previous review is fixed
- Review any new commits since last review
- Re-check control-socket activation logic
- Re-check DBGp protocol changes
- Re-check new tests
- Post final review
Re-reviewed the PR at its current state (34 commits, HEAD b3b2319). One new commit since the previous review — b3b2319 fix: report the current file in 'ps' when the observer is not collecting frames — which directly addresses the previous pass's single finding. No new findings this pass.
Previous finding resolved
src/base/ctrl_socket.c — ps handler ZSTR_VAL ownership bug ✅ Fixed in b3b2319
The fix at src/base/ctrl_socket.c:227 applies exactly the recommended change:
if (fse && fse->filename) {
/* xdstrdup, because the node owns its text and frees it: ZSTR_VAL is an
* interior pointer into the zend_string, not its allocation. */
xdebug_xml_add_text(file, xdstrdup(ZSTR_VAL(fse->filename)));
}
The commit also goes further than the minimum fix: it rebuilds the stack on demand when the observer isn't collecting frames (src/base/ctrl_socket.c:200-213), so ps can now report a fileuri even in the common "no client attached, control_socket=time" configuration — previously that path reported an empty <fileuri></fileuri>. The on-demand rebuild then empties the stack again at ctrl_socket.c:251-253, which is correct: nothing else is pushing or popping frames when the observer is inactive, so a stack left behind would go stale.
I checked the ownership round-trip:
xdebug_rebuild_stack() (base.c:439) starts with xdebug_vector_empty(), then calls add_stack_frame_recursively(), which pushes function_stack_entry values with addref'd zend_string filenames.
function_stack_entry_dtor (base.c:127-129) releases each fse->filename via zend_string_release.
- The post-ps
xdebug_vector_empty() pops every entry, firing that dtor, so the addrefs are balanced.
Two regression tests cover both branches of the new logic:
tests/debugger/ctrl-socket-ps-fileuri.phpt — stack empty (start_with_request=no, no on_demand_debugging_enabled), asserts the fileuri IS reported after on-demand rebuild.
tests/debugger/ctrl-socket-ps-stack.phpt — observer collecting frames (on_demand_debugging_enabled=1), asserts ps doesn't crash after the response tree is freed by the 442fcaa leak fix.
The ctrlSocketClient.php harness is extended with a $blankFileUri option so the first test can assert against the real path. Clean, targeted change.
A small style nit, not blocking: the Unix ps semantics imply the "process program" (the entry-point file). XDEBUG_VECTOR_HEAD returns index 0, and add_stack_frame_recursively recurses on prev_execute_data before pushing the current frame, so HEAD is the oldest (bottom) frame — the entry script. That matches the intent and preserves the pre-existing behavior of the handler, but it is not obvious from the code; a one-line comment on fse = XDEBUG_VECTOR_HEAD(...) saying "entry-point frame, Unix-ps-style" would spare the next reader my paragraph of digging.
Still outstanding — upstream carry-overs (not fork-introduced, not blocking)
Same classification as the previous pass:
Worth tracking back to Xdebug rather than fixing here (diverging increases future-sync cost). None are regressions from this PR.
Fork-specific control-socket logic — still looks correct
Re-walked the full flow:
xdebug.c:613-648 — if no client connected AND neither on_demand_debugging_enabled nor control_socket_active is set, bail out before enabling ZEND_COMPILE_EXTENDED_STMT. The two log messages (OPCACHE-OD / OPCACHE-CS) correctly distinguish the two reasons the observer stays armed.
src/base/ctrl_socket.c:263-290 — pause re-enables the statement handler and observer, and rebuilds the stack if the observer wasn't active. The pattern is intentionally different from ps: pause means "we're attaching, keep the stack around"; ps means "snapshot and move on."
xdebug_statement_call in xdebug.c:692 still dispatches the control socket before the statement_handler_enabled short-circuit, so polling continues even when the heavier debugger logic is skipped.
Comment / docstring consistency
Both src/base/ctrl_socket.h:25-28 and xdebug.c:617-623 now list both the statement handler AND the observer's xdebug_execute_user_code_begin() as polling sites. CLAUDE.md:7 acknowledges the one deliberate INI-default divergence (xdebug.control_socket=no) with a cross-reference to the detailed note at CLAUDE.md:108. Both of the previous pass's doc findings read cleanly now.
Summary
- Previous finding fixed cleanly, plus a UX improvement (fileuri now reported in the no-client case) and two tests covering both branches.
- No new findings this pass.
- Everything else is upstream carry-over worth tracking back to Xdebug.
LGTM from my side.
• Branch: update-to-latest-xdebug
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Reviewers
pronskiy
pronskiy approved these changes
Uh oh!
There was an error while loading. Please reload this page.
Assignees
No one assigned
Milestone
No milestone
Development
Successfully merging this pull request may close these issues.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
|