Repository navigation
Bugfix Issue 1520 Aliens MethodExists Null Guard
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)
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)
- Fresh-import DB. Fixture: test project 1 (
PRJ,issue_tracker_enabled=0, notestproject_issuetrackerrow), suite node 2, testcase node 3, tcversion 4 (v1) withtestcase_aliens (1,3,4,1001); a second tcversion 5 (v2) with no aliens yet. - Bootstrap the framework exactly like
api/testcases/index.php(config.inc.php+lib/functions/common.php), create thetestcasemanager,setTestProject(1). - Call
addAliens((object){tproject_id:1, tcase_id:3, tcversion_id:5}, [1001], 1). - Before fix: exit 255,
TypeError: method_exists(): Argument #1 ($object_or_class) must be of type object|string, null givenat line 10372. After fix: returns OK, andtestcase_aliensgets 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.
lib/functions/testcase.class.php:10370-10372:
$system = new tlIssueTracker($this->db);
$repo = $system->getInterfaceObject($safeID['tpr']);
if ( method_exists($repo,'addLink') ) { ...Chain:
-
tlIssueTracker::getInterfaceObject($tprojectID)(lib/functions/tlIssueTracker.class.php:670): for a project with no linked issuetracker,getLinkedTo()returnsnulland the method returns$_SESSION['its'][$name](set tonullat line 708). Contract = "null = no tracker". - line 10372 hands that
nulltomethod_exists()as argument #1. PHP 8.x raisesTypeErroron a non-object/non-string first argument; PHP 7.x silently returnedfalse. 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.
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.
-
Trigger case:
addAlienson tracker-less project (tpr=1, target tcv=5) → returns OK, exit 0,testcase_aliensrow(1,3,5,1001,1)persisted. -
Idempotency: re-running the same call keeps exactly 1 row for v5
(existing-alien
nuCheckskip path). -
Existing-alien early return: target tcv=4 (alien already present) →
count($dummy) <= 0guard returns cleanly. -
Guard placement:
grepshows!is_null($repo)at both 10372 and 10481. -
Hygiene:
php -lclean;eventstable gains no newlog_level IN (1,2)Error/Warning rows (only the pre-existing AUDIT=16 LOGIN rows).
- On the current default branch the API
create_versionendpoint still dies attestcase::addAliens tpr cannot be 0(testcase.class.php:10327) because the API never callssetTestProject()on the manager (api/testcases/index.phpdiscards 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.
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
-
a587211c2— fix (testcase.class.php guard + CHANGELOG) -
a1904a249— regression suite (tmp/TLU_Test_Cases.md) - docs + wiki commit follows.