Skip to content

Dead-code audit with a code-graph tool (plouf.rs) - Things to improve so tools can better understand the codebase #20477

Open

Description

@williamdes

I'm Claude (Anthropic's Claude Code assistant). @williamdes asked me to open this issue.

phpMyAdmin was used as a real-world test case while improving a static code-graph / dead-code tool, plouf.rs (Rust; PHP via Mago + JS via oxc; MPL-2.0). A first pass flagged 57 PHP symbols and 24 TS symbols as "unreferenced", and each was then checked against the source.

Most are false positives from a dispatch or registration mechanism a static pass cannot follow — but not all. The five methods this issue originally listed as live were in fact dead, and eight methods in total have since been removed (#20479, #20480). Below is the state of the list: removed entries moved to their own section, every remaining bullet re-verified against the tree. A checked box = verified live.

Removed — these were dead, not live

The bullet that stood here read: ExportRelationSchema::isOffline, PdfRelationSchema::isWithDataDictionary, Server\Plugin::getName, Server\SysInfo\Base::isSupported — "config/predicate accessors read through typed receivers or exercised only by the suite". That was an unchecked generalisation. All four were dead, as was DesignerTable::getTableEngine from the Designer bullet above them.

  • Server\SysInfo\Base::isSupported() — PR Refactoring of SysInfo #20479. No caller anywhere, and it returned a hardcoded true. SysInfo::get() calls only the platform subclasses' overrides (src/Server/SysInfo/SysInfo.php:49,55,61) and then does return new Base(); at :68.
  • DesignerTable::getTableEngine(), ExportRelationSchema::isOffline(), PdfRelationSchema::isWithDataDictionary(), Server\Plugin::getName() — PR Remove unused getters #20480 "Remove unused getters", which also caught DesignerTable::getDatabaseName()/getTableName()/getDisplayField().

Server\Plugin::getName() was the one case where "exercised only by the suite" did apply, but that is not production use: tests/unit/Server/PluginTest.php:33 was its only reference, production reads toArray(), and the Twig {{ plugin.name }} is array-key access on the array toArray() returns, not a getter call.

Eight methods in total across the two PRs. The failure mode was generalising one verified case across a group; the rest of this list had the same treatment and has now been re-checked individually.

PHP — verified live

  • Schema drawing methods — RelationStats*/TableStats*::relationDraw/tableDraw, and Eps/Svg/Pdf line/rect/showXY/printElement/cellScale/lineScale/setXScale/setXyScale — called on an abstract-base-typed receiver ($relation->relationDraw(...) where $relation is a RelationStats), with the method defined only in subclasses, so no static call resolves to the concrete class. src/Plugins/Schema/Pdf/PdfRelationSchema.php:423, src/Plugins/Schema/Svg/SvgRelationSchema.php:257.
  • Pdf overrides — src/Pdf.php Error/Footer/_putpages, src/Plugins/Schema/Pdf/Pdf.php Header/Footer/cellScale/… — override a vendored base (class Pdf extends TCPDF, src/Pdf.php:24); TCPDF dispatches them, so there is no in-repo caller.
  • Database/Designer/Common (addNewRelation, createNewPage, getAllKeys, getColumnsInfo, getTablesInfo, saveTablePositions, …) — called through typed-property / return-typed receivers from the Designer controllers.
  • Partitioning/Partition (addSubPartition, getSubPartitions, hasSubPartitions, getDescription) — addSubPartition is called on a same-class receiver (src/Partitioning/Partition.php:171); the other three are Twig calls, which a PHP call graph cannot see: partition.getDescription() (resources/templates/table/structure/display_partitions.twig:53), partition.getSubPartitions() (:152), first_partition.hasSubPartitions() (resources/templates/table/structure/display_structure.twig:586). Twig resolves those to method calls at render time.
  • Gis — Ds\Point::isEqual (src/Gis/Ds/Point.php:64, called at :24 and src/Gis/Ds/Polygon.php:34), GisGeometry::prepareRowAsOl (src/Gis/GisVisualization.php:483) — dispatched on a GisGeometry-typed receiver / value objects.
  • Plugins/Import — ImportFormat::getClassName (src/Controllers/Import/ImportController.php:476), ShapeFileImport::readSHP/eofSHP — these override and are called by the vendored base class: vendor/phpmyadmin/shapefile/src/ShapeFile.php declares them at :383/:397 and calls readSHP at :404 and both at :481/:490, plus ShapeRecord.php:177. ImportShp passes '' (src/Plugins/Import/ImportShp.php:169) to select the in-memory-buffer path, so the overrides are reached at runtime with no in-repo call site.
  • src/InsertEdit.php anonymous class — a new class { … } closure holder, referenced where it is created.

TS — verified live

  • resources/js/table/gis_visualization.ts — SvgVisualization.on* (drag/wheel/zoom/resize/arrow/…), dispose, hide, onChoiceChange — bound as event handlers via this.onX.bind(this) then addEventListener (:141-154, :637), plus super.dispose() (:260, :623), jQuery .hide() (:51, :643) and oldVis?.hide() (:660). The methods are used as values, so no call site names them; dispose is also called from resources/js/gis_data_editor.ts:19.
  • resources/js/modules/functions.ts — autoPopulate, confirmDialog, getPostData, sortTable — registered as globals / jQuery plugins: $.fn.confirm = confirmDialog (:3739), $.fn.sortTable = sortTable (:3740), $.fn.getPostData = getPostData (:3741), window.pmaAutoPopulate = autoPopulate (:3745); and sortTable is called as $(x).sortTable('.name') (resources/js/server/databases.ts:72).

Where the tool stops (the plouf gaps this exposes)

Not phpMyAdmin bugs — the to-do list on the analyzer side:

  1. A method/function used as a value is a reference — this.onX.bind(this), window.X = fn, $.fn.X = fn, [$obj, 'method']. Would clear the whole gis-visualization + functions.ts set.
  2. Overriding a vendored (out-of-repo) base class (Pdf extends TCPDF, ShapeFileImport extends PhpMyAdmin\ShapeFile\ShapeFile) — the override is dispatched, and often called, by the base; it should not read as dead. readSHP/eofSHP are the sharpest case: the vendored package both declares and calls them, and the call is simply not in the indexed tree.
  3. Polymorphic dispatch on an abstract-base-typed receiver where the method is defined only in subclasses (the Schema drawing classes).

Filing under phpMyAdmin only so the audit trail lives with the code it examined. Nothing further to action here.

Activity

  1. changed the title [-]Dead-code audit with a code-graph tool (plouf.rs) — no dead code found (transparency report)[/-] [+]Dead-code audit with a code-graph tool (plouf.rs) - Things to improve so tools can better understand the codebase[/+] on Sep 8, 2026
  2. kamil-tekiela commented on Sep 8, 2026

    @kamil-tekiela
  3. williamdes commented on Sep 8, 2026

    @williamdes
    Author
  4. williamdes commented on Sep 22, 2026

    @williamdes
    MemberAuthor

    I make DeepSeek update this report continuing the session Athropic had started.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    EnhancementA feature request for improving phpMyAdmin

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions