Skip to content

Incorporate an SCCP check #1145

@rlerdorf

Description

@rlerdorf

Phan's -x (dead code detection) mode is quite simplistic and basically only looks for functions and methods that are never called. Starting with PHP 7.2, but much more so in PHP 7.3 the opcache optimizer does pretty advanced DCE & SCCP. Take this example:

<?php
class C { 
	public $i;
}

function fn(int $x) {
	$c = new C;
	$c->i = 1;
	if($x) {
		$a = [1, 2, 3];
	} else {
		$a = [3, 2, 1];
	}
	return $a[$c->i];
	$c->i++;
	return $x;
}
fn(1);

This is obviously horrible code, but phan -x has no complaints. Not even for the 2nd return in the function. If we run it through the optimizer like this:
php -d opcache.optimization_level=-1 -d opcache.opt_debug_level=0x20020000 -l script.php (using PHP 7.3) we get:

SCCP Values for "fn":
    #4.X3 = {}
    #5.CV1($c) = {}
    #6.CV1($c) = {"i" => int(1)}
    #7.CV2($a) = array(...)
    #8.CV2($a) = array(...)
    #9.CV2($a) = [1 => int(2)]
    #10.X4 = int(1)
    #11.X3 = int(2)

$_main: ; (lines=4, args=0, vars=0, tmps=0)
    ; (after optimizer)
    ; script.php:1-19
L0:     INIT_FCALL 1 96 string("fn")
L1:     SEND_VAL int(1) 1
L2:     DO_UCALL
L3:     RETURN int(1)

fn: ; (lines=2, args=1, vars=1, tmps=0)
    ; (after optimizer)
    ; script.php:6-17
L0:     CV0($x) = RECV 1
L1:     RETURN int(2)
No syntax errors detected in d6

The big block of SCCP values for the fn function tells us that the Sparse Conditional Constant Propogation pass kicked in big time on this code and we see from the final opcodes that the entire fn function was reduced to the equivalent of:

function fn($x) {
    return 2;
}

We should figure out some way to surface this from Phan. The opcache debug output has a range of line numbers on each block of opcodes, but it isn't showing it on a per-opcode basis even though each opcode does have a line number internally. We might need to change that debug output, or at least add an option to make it more verbose so we can provide more specific warnings from Phan on code like this. But since it also rewrites the code (replacing $c->i with 1 in the return call in this case) we can't easily exactly describe what the code was reduced to unless we write some translator to go from opcodes back to PHP code. The easiest is to just indicate that the block of code indicated by the script.php:6-17 range contains dead/redundant code.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementThis improves the quality of Phan's analysis of a codebase

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions