$token) { if (is_string($token)) { if ($token === '{') $depth++; else if ($token === '}') $depth--; continue; } if ($depth !== 0) continue; if (in_array($token[0], $controlFlow, true)) { $found[] = $token[1]; continue; } if ($token[0] !== T_STRING) continue; if (in_array(strtolower($token[1]), $allowed, true)) continue; // Skip the name in a declaration - `function foo(` looks like a call. $isDeclaration = false; for ($j = $i - 1; $j >= 0; $j--) { $prev = $tokens[$j]; if (is_array($prev) and $prev[0] === T_WHITESPACE) continue; $isDeclaration = is_array($prev) and in_array($prev[0], array(T_FUNCTION, T_CLASS, T_INTERFACE, T_TRAIT, T_CONST), true); break; } if ($isDeclaration) continue; for ($j = $i + 1; $j < count($tokens); $j++) { $next = $tokens[$j]; if (is_array($next) and $next[0] === T_WHITESPACE) continue; if ($next === '(') $found[] = $token[1]; break; } } return $found; } $dbPath = __DIR__.'/../../web/includes/database.php'; $source = file_get_contents($dbPath); check('database.php calls nothing at include time', topLevelCalls($dbPath), array()); // Would pass vacuously if the accessors did not exist. check('zmDbConn() is defined', (bool)preg_match('/^function zmDbConn\(\)/m', $source), true); check('zmDbConnOrNull() is defined', (bool)preg_match('/^function zmDbConnOrNull\(\)/m', $source), true); // Every query funnels through dbQuery(), so that is the one that must open the // connection. If it went back to `global $dbConn` it would dereference `false`. check('dbQuery() obtains the connection through the accessor', (bool)preg_match('/function dbQuery\([^)]*\)\s*\{\s*\$dbConn = zmDbConn\(\);/', $source), true); // No caller should reach the raw global any more except the accessors, // dbConnect() itself, and dbDisconnect(). preg_match_all('/function (\w+)\s*\([^)]*\)\s*\{(?:[^{}]|\{[^{}]*\})*?global \$dbConn/', $source, $m); sort($m[1]); check('only the connection plumbing touches the $dbConn global', $m[1], array('dbConnect', 'dbDisconnect', 'zmDbConn', 'zmDbConnOrNull')); // Sanity-check the detector against files that do run code at include time, so // a broken checker cannot silently report success above. The second fixture is // the shape this file actually had before the change - the call nested inside // an if condition - which an earlier version of this check failed to detect. $fixture = tempnam(sys_get_temp_dir(), 'zmtest').'.php'; file_put_contents($fixture, "