You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Action: verify the selected row is a Process before casting - #2151
Guards the Process cast in the five per-process InfoScreen actions — actionLsof, actionShowLocks, actionStrace, actionShowEnvScreen, actionShowCommandScreen — by checking the selected row's class before casting,
instead of relying on an assert after the cast.
Why
Each of these actions is gated only by Action_readableProcess / Action_writeableProcess, which check nothing but settings->ss->dynamic. They
then cast the selected main-panel row straight to Process* and assert its
class.
settings->ss and host->activeTable can fall out of sync:
rebuildSettingsArray() (ScreensPanel.c) points settings->ss at the screen
selected in the Screens list, without touching host->activeTable.
Action_runSetup() returns without calling setActiveScreen() — the only
place that re-pairs ss and activeTable.
So after visiting Setup, ss can refer to a built-in (non-dynamic) screen while activeTable still refers to a dynamic table whose rows are Instances, not Processes. The readable/writeable check passes, the row is cast to Process, and the assert aborts in debug builds — or, under NDEBUG, a
non-Process object is used as a Process (undefined behavior).
Reproduction (PCP build, which has dynamic screens):
Make a dynamic screen active.
Open Setup and select a built-in screen in the Screens list.
Press F7/[/-/F8/]/+/F9 — rebuildSettingsArray sets settings->ss to the built-in screen; host->activeTable is unchanged.
Exit Setup and press l, x, s, e or c.
Fix
Fetch the selected row as an Object*, bail out with HTOP_OK unless Object_isA(..., &Process_class), then cast. Each call's existing const-ness is
preserved.
No automated test: htop has no C unit-test harness, and the only integration
driver (test_spec.lua) runs the plain build, which has no dynamic screens to
produce the desync. Verified by code analysis and the manual sequence above.
actionLsof, actionShowLocks, actionStrace, actionShowEnvScreen and
actionShowCommandScreen cast the selected main-panel row to Process and
assert its class, guarded only by Action_readableProcess /
Action_writeableProcess, which check nothing but settings->ss->dynamic.
The Setup screen can leave settings->ss and host->activeTable out of
sync: rebuildSettingsArray() points settings->ss at the screen selected
in the Screens list without updating host->activeTable, and
Action_runSetup() returns without calling setActiveScreen() (the only
place that re-pairs the two). So after Setup, ss can refer to a built-in
(non-dynamic) screen while activeTable still refers to a dynamic table
whose rows are Instances, not Processes. The readable/writeable check
then passes and the row is cast to Process: the assert aborts in debug
builds, and NDEBUG builds use a non-Process row as a Process (undefined
behavior).
Check Object_isA(..., &Process_class) before the cast and bail out with
HTOP_OK when the selected row is not a Process.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Pierre Wilke <wilke.pierre@gmail.com>
Five process-specific actions now check that the selected panel object exists and is a Process before casting it. If the selection is missing or has another type, each action returns HTOP_OK.
Possibly related PRs
htop-dev/htop#2148: Adds a process-type check to actionShowMaps, using the same selected-object validation pattern.
Suggested reviewers:benbe, explorer09
Change: Bug fix
Merge Risk: Low · up to e87dc
Setup now restores the correct active table, but switching tables can unexpectedly follow an unrelated row with the same numeric ID. This is a bounded selection issue; preserve table-specific follow state and add regression coverage.
- [ ] Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Five actions check the selected row Before a screen can open A missing choice returns cleanly Other objects pass no cast The process path proceeds as planned
The reason will be displayed to describe this comment to others. Learn more.
Note
Follow-up findings are listed below so you can review them separately from inline comments.
Follow-up (1)
Minor (1)
Minor · Resync the active table when Setup closes. · Action.c:612
Action.c:612 Minor · Functional Correctness · Quick win
Resync the active table when Setup closes.
Moving a dynamic screen in Setup can set settings->ss to that screen. Action_runSetup() does not update host->activeTable, so the main panel keeps the process table. actionShowLocks() then rejects valid process rows through Action_readableProcess(). Reconcile settings->ss and host->activeTable when Setup returns.
Add regression coverage for the mismatched-row guards.
No CI step runs the Lua suite, and no C test target exists. Removing any of the five new Object_isA guards would not fail CI. The Lua suite sends keys only to the main, Setup, help, and theme screens. No case selects a non-Process row on a dynamic screen.
Add a case that opens a dynamic screen with an Instance row selected, then sends l, x, s, e, and w. The case should check that no screen opens. The test configuration must expose a dynamic screen for this case. If the existing configuration cannot provide one, record the gap as a known limitation.
Review info
Run configuration
Source mode: Auto
Repository: Unavailable
Branch: Unavailable
File: No configuration file from this repository applied
Commit: Unavailable
Layers: Repository UI
Review profile: ASSERTIVE
Plan: Advanced
Run ID: d8e44ef5-2dbe-4862-8255-724faa83e9d6
Commits
Reviewing files that changed from the base of the PR and between 5091900 and 5f15a39.
Files selected for processing: 1
Action.c
Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.
The reason we should fix this goes even a bit deeper, as there are plans (not soon, but they exist), to show things other than processes in an active screen. Ad even today, if for some reason, one of the info screens re-triggered one of the existing Actions this type confusion could cause havoc.
Also note, that in theory you could have an active screen without an active, selected process (empty process list ;-)). So being extra paranoid with the active screen list and its rows absolutely doesn't hurt.
@pwilke Can you add a second commit resolving the follow-up finding from @coderabbitai? We can ignore the CI nitpick. ;-)
For best results, initiate chat on the files or code changes.
@BenBE Agreed. Actions should handle an empty selection and verify that the selected row is a Process before using process-specific data. We can leave the CI nitpick out of this PR.
Learnings used
Learnt from: BenBE
Repo: htop-dev/htop PR: 0
File: :0-0
Timestamp: 2026-05-24T22:27:59.364Z
Learning: For the htop-dev/htop repository, commits that correct issues introduced within the same PR must be squashed/fixup'd into the original commit rather than left as separate commits. Maintainer preference (BenBE) is for clean, linear commit history before merging. Encourage contributors to use `git rebase -i` with fixup to achieve this.
Fixes the root cause behind the previous commit's guards. rebuildSettingsArray()
repoints settings->ss at the screen selected in the Screens list, but
Action_runSetup() returned without updating host->activeTable, and
setActiveScreen() (the only place that re-pairs them) is not called on the Setup
exit path. So after Setup, settings->ss and host->activeTable could disagree: a
dynamic screen could be active while the panel still showed the process table
(or vice versa), which also made Action_readableProcess() reject valid process
rows.
Call setActiveScreen() when Setup returns so settings->ss and host->activeTable
are reconciled. The per-action row-type guards are kept as defense in depth, as
other screens may become non-process in the future and an info screen could
re-enter an action.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Pierre Wilke <wilke.pierre@gmail.com>
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Review info
Run configuration
Source mode: Auto
Repository: Unavailable
Branch: Unavailable
File: No configuration file from this repository applied
Commit: Unavailable
Layers: Repository UI
Review profile: ASSERTIVE
Plan: Advanced
Run ID: dd6dc5ae-8b28-413b-a6dc-fdb465d84fc1
Commits
Reviewing files that changed from the base of the PR and between 5f15a39 and e87dc28.
Files selected for processing: 1
Action.c
Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.
nl -ba ScreensPanel.c | sed -n '1,60p;234,280p;365,430p'
nl -ba ScreenManager.c | sed -n '225,282p'
nl -ba Table.c | sed -n '1,115p;115,255p'
nl -ba pcp/Instance.c | sed -n '1,180p'
nl -ba Machine.c | sed -n '50,120p'
nl -ba pcp/screens/devices | sed -n '1,100p'
rg -n -F -e 'Action_follow' -e 'following' -- docs/htop.1.in htop.1.in README.md 2>/dev/null
Repository: htop-dev/htop
Length of output: 30591
Do not transfer follow IDs between unrelated tables on Setup exit.
If Setup selects a different table, setActiveScreen can copy a follow ID from the previous table. A PCP instance with ID 1 can then cause the process table to follow PID 1. Preserve each table’s follow state without transferring IDs across tables.
nl -ba Action.c | sed -n '365,445p;585,600p'
nl -ba ScreensPanel.c | sed -n '90,235p;280,365p'
nl -ba Machine.c | sed -n '50,100p'
nl -ba .github/workflows/ci.yml | sed -n '1,175p;200,245p;410,455p'
nl -ba TESTPLAN | sed -n '1,200p'
git ls-files '*htoprc*'iftest -f default.htoprc;then nl -ba default.htoprc;elseecho'default.htoprc is not present in checkout';fi
rg -n -i -e 'busted' -e 'test_spec\.lua' -e 'make check' -e 'installcheck' --glob '!test_spec.lua' --glob '!configure.ac' --glob '!Makefile.in' --hidden -g '!.git/**'.||test"$?" -eq 1
Repository: htop-dev/htop
Length of output: 31120
Add a regression test for Setup table synchronization.
If a screen backed by a different table is reordered in Setup, removing this call can leave settings->ss and host->activeTable out of sync. Add a test that reorders screens backed by different tables and checks the resulting pairing.
The reason will be displayed to describe this comment to others. Learn more.
I see two ways out of this, tell me what you prefer.
inline most of the setActiveScreen function here, without the follow ID transfers
Machine*host=st->host;
if (!settings->ss->table)
settings->ss->table=host->processTable;
host->activeTable=settings->ss->table;
boolreadonly=Settings_isReadonly() || (host->activeTable!=host->processTable);
MainPanel_setFunctionBar(st->mainPanel, readonly);
would replace the current setActiveScreen call.
extract the follow-ID-free part of setActiveScreen into another function, say setActiveScreenNoFollow, can call this latter function in Action_runSetup
staticinlineboolsetActiveScreenNoFollow(Settings*settings, State*st, unsigned intssIdx) {
assert(settings->ssIndex==ssIdx);
Machine*host=st->host;
settings->ss=settings->screens[ssIdx];
if (!settings->ss->table)
settings->ss->table=host->processTable;
host->activeTable=settings->ss->table;
// set correct functionBar - readonly if requested, and/or with non-process screensboolreadonly=Settings_isReadonly() || (host->activeTable!=host->processTable);
MainPanel_setFunctionBar(st->mainPanel, readonly);
returnhost->activeTable->following!=-1;
}
staticinlineboolsetActiveScreen(Settings*settings, State*st, unsigned intssIdx) {
// Save following state from current table before switching screensintfollowing=st->host->activeTable->following;
setActiveScreenNoFollow(settings, st, ssIdx);
// Transfer following state to new table if it doesn't already have oneMachine*host=st->host;
if (following!=-1&&host->activeTable->following==-1)
host->activeTable->following=following;
returnhost->activeTable->following!=-1;
}
@pwilke I prefer the second option. It keeps screen and function-bar synchronization in one place and avoids transferring a follow ID on Setup exit. It also explicitly sets settings->ss from settings->screens[ssIdx].
The wrapper preserves the existing behavior for normal tab changes. Those changes can still transfer follow IDs between different tables, so this proposal addresses Setup exit only.
After the change, please verify the Setup reproduction with process and PCP screens, including that Setup does not copy the previous table’s follow ID to the newly active table.
The reason will be displayed to describe this comment to others. Learn more.
How about this new version of setActiveScreen
staticinlineboolsetActiveScreen(Settings*settings, State*st, unsigned intssIdx, booltransferFollow) {
assert(settings->ssIndex==ssIdx);
Machine*host=st->host;
// Save following state from current table before switching screensintfollowing=host->activeTable->following;
settings->ss=settings->screens[ssIdx];
if (!settings->ss->table)
settings->ss->table=host->processTable;
host->activeTable=settings->ss->table;
// Transfer following state to the new table if requested and it has none.// Skipped across unrelated tables (e.g. on Setup exit): a row id is not// meaningful from one table kind to another.if (transferFollow&&following!=-1&&host->activeTable->following==-1)
host->activeTable->following=following;
// set correct functionBar - readonly if requested, and/or with non-process screensboolreadonly=Settings_isReadonly() || (host->activeTable!=host->processTable);
MainPanel_setFunctionBar(st->mainPanel, readonly);
returnhost->activeTable->following!=-1;
}
It adds a flag saying whether follow should be transferred. There are 3 existing calls to patch with an extra true parameter, and our new call in Action_runSetup that would call setActiveScreen with transferFollow set to false.
@pwilke Yes, I prefer this version. It keeps screen synchronization in one function and makes follow-ID transfer explicit.
Pass false on Setup exit and true at the three existing screen-switch calls. Also update the forward declaration. I would adjust the comment: transfers are skipped on Setup exit, but normal screen switches can still transfer IDs between unrelated tables.
After the change, please verify the Setup reproduction with process and PCP screens. Check that the destination table keeps its own follow state and does not receive the previous table’s ID.
You are interacting with an AI system.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
What
Guards the
Processcast in the five per-process InfoScreen actions —actionLsof,actionShowLocks,actionStrace,actionShowEnvScreen,actionShowCommandScreen— by checking the selected row's class before casting,instead of relying on an
assertafter the cast.Why
Each of these actions is gated only by
Action_readableProcess/Action_writeableProcess, which check nothing butsettings->ss->dynamic. Theythen cast the selected main-panel row straight to
Process*andassertitsclass.
settings->ssandhost->activeTablecan fall out of sync:rebuildSettingsArray()(ScreensPanel.c) pointssettings->ssat the screenselected in the Screens list, without touching
host->activeTable.Action_runSetup()returns without callingsetActiveScreen()— the onlyplace that re-pairs
ssandactiveTable.So after visiting Setup,
sscan refer to a built-in (non-dynamic) screen whileactiveTablestill refers to a dynamic table whose rows areInstances, notProcesses. Thereadable/writeablecheck passes, the row is cast toProcess, and theassertaborts in debug builds — or, underNDEBUG, anon-
Processobject is used as aProcess(undefined behavior).Reproduction (PCP build, which has dynamic screens):
F7/[/-/F8/]/+/F9—rebuildSettingsArraysetssettings->ssto the built-in screen;host->activeTableis unchanged.l,x,s,eorc.Fix
Fetch the selected row as an
Object*, bail out withHTOP_OKunlessObject_isA(..., &Process_class), then cast. Each call's existing const-ness ispreserved.
Notes
@BenBE's suggestion there, the pre-existing occurrences are fixed here on their
own; Show memory map of highlighted process #2148 writes its new action correctly from the start.
driver (
test_spec.lua) runs the plain build, which has no dynamic screens toproduce the desync. Verified by code analysis and the manual sequence above.
🤖 Generated with Claude Code