Skip to content

Instantly share code, notes, and snippets.

@SammyK
Forked from nikic/varVar.php
Last active August 1, 2018 16:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SammyK/5d942381e396bba84a49aa3c722cd2c7 to your computer and use it in GitHub Desktop.
Save SammyK/5d942381e396bba84a49aa3c722cd2c7 to your computer and use it in GitHub Desktop.
Script for finding variable-variable usages potentially affected by uniform variable syntax

Find Bugs Related To Uniform Variable Syntax

When upgrading to PHP 7, you might run into really hard-to-debug error messages which might be a result of the uniform variable syntax RFC.

This script (forked from Nikita Popov's gist) will scan a PHP 5 codebase and call out the file and line number of an indirect expression with an offset. The evaluation of these expressions has changed in PHP 7.

Installation

To run this script against a PHP 5 codebase, make sure you have PHP and Composer installed. Then create a directory somewhere and install nikic/php-parser with Composer.

$ mkdir ~/uvs-bug-finder && cd "$_"
$ composer require nikic/php-parser

Then either copy/paste the script in this gist into a PHP file called uvs-bug-finder.php, or just download the raw file directly with PHP:

$ php -r "copy('https://gist.githubusercontent.com/SammyK/5d942381e396bba84a49aa3c722cd2c7/raw/d615884bf7b667a94885b113a253026ab6bf0ed3/uvs-bug-finder.php', 'uvs-bug-finder.php');"

Usage

If the PHP 5 project you want to scan lives at /path/to/project, you can send the directory path to the script as a CLI argument.

$ php uvs-bug-finder.php /path/to/project
/path/to/project/test.php @ 15:
FooBar::$methods[0]();
/path/to/project/test.php @ 18:
var_dump(DateTime::$bar['method_name']());

Fixing The Bugs

The errors can typically be fixed by wrapping the offset-part of the expression with curly brackets. So using the example above that was found in test.php on line 18...

/path/to/project/test.php @ 18:
var_dump(DateTime::$bar['method_name']());

...we simply open up the PHP file, go to line 18 and add curly brackets around the offset part like this...

var_dump(DateTime::{$bar['method_name']}());

And that should fix it! Good luck! :D

<?php
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\ParserFactory;
error_reporting(E_ALL);
ini_set('memory_limit', -1);
if (!isset($argv[1])) {
die("Please provide a directory to scan\n");
}
$dir = $argv[1];
require_once __DIR__ . '/vendor/autoload.php';
class AnalyzingVisitor extends PhpParser\NodeVisitorAbstract {
private $file;
private $code;
public function setFile($file, $code) {
$this->file = realpath($file);
$this->code = $code;
}
public function printNodeContext(Node $node) {
$lineNo = $node->getLine();
$lineCode = explode("\n", $this->code)[$lineNo-1];
echo "$this->file @ $lineNo:\n$lineCode\n";
}
}
class VarVarVisitor extends AnalyzingVisitor {
public function enterNode(Node $node) {
if (!$node instanceof Expr\Variable
&& !$node instanceof Expr\PropertyFetch && !$node instanceof Expr\StaticPropertyFetch
&& !$node instanceof Expr\MethodCall && !$node instanceof Expr\StaticCall
) {
return;
}
if ($node->name instanceof Expr\ArrayDimFetch) {
$this->printNodeContext($node);
}
}
}
$parser = (new ParserFactory)->create(ParserFactory::ONLY_PHP5);
$visitor = new VarVarVisitor;
$traverser = new PhpParser\NodeTraverser;
$traverser->addVisitor($visitor);
foreach (new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir),
RecursiveIteratorIterator::LEAVES_ONLY)
as $file
) {
if (!preg_match('/\.php$/', $file)) continue;
$code = file_get_contents($file);
$visitor->setFile($file, $code);
try {
$traverser->traverse($parser->parse(file_get_contents($file)));
} catch (PhpParser\Error $e) {
echo $file, "\n", $e->getMessage(), "\n";
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment