Bugfix Issue 1737 reqSpecCommands Unguarded Reads E_WARNING - sebiboga/testlink-upgraded GitHub Wiki
Issue #1737 — reqSpecCommands::deleteFile() / bulkReqMon() each wrote one E_WARNING row into the Event Viewer per request (2 unguarded reads)
Issue: #1737
Commits: 263b56388 (fix) + 93b38e488 (regression suite)
Branch: fix/issue-1737 (pushed; the CI workflow lands it)
Status: VERIFIED-FIXED — regression suite 4/4 PASS (Suite "Regression — Issue #1737")
Scope of this document: the two Event-Viewer noise paths of the legacy Requirement
Specification editor. Found while testing #1736.
Symptom
Two methods of lib/requirements/reqSpecCommands.class.php, reached through the legacy editor
lib/requirements/reqSpecEdit.php, each wrote one log_level=2 (E_WARNING) row into the
Event Viewer per request:
E_WARNING Undefined property: stdClass::$uploadOp
- lib/requirements/reqSpecCommands.class.php - Line 844 (every doAction=deleteFile)
E_WARNING foreach() argument must be of type array|object, null given
- lib/requirements/reqSpecCommands.class.php - Line 877 (every doAction=bulkReqMon on a spec with no requirements yet)
The HTTP responses themselves are normal (deleteFile 302 to reqSpecView.php, bulkReqMon
200), so from the outside the requests are indistinguishable from the intended path. The defect
exists only in the log — same "Event-Viewer-only" class as #1736, #1731, #1654. Measured on the
freshly imported DB: MAX(events.id)=2 before, and after the two requests rows id=3
(:877) and id=4 (:844) appeared.
The approach, and why this one
Root cause
:844 — initGuiObjForAttachmentOperations() reads $argsObj->uploadOp unconditionally, but
only fileUpload() ever creates that property:
814 function fileUpload(&$argsObj,$request) {
816 $argsObj->uploadOp = fileUploadManagement(...); // ONLY writer
819 return $this->initGuiObjForAttachmentOperations($argsObj);
825 function deleteFile(&$argsObj) {
827 deleteAttachment($this->db,$argsObj->file_id);
828 return $this->initGuiObjForAttachmentOperations($argsObj); // never sets uploadOp
835 private function initGuiObjForAttachmentOperations($argsObj) {
844 $guiObj->uploadOp = $argsObj->uploadOp; // ← E_WARNING
$argsObj is the args bean from reqSpecEdit.php:init_args() (:271-290); its iParams never
declares uploadOp. :819 and :828 are the only two callers of the helper.
:877 — bulkReqMon() iterates a possibly-null collection:
858 $obj = $this->initGuiBean(); // initGuiBean() (:65) does NOT set ->items
865 if( $my['options']['get_items'] ) {
869 $obj->items = $this->reqSpecMgr->get_requirements(...); // null when 0 reqs
877 foreach($obj->items as $xdx => &$itx) { ... } // ← E_WARNING
get_requirements() returns nothing for a spec with no requirements, so $obj->items stays
null — the common "just created the spec" case.
Reachability
Both verbs are whitelisted by reqSpecEdit.php (#1736 dispatch) and gated only by
checkRights() (mgt_view_req and mgt_modify_req). deleteFile is the attachment-delete
URL built at requirement_spec_mgr.class.php:2601; bulkReqMon is the bulk-monitor button at
gui/templates/dashio/requirements/reqSpecView.tpl:58. So any user who can edit requirements
writes one row per click — noise exactly where a real Error would need to be spotted.
The fix
Two one-line, behaviour-neutral changes in lib/requirements/reqSpecCommands.class.php:
deleteFile()initialises$argsObj->uploadOp = null;before calling the helper. A delete has no upload status, andrenderGui()(reqSpecEdit.php:460) already treatsnullas "nothing to report", so the response is byte-for-byte what it was.bulkReqMon()normalises the collection once:$obj->items = (array)($obj->items ?? array());It covers bothnull(0 requirements) and undefined (get_items=false), and givesreqBulkMon.tpl'sforeach/counta real array.
Alternatives rejected
- Drop the
$guiObj->uploadOp = $argsObj->uploadOp;assignment frominitGuiObjForAttachmentOperations()(the ticket's other suggestion). Rejected: with the assignment gone,fileUploadwould leave$guiObj->uploadOpunset, andreqSpecEdit.php:460'snull != $guiObj->uploadOpwould raise a new undefined-property warning on the upload path. Initialising indeleteFile()is the minimal correct fix. - Guard only the
foreachwith(array). Rejected in favour of normalising$obj->itemsitself, so thenullnever reaches the downstream template either.
Neither change alters the response for a well-formed request, and no user-facing string changed, so no i18n keys were added.
Verification
Suite "Regression — Issue #1737", tmp/TLU_Test_Cases.md — 4/4 PASS. Fixture
tmp/fixtures_1737.php; watermark MAX(events.id) before each request:
| Case | Result |
|---|---|
bulkReqMon on a spec with 0 requirements |
200, 10217 B, 0 new events (was 1 × :877) |
deleteFile&file_id=999999 on a throwaway spec |
302, 0 B, 0 new events (was 1 × :844) |
bulkReqMon on a spec with 2 requirements (no regression) |
200, 11303 B, 2 itemSet rows, startMon enabled, 0 events |
fileUpload (no regression) |
302 → reqSpecView.php…&uploadOPStatusCode=0, 0 events |
create / edit / reorder (no regression) |
200, 0 new events |
php -l |
no syntax errors |
Blast radius
| Measure | Value |
|---|---|
| Affected members | deleteFile + bulkReqMon, 2 of the 18 GUI actions |
Callers of initGuiObjForAttachmentOperations() |
reqSpecCommands.class.php:819 (fileUpload), :828 (deleteFile) |
deleteFile reachable from |
requirement_spec_mgr.class.php:2601 |
bulkReqMon reachable from |
gui/templates/dashio/requirements/reqSpecView.tpl:58 |
| Rows per affected request | 1 × log_level=2 |