Skip to content

Bugfix Issue 1520 Aliens MethodExists Null Guard

TestLink CI edited this page Sep 16, 2026 · 1 revision

Bugfix Issue 1520 — addAliens method_exists(null,'addLink') TypeError on tracker-less projects (PHP 8)

Issue: #1520 Branch: fix/issue-1520 Status: VERIFIED-FIXED (2026-09-16)

Symptom

testcase::addAliens() fatals on PHP 8 whenever the owning test project has NO issue tracker configured. The alien INSERT into testcase_aliens (lines 10365-10367) succeeds, then the optional tracker-update block fatally aborts:

PHP Fatal error:  Uncaught TypeError: method_exists(): Argument #1
($object_or_class) must be of type object|string, null given in
.../lib/functions/testcase.class.php:10372

Stack trace:
#0 .../lib/functions/testcase.class.php(10372): method_exists()
#1 .../lib/functions/testcase.class.php(10584): testcase->addAliens()
   (call chain: copyAliensTo -> addAliens)

Repro steps

  1. Fresh-import DB. Fixture: test project 1 (PRJ, issue_tracker_enabled=0, no testproject_issuetracker row), suite node 2, testcase node 3, tcversion 4 (v1) with testcase_aliens (1,3,4,1001); a second tcversion 5 (v2) with no aliens yet.
  2. Bootstrap the framework exactly like api/testcases/index.php (config.inc.php + lib/functions/common.php), create the testcase manager, setTestProject(1).
  3. Call addAliens((object){tproject_id:1, tcase_id:3, tcversion_id:5}, [1001], 1).
  4. Before fix: exit 255, TypeError: method_exists(): Argument #1 ($object_or_class) must be of type object|string, null given at line 10372. After fix: returns OK, and testcase_aliens gets row (1,3,5,1001,1).

Expected: the tracker-add loop is declared optional (if ( method_exists($repo,'addLink') )). With no tracker configured the loop must be skipped silently while the testcase_aliens rows are still persisted.

Root cause

lib/functions/testcase.class.php:10370-10372:

$system = new tlIssueTracker($this->db);
$repo = $system->getInterfaceObject($safeID['tpr']);
if ( method_exists($repo,'addLink') ) { ...

Chain:

  1. tlIssueTracker::getInterfaceObject($tprojectID) (lib/functions/tlIssueTracker.class.php:670): for a project with no linked issuetracker, getLinkedTo() returns null and the method returns $_SESSION['its'][$name] (set to null at line 708). Contract = "null = no tracker".
  2. line 10372 hands that null to method_exists() as argument #1. PHP 8.x raises TypeError on a non-object/non-string first argument; PHP 7.x silently returned false. Latent PHP7-era code.

Why it breaks now: TestLink 2.0.1 runs PHP 8; any code path that reaches addAliens() on a tracker-less project crashes. Reachable via copyAliensTo() (create_new_version, line 10584) and testcaseCommands::addAlien() (line 1610) whenever $tcaseMgr->tproject_id holds a valid tracker-less project id.

Blast radius: testcase::addAliens() line 10372 (addLink) and its sibling testcase::removeAlien() line 10481 (method_exists($repo,'removeLink')) — the identical latent crash on the remove path. Both fixed with one guard each.

Fix (minimal)

lib/functions/testcase.class.php — null-guard both optional-tracker blocks:

// line 10372 (addAliens)
if ( !is_null($repo) && method_exists($repo,'addLink') ) {
// line 10481 (removeAlien)
if ( !is_null($repo) && method_exists($repo,'removeLink') ) {

method_exists() is only invoked on a real repo object; a tracker-less project (null repo) skips the tracker update but the testcase_aliens INSERT/DELETE still completes. Projects WITH a tracker behave exactly as before. No i18n / locale / rights changes needed.

Verification (regression matrix, all PASS on localhost:8082)

  • Trigger case: addAliens on tracker-less project (tpr=1, target tcv=5) → returns OK, exit 0, testcase_aliens row (1,3,5,1001,1) persisted.
  • Idempotency: re-running the same call keeps exactly 1 row for v5 (existing-alien nuCheck skip path).
  • Existing-alien early return: target tcv=4 (alien already present) → count($dummy) <= 0 guard returns cleanly.
  • Guard placement: grep shows !is_null($repo) at both 10372 and 10481.
  • Hygiene: php -l clean; events table gains no new log_level IN (1,2) Error/Warning rows (only the pre-existing AUDIT=16 LOGIN rows).

Discoveries while testing

  • On the current default branch the API create_version endpoint still dies at testcase::addAliens tpr cannot be 0 (testcase.class.php:10327) because the API never calls setTestProject() on the manager (api/testcases/index.php discards the $checkWrite() return at line 2594). That is a separate bug tracked by issue #1517; the guard fixed here is the prerequisite so that once #1517 lands, the tracker-less alien flow verifies end-to-end. Not touched in this run per one-issue scope.
  • The INSERT (line 10367) runs before the crash point, so alien rows could be persisted despite the fatal — consistent with the issue body note.

Evidence (backend bug — measured log tail)

Reproduction harness /tmp/repro1520.php before fix:

Calling addAliens on tracker-less project (target v5)...
PHP Fatal error:  Uncaught TypeError: method_exists(): Argument #1
($object_or_class) must be of type object|string, null given in
/home/runner/work/testlink-upgraded/testlink-upgraded/lib/functions/testcase.class.php:10372
Stack trace:
#0 .../testcase.class.php(10372): method_exists()
#1 /tmp/repro1520.php(23): testcase->addAliens()
EXIT: 255

After fix:

Calling addAliens on tracker-less project (target v5)...
addAliens returned OK
EXIT: 0

SELECT * FROM testcase_aliens after fix (row for v5 persisted):

testcase_id  tcversion_id  alien_id  relation_type
3            4             1001      1
3            5             1001      1

Commits

  • a587211c2 — fix (testcase.class.php guard + CHANGELOG)
  • a1904a249 — regression suite (tmp/TLU_Test_Cases.md)
  • docs + wiki commit follows.

Clone this wiki locally