Example #1
0
function pass2($file, $namespace, $ast, $current_scope, $parent_node = null, $current_class = null, $current_function = null, $parent_scope = null) : string
{
    global $classes, $functions, $namespace_map, $scope, $tainted_by, $quick_mode;
    static $next_node = 1;
    $vars = [];
    $parent_kind = null;
    if ($parent_node instanceof \ast\Node) {
        $parent_kind = $parent_node->kind;
    }
    if ($ast instanceof \ast\Node) {
        // Infinite Recursion check
        if (empty($ast->id)) {
            $ast->id = $next_node++;
        }
        if (!empty($parent_node)) {
            if (empty($ast->visited_from[$parent_node->id])) {
                $ast->visited_from[$parent_node->id] = 1;
            } else {
                return $namespace;
            }
        }
        switch ($ast->kind) {
            case \ast\AST_NAMESPACE:
                $namespace = (string) $ast->children[0] . '\\';
                break;
            case \ast\AST_USE_TRAIT:
                // We load up the trais in AST_CLASS, this part is just for pretty error messages
                foreach ($ast->children[0]->children as $trait) {
                    $name = $trait->children[0];
                    $lname = strtolower($name);
                    if (!empty($namespace_map[T_CLASS][$file][$lname])) {
                        $name = $namespace_map[T_CLASS][$file][$lname];
                    } else {
                        if ($trait->flags & \ast\flags\NAME_NOT_FQ) {
                            $name = $namespace . $name;
                        }
                    }
                    if (empty($classes[strtolower($name)])) {
                        Log::err(Log::EUNDEF, "Undeclared trait {$trait->children[0]}", $file, $ast->lineno);
                    }
                }
                break;
            case \ast\AST_CLASS:
                $lname = strtolower($namespace . $ast->name);
                if (empty($classes[$lname])) {
                    dump_scope($scope);
                    Log::err(Log::EFATAL, "Can't find class {$namespace}{$ast->name} - aborting", $file, $ast->lineno);
                }
                $current_class = $classes[$lname];
                $traits = $classes[$lname]['traits'];
                // Copy the trait over into this class
                foreach ($traits as $trait) {
                    if (empty($classes[$trait])) {
                        continue;
                    }
                    // TODO: Implement the various trait aliasing mechanisms here
                    $classes[$lname]['properties'] = array_merge($classes[$lname]['properties'], $classes[$trait]['properties']);
                    $classes[$lname]['constants'] = array_merge($classes[$lname]['constants'], $classes[$trait]['constants']);
                    $classes[$lname]['methods'] = array_merge($classes[$lname]['methods'], $classes[$trait]['methods']);
                    // Need the scope as well
                    foreach ($classes[$trait]['methods'] as $k => $method) {
                        if (empty($scope["{$classes[$trait]['name']}::{$method['name']}"])) {
                            continue;
                        }
                        $cs = $namespace . $ast->name . '::' . $method['name'];
                        if (!array_key_exists($cs, $scope)) {
                            $scope[$cs] = [];
                        }
                        if (!array_key_exists('vars', $scope[$cs])) {
                            $scope[$cs]['vars'] = [];
                        }
                        $scope[$cs] = $scope["{$classes[$trait]['name']}::{$method['name']}"];
                        // And finally re-map $this to point to this class
                        $scope[$cs]['vars']['this']['type'] = $namespace . $ast->name;
                    }
                }
                break;
            case \ast\AST_FUNC_DECL:
                if (empty($functions[strtolower($namespace . $ast->name)])) {
                    Log::err(Log::EFATAL, "Can't find function {$namespace}{$ast->name} - aborting", $file, $ast->lineno);
                }
                $current_function = $functions[strtolower($namespace . $ast->name)];
                $parent_scope = $current_scope;
                $current_scope = $namespace . $ast->name;
                break;
            case \ast\AST_CLOSURE:
                $closure_name = '{closure ' . $ast->id . '}';
                $functions[$closure_name] = node_func($file, false, $ast, $closure_name, '');
                $current_function = $closure_name;
                $parent_scope = $current_scope;
                $current_scope = $closure_name;
                if (!empty($scope[$parent_scope]['vars']['this'])) {
                    // TODO: check for a static closure
                    add_var_scope($current_scope, 'this', $scope[$parent_scope]['vars']['this']['type']);
                }
                if (!empty($ast->children[1]) && $ast->children[1]->kind == \ast\AST_CLOSURE_USES) {
                    $uses = $ast->children[1];
                    foreach ($uses->children as $use) {
                        if ($use->kind != \ast\AST_CLOSURE_VAR) {
                            Log::err(Log::EVAR, "You can only have variables in a closure use() clause", $file, $ast->lineno);
                        } else {
                            $name = var_name($use->children[0]);
                            if ($use->flags & \ast\flags\PARAM_REF) {
                                if (empty($parent_scope) || empty($scope[$parent_scope]['vars']) || empty($scope[$parent_scope]['vars'][$name])) {
                                    add_var_scope($parent_scope, $name, '');
                                }
                                $scope[$current_scope]['vars'][$name] =& $scope[$parent_scope]['vars'][$name];
                            } else {
                                if (empty($parent_scope) || empty($scope[$parent_scope]['vars']) || empty($scope[$parent_scope]['vars'][$name])) {
                                    Log::err(Log::EVAR, "Variable \${$name} is not defined", $file, $ast->lineno);
                                } else {
                                    $scope[$current_scope]['vars'][$name] = $scope[$parent_scope]['vars'][$name];
                                }
                            }
                        }
                    }
                }
                break;
            case \ast\AST_METHOD:
                if (empty($current_class['methods'][strtolower($ast->name)])) {
                    Log::err(Log::EFATAL, "Can't find method {$current_class['name']}:{$ast->name} - aborting", $file, $ast->lineno);
                }
                $current_function = $current_class['methods'][strtolower($ast->name)];
                $parent_scope = $current_scope;
                $current_scope = $current_class['name'] . '::' . $ast->name;
                break;
            case \ast\AST_USE:
                // TODO: Figure out a clean way to map namespaces
                break;
            case \ast\AST_FOREACH:
                // Not doing depth-first here, because we need to declare the vars for the body of the loop
                if ($ast->children[2] instanceof \ast\Node && $ast->children[2]->kind == \ast\AST_LIST) {
                    Log::err(Log::EFATAL, "Can't use list() as a key element - aborting", $file, $ast->lineno);
                }
                if ($ast->children[1]->kind == \ast\AST_LIST) {
                    add_var_scope($current_scope, var_name($ast->children[1]->children[0]), '', true);
                    add_var_scope($current_scope, var_name($ast->children[1]->children[1]), '', true);
                } else {
                    // value
                    add_var_scope($current_scope, var_name($ast->children[1]), '', true);
                    // key
                    if (!empty($ast->children[2])) {
                        add_var_scope($current_scope, var_name($ast->children[2]), '', true);
                    }
                }
                break;
            case \ast\AST_CATCH:
                $obj = var_name($ast->children[0]);
                $name = var_name($ast->children[1]);
                if (!empty($name)) {
                    add_var_scope($current_scope, $name, $obj, true);
                }
                break;
        }
        // Depth-First for everything else
        foreach ($ast->children as $child) {
            $namespace = pass2($file, $namespace, $child, $current_scope, $ast, $current_class, $current_function, $parent_scope);
        }
        switch ($ast->kind) {
            case \ast\AST_ASSIGN:
            case \ast\AST_ASSIGN_REF:
                var_assign($file, $namespace, $ast, $current_scope, $current_class, $vars);
                foreach ($vars as $k => $v) {
                    if (empty($v)) {
                        $v = ['type' => '', 'tainted' => false, 'tainted_by' => ''];
                    }
                    if (empty($v['type'])) {
                        $v['type'] = '';
                    }
                    if (strpos($k, '::') === false) {
                        $cs = $current_scope;
                    } else {
                        $cs = 'global';
                    }
                    // Put static properties in the global scope TODO: revisit
                    // Check if we are assigning something to $GLOBALS[key]
                    if ($k == 'GLOBALS' && $ast->children[0]->kind == \ast\AST_DIM) {
                        $temp = $ast;
                        $depth = 0;
                        while ($temp->children[0]->kind == \ast\AST_DIM) {
                            $depth++;
                            $temp = $temp->children[0];
                        }
                        // If the index is a simple scalar, set it in the global scope
                        if (!empty($temp->children[1]) && !$temp->children[1] instanceof \ast\Node) {
                            $cs = 'global';
                            $k = $temp->children[1];
                            if ($depth == 1) {
                                $taint = false;
                                $tainted_by = '';
                                $v['type'] = node_type($file, $namespace, $ast->children[1], $current_scope, $current_class, $taint);
                                $v['tainted'] = $taint;
                                $v['tainted_by'] = $tainted_by;
                            } else {
                                // This is a $GLOBALS['a']['b'] type of assignment
                                // TODO: track array content types
                                $v['type'] = 'array';
                                $v['tainted'] = false;
                                $v['tainted_by'] = '';
                            }
                        }
                    }
                    if ($k == 'GLOBALS') {
                        break;
                    }
                    add_var_scope($cs, $k, $v['type']);
                    $scope[$cs]['vars'][$k]['tainted'] = $v['tainted'];
                    $scope[$cs]['vars'][$k]['tainted_by'] = $v['tainted_by'];
                }
                break;
            case \ast\AST_LIST:
                // TODO: Very simplistic here - we can be smarter
                foreach ($ast->children as $c) {
                    $name = var_name($c);
                    if (!empty($name)) {
                        add_var_scope($current_scope, $name, '');
                    }
                }
                break;
            case \ast\AST_GLOBAL:
                if (!array_key_exists($current_scope, $scope)) {
                    $scope[$current_scope] = [];
                }
                if (!array_key_exists('vars', $scope[$current_scope])) {
                    $scope[$current_scope]['vars'] = [];
                }
                $name = var_name($ast);
                if ($name === false) {
                    break;
                }
                if (!array_key_exists($name, $scope['global']['vars'])) {
                    add_var_scope('global', $name, '');
                }
                $scope[$current_scope]['vars'][$name] =& $scope['global']['vars'][$name];
                break;
            case \ast\AST_FOREACH:
                // check the array, the key,value part was checked on in the non-DPS part above
                $type = node_type($file, $namespace, $ast->children[0], $current_scope, $current_class);
                if (type_scalar($type)) {
                    Log::err(Log::ETYPE, "{$type} passed to foreach instead of array", $file, $ast->lineno);
                }
                break;
            case \ast\AST_STATIC:
                $name = var_name($ast);
                $type = node_type($file, $namespace, $ast->children[1], $current_scope, $current_class, $taint);
                add_var_scope($current_scope, $name, $type);
                $scope[$current_scope]['vars'][$name]['tainted'] = $taint;
                $scope[$current_scope]['vars'][$name]['tainted_by'] = $tainted_by;
                break;
            case \ast\AST_PRINT:
            case \ast\AST_ECHO:
                $taint = false;
                $tainted_by = '';
                $type = node_type($file, $namespace, $ast->children[0], $current_scope, $current_class, $taint);
                if ($type == 'array') {
                    Log::err(Log::ETYPE, "array to string conversion", $file, $ast->lineno);
                }
                if ($taint) {
                    if (empty($tainted_by)) {
                        Log::err(Log::ETAINT, "possibly tainted output.", $file, $ast->lineno);
                    } else {
                        Log::err(Log::ETAINT, "possibly tainted output. Data tainted at {$tainted_by}", $file, $ast->lineno);
                    }
                }
                break;
            case \ast\AST_VAR:
                if ($parent_kind == \ast\AST_STMT_LIST) {
                    Log::err(Log::ENOOP, "no-op variable", $file, $ast->lineno);
                }
                break;
            case \ast\AST_ARRAY:
                if ($parent_kind == \ast\AST_STMT_LIST) {
                    Log::err(Log::ENOOP, "no-op array", $file, $ast->lineno);
                }
                break;
            case \ast\AST_CONST:
                if ($parent_kind == \ast\AST_STMT_LIST) {
                    Log::err(Log::ENOOP, "no-op constant", $file, $ast->lineno);
                }
                break;
            case \ast\AST_CLOSURE:
                if ($parent_kind == \ast\AST_STMT_LIST) {
                    Log::err(Log::ENOOP, "no-op closure", $file, $ast->lineno);
                }
                break;
            case \ast\AST_RETURN:
                // Check if there is a return type on the current function
                if (!empty($current_function['oret'])) {
                    $ret = $ast->children[0];
                    if ($ret instanceof \ast\Node) {
                        if ($ast->children[0]->kind == \ast\AST_ARRAY) {
                            $ret_type = 'array';
                        } else {
                            $ret_type = node_type($file, $namespace, $ret, $current_scope, $current_class);
                        }
                    } else {
                        $ret_type = type_map(gettype($ret));
                        // This is distinct from returning actual NULL which doesn't hit this else since it is an AST_CONST node
                        if ($ret_type == 'NULL') {
                            $ret_type = 'void';
                        }
                    }
                    $check_type = $current_function['oret'];
                    if (strpos("|{$check_type}|", '|self|') !== false) {
                        $check_type = preg_replace("/\\bself\\b/", $current_class['name'], $check_type);
                    }
                    if (strpos("|{$check_type}|", '|static|') !== false) {
                        $check_type = preg_replace("/\\bstatic\\b/", $current_class['name'], $check_type);
                    }
                    if (strpos("|{$check_type}|", '|\\$this|') !== false) {
                        $check_type = preg_replace("/\\b\$this\\b/", $current_class['name'], $check_type);
                    }
                    if (!type_check($ret_type, $check_type, $namespace)) {
                        Log::err(Log::ETYPE, "return {$ret_type} but {$current_function['name']}() is declared to return {$current_function['oret']}", $file, $ast->lineno);
                    }
                } else {
                    $type = node_type($file, $namespace, $ast->children[0], $current_scope, $current_class);
                    if (!empty($functions[$current_scope]['oret'])) {
                        // The function has a return type declared
                        if (!type_check($type, $functions[$current_scope]['oret'], $namespace)) {
                            Log::err(Log::ETYPE, "return {$type} but {$functions[$current_scope]['name']}() is declared to return {$functions[$current_scope]['oret']}", $file, $ast->lineno);
                        }
                    } else {
                        if (strpos($current_scope, '::') !== false) {
                            list($class_name, $method_name) = explode('::', $current_scope, 2);
                            $idx = find_method_class($class_name, $method_name);
                            if ($idx) {
                                $classes[$idx]['methods'][strtolower($method_name)]['ret'] = $type;
                            }
                        } else {
                            if (!empty($functions[$current_scope]['ret'])) {
                                foreach (explode('|', $type) as $t) {
                                    if (!empty($t) && strpos($functions[$current_scope]['ret'], $t) === false) {
                                        $functions[$current_scope]['ret'] = $functions[$current_scope]['ret'] . '|' . $type;
                                    }
                                }
                                $functions[$current_scope]['ret'] = trim($functions[$current_scope]['ret'], '|');
                            } else {
                                if ($current_scope != 'global') {
                                    $functions[$current_scope]['ret'] = $type;
                                }
                            }
                        }
                    }
                }
                break;
            case \ast\AST_CLASS_CONST_DECL:
            case \ast\AST_PROP_DECL:
                // TODO
                break;
            case \ast\AST_CALL:
                $found = false;
                $call = $ast->children[0];
                if ($call->kind == \ast\AST_NAME) {
                    $func_name = $call->children[0];
                    $found = null;
                    if ($call->flags & \ast\flags\NAME_NOT_FQ) {
                        if (!empty($namespace_map[T_FUNCTION][$file][strtolower($namespace . $func_name)])) {
                            $cs = $namespace_map[T_FUNCTION][$file][strtolower($namespace . $func_name)];
                            $found = $functions[strtolower($cs)];
                        } else {
                            if (!empty($namespace_map[T_FUNCTION][$file][strtolower($func_name)])) {
                                $cs = $namespace_map[T_FUNCTION][$file][strtolower($func_name)];
                                $found = $functions[strtolower($cs)];
                            } else {
                                if (!empty($functions[strtolower($namespace . $func_name)])) {
                                    $cs = $namespace . $func_name;
                                    $found = $functions[strtolower($cs)];
                                } else {
                                    if (!empty($functions[strtolower($func_name)])) {
                                        $cs = $func_name;
                                        $found = $functions[strtolower($func_name)];
                                    }
                                }
                            }
                        }
                    } else {
                        if (!empty($functions[strtolower($func_name)])) {
                            $cs = $func_name;
                            $found = $functions[strtolower($func_name)];
                        }
                    }
                    if (!$found) {
                        Log::err(Log::EUNDEF, "call to undefined function {$func_name}()", $file, $ast->lineno);
                    } else {
                        // Ok, the function exists, but are we calling it correctly?
                        if ($found instanceof ReflectionType) {
                            echo "oops at {$file}:{$ast->lineno}\n";
                        }
                        // DEBUG
                        arg_check($file, $namespace, $ast, $func_name, $found, $current_scope, $current_class);
                        if ($found['file'] != 'internal') {
                            // re-check the function's ast with these args
                            if (!$quick_mode) {
                                pass2($found['file'], $found['namespace'], $found['ast'], $found['scope'], $ast, $current_class, $found, $parent_scope);
                            }
                        } else {
                            if (!$found['avail']) {
                                if (!$found) {
                                    Log::err(Log::EAVAIL, "function {$func_name}() is not compiled into this version of PHP", $file, $ast->lineno);
                                }
                            }
                        }
                    }
                } else {
                    if ($call->kind == \ast\AST_VAR) {
                        $name = var_name($call);
                        if ($name instanceof \ast\Node) {
                            // $$var() - Ugh..
                            // TODO - something brilliant here
                        } else {
                            // $var() - hopefully a closure, otherwise we don't know
                            if (array_key_exists($name, $scope[$current_scope]['vars'])) {
                                if (($pos = strpos($scope[$current_scope]['vars'][$name]['type'], '{closure ')) !== false) {
                                    $closure_id = (int) substr($scope[$current_scope]['vars'][$name]['type'], $pos + 9);
                                    $func_name = '{closure ' . $closure_id . '}';
                                    $found = $functions[$func_name];
                                    arg_check($file, $namespace, $ast, $func_name, $found, $current_scope, $current_class);
                                    if (!$quick_mode) {
                                        pass2($found['file'], $found['namespace'], $found['ast'], $found['scope'], $ast, $current_class, $found, $parent_scope);
                                    }
                                }
                            }
                        }
                    }
                }
                break;
            case \ast\AST_NEW:
                $class_name = find_class_name($file, $ast, $namespace, $current_class, $current_scope);
                if ($class_name) {
                    $method_name = '__construct';
                    // No type checking for PHP4-style constructors
                    $method = find_method($class_name, $method_name);
                    if ($method) {
                        // Found a constructor
                        arg_check($file, $namespace, $ast, $method_name, $method, $current_scope, $current_class, $class_name);
                        if ($method['file'] != 'internal') {
                            // re-check the function's ast with these args
                            if (!$quick_mode) {
                                pass2($method['file'], $method['namespace'], $method['ast'], $method['scope'], $ast, $classes[strtolower($class_name)], $method, $parent_scope);
                            }
                        }
                    }
                }
                break;
            case \ast\AST_STATIC_CALL:
                $static_call_ok = false;
                $class_name = find_class_name($file, $ast, $namespace, $current_class, $current_scope, $static_call_ok);
                if ($class_name) {
                    // The class is declared, but does it have the method?
                    $method_name = $ast->children[1];
                    $method = find_method($class_name, $method_name);
                    if (is_array($method) && array_key_exists('avail', $method) && !$method['avail']) {
                        Log::err(Log::EAVAIL, "method {$class_name}::{$method_name}() is not compiled into this version of PHP", $file, $ast->lineno);
                    }
                    if ($method === false) {
                        Log::err(Log::EUNDEF, "static call to undeclared method {$class_name}::{$method_name}()", $file, $ast->lineno);
                    } else {
                        if ($method != 'dynamic') {
                            // Was it declared static?
                            if (!($method['flags'] & \ast\flags\MODIFIER_STATIC)) {
                                if (!$static_call_ok) {
                                    Log::err(Log::ESTATIC, "static call to non-static method {$class_name}::{$method_name}() defined at {$method['file']}:{$method['lineno']}", $file, $ast->lineno);
                                }
                            }
                            arg_check($file, $namespace, $ast, $method_name, $method, $current_scope, $current_class, $class_name);
                            if ($method['file'] != 'internal') {
                                // re-check the function's ast with these args
                                if (!$quick_mode) {
                                    pass2($method['file'], $method['namespace'], $method['ast'], $method['scope'], $ast, $classes[strtolower($class_name)], $method, $parent_scope);
                                }
                            }
                        }
                    }
                }
                break;
            case \ast\AST_METHOD_CALL:
                $class_name = find_class_name($file, $ast, $namespace, $current_class, $current_scope);
                if ($class_name) {
                    $method_name = $ast->children[1];
                    $method = find_method($class_name, $method_name);
                    if ($method === false) {
                        Log::err(Log::EUNDEF, "call to undeclared method {$class_name}->{$method_name}()", $file, $ast->lineno);
                    } else {
                        if ($method != 'dynamic') {
                            if (array_key_exists('avail', $method) && !$method['avail']) {
                                Log::err(Log::EAVAIL, "method {$class_name}::{$method_name}() is not compiled into this version of PHP", $file, $ast->lineno);
                            }
                            arg_check($file, $namespace, $ast, $method_name, $method, $current_scope, $current_class, $class_name);
                            if ($method['file'] != 'internal') {
                                // re-check the function's ast with these args
                                if (!$quick_mode) {
                                    pass2($method['file'], $method['namespace'], $method['ast'], $method['scope'], $ast, $classes[strtolower($class_name)], $method, $parent_scope);
                                }
                            }
                        }
                    }
                }
                break;
        }
    } else {
        if ($parent_kind == \ast\AST_STMT_LIST) {
            if ($ast !== null) {
                Log::err(Log::ENOOP, "(line number not accurate) dangling expression: " . var_export($ast, true), $file, $parent_node->lineno);
            }
        }
    }
    return $namespace;
}
Example #2
0
File: pass1.php Project: nikic/phan
function pass1($file, $namespace, $conditional, $ast, $current_scope, $current_class = null, $current_function = null)
{
    global $classes, $functions, $namespace_map, $summary, $bc_checks;
    $done = false;
    $lc = strtolower((string) $current_class);
    if ($ast instanceof \ast\Node) {
        switch ($ast->kind) {
            case \ast\AST_NAMESPACE:
                $namespace = (string) $ast->children[0] . '\\';
                break;
            case \ast\AST_IF:
                $conditional = true;
                $summary['conditionals']++;
                break;
            case \ast\AST_DIM:
                if ($bc_checks) {
                    if ($ast->children[0] instanceof \ast\Node && $ast->children[0]->children[0] instanceof \ast\Node) {
                        // check for $$var[]
                        if ($ast->children[0]->kind == \ast\AST_VAR && $ast->children[0]->children[0]->kind == \ast\AST_VAR) {
                            $temp = $ast->children[0]->children[0];
                            $depth = 1;
                            while ($temp instanceof \ast\Node) {
                                $temp = $temp->children[0];
                                $depth++;
                            }
                            $dollars = str_repeat('$', $depth);
                            $ftemp = new \SplFileObject($file);
                            $ftemp->seek($ast->lineno - 1);
                            $line = $ftemp->current();
                            unset($ftemp);
                            if (strpos($line, '{') === false || strpos($line, '}') === false) {
                                Log::err(Log::ECOMPAT, "{$dollars}{$temp}[] expression may not be PHP 7 compatible", $file, $ast->lineno);
                            }
                        } else {
                            if (!empty($ast->children[0]->children[1]) && $ast->children[0]->children[1] instanceof \ast\Node && $ast->children[0]->kind == \ast\AST_PROP && $ast->children[0]->children[0]->kind == \ast\AST_VAR && $ast->children[0]->children[1]->kind == \ast\AST_VAR) {
                                $ftemp = new \SplFileObject($file);
                                $ftemp->seek($ast->lineno - 1);
                                $line = $ftemp->current();
                                unset($ftemp);
                                if (strpos($line, '{') === false || strpos($line, '}') === false) {
                                    Log::err(Log::ECOMPAT, "expression may not be PHP 7 compatible", $file, $ast->lineno);
                                }
                            }
                        }
                    }
                }
                break;
            case \ast\AST_USE:
                foreach ($ast->children as $elem) {
                    $target = $elem->children[0];
                    if (empty($elem->children[1])) {
                        if (($pos = strrpos($target, '\\')) !== false) {
                            $alias = substr($target, $pos + 1);
                        } else {
                            $alias = $target;
                        }
                    } else {
                        $alias = $elem->children[1];
                    }
                    $namespace_map[$ast->flags][$file][strtolower($alias)] = $target;
                }
                break;
            case \ast\AST_CLASS:
                if (!empty($classes[strtolower($namespace . $ast->name)])) {
                    for ($i = 1;; $i++) {
                        if (empty($classes[$i . ":" . strtolower($namespace . $ast->name)])) {
                            break;
                        }
                    }
                    $current_class = $i . ":" . $namespace . $ast->name;
                } else {
                    $current_class = $namespace . $ast->name;
                }
                $lc = strtolower($current_class);
                if (!empty($ast->children[0])) {
                    $parent = $ast->children[0]->children[0];
                    if ($ast->children[0]->flags & \ast\flags\NAME_NOT_FQ) {
                        if (($pos = strpos($parent, '\\')) !== false) {
                            // extends A\B
                            // check if we have a namespace alias for A
                            if (!empty($namespace_map[T_CLASS][$file][strtolower(substr($parent, 0, $pos))])) {
                                $parent = $namespace_map[T_CLASS][$file][strtolower(substr($parent, 0, $pos))] . substr($parent, $pos);
                                goto done;
                            }
                        }
                        $parent = $namespace_map[T_CLASS][$file][strtolower($parent)] ?? $namespace . $parent;
                        done:
                    }
                } else {
                    $parent = null;
                }
                $classes[$lc] = ['file' => $file, 'namespace' => $namespace, 'conditional' => $conditional, 'flags' => $ast->flags, 'lineno' => $ast->lineno, 'endLineno' => $ast->endLineno, 'name' => $namespace . $ast->name, 'docComment' => $ast->docComment, 'parent' => $parent, 'pc_called' => true, 'type' => '', 'properties' => [], 'constants' => [], 'traits' => [], 'interfaces' => [], 'methods' => []];
                $classes[$lc]['interfaces'] = array_merge($classes[$lc]['interfaces'], node_namelist($file, $ast->children[1], $namespace));
                $summary['classes']++;
                break;
            case \ast\AST_USE_TRAIT:
                $classes[$lc]['traits'] = array_merge($classes[$lc]['traits'], node_namelist($file, $ast->children[0], $namespace));
                $summary['traits']++;
                break;
            case \ast\AST_METHOD:
                if (!empty($classes[$lc]['methods'][strtolower($ast->name)])) {
                    for ($i = 1;; $i++) {
                        if (empty($classes[$lc]['methods'][$i . ':' . strtolower($ast->name)])) {
                            break;
                        }
                    }
                    $method = $i . ':' . $ast->name;
                } else {
                    $method = $ast->name;
                }
                $classes[$lc]['methods'][strtolower($method)] = node_func($file, $conditional, $ast, "{$current_class}::{$method}", $current_class, $namespace);
                $summary['methods']++;
                $current_function = $method;
                $current_scope = "{$current_class}::{$method}";
                if ($method == '__construct') {
                    $classes[$lc]['pc_called'] = false;
                }
                if ($method == '__invoke') {
                    $classes[$lc]['type'] = merge_type($classes[$lc]['type'], 'callable');
                }
                break;
            case \ast\AST_PROP_DECL:
                if (empty($current_class)) {
                    Log::err(Log::EFATAL, "Invalid property declaration", $file, $ast->lineno);
                }
                $dc = null;
                if (!empty($ast->docComment)) {
                    $dc = parse_doc_comment($ast->docComment);
                }
                foreach ($ast->children as $i => $node) {
                    $classes[$lc]['properties'][$node->children[0]] = ['flags' => $ast->flags, 'name' => $node->children[0], 'lineno' => $node->lineno];
                    $type = node_type($file, $namespace, $node->children[1], $current_scope, empty($classes[$lc]) ? null : $classes[$lc]);
                    if (!empty($dc['vars'][$i]['type'])) {
                        if ($type !== 'null' && !type_check($type, $dc['vars'][$i]['type'])) {
                            Log::err(Log::ETYPE, "property is declared to be {$dc['vars'][$i]['type']} but was assigned {$type}", $file, $node->lineno);
                        }
                        // Set the declarted type to the doc-comment type and add |null if the default value is null
                        $classes[$lc]['properties'][$node->children[0]]['dtype'] = $dc['vars'][$i]['type'] . ($type === 'null' ? '|null' : '');
                        $classes[$lc]['properties'][$node->children[0]]['type'] = $dc['vars'][$i]['type'];
                        if (!empty($type) && $type != $classes[$lc]['properties'][$node->children[0]]['type']) {
                            $classes[$lc]['properties'][$node->children[0]]['type'] = merge_type($classes[$lc]['properties'][$node->children[0]]['type'], strtolower($type));
                        }
                    } else {
                        $classes[$lc]['properties'][$node->children[0]]['dtype'] = '';
                        $classes[$lc]['properties'][$node->children[0]]['type'] = $type;
                    }
                }
                $done = true;
                break;
            case \ast\AST_CLASS_CONST_DECL:
                if (empty($current_class)) {
                    Log::err(Log::EFATAL, "Invalid constant declaration", $file, $ast->lineno);
                }
                foreach ($ast->children as $node) {
                    $classes[$lc]['constants'][$node->children[0]] = ['name' => $node->children[0], 'lineno' => $node->lineno, 'type' => node_type($file, $namespace, $node->children[1], $current_scope, empty($classes[$lc]) ? null : $classes[$lc])];
                }
                $done = true;
                break;
            case \ast\AST_FUNC_DECL:
                if (!empty($functions[strtolower($namespace . $ast->name)])) {
                    for ($i = 1;; $i++) {
                        if (empty($functions[$i . ":" . strtolower($namespace . $ast->name)])) {
                            break;
                        }
                    }
                    $function = $i . ':' . $namespace . $ast->name;
                } else {
                    $function = $namespace . $ast->name;
                }
                $functions[strtolower($function)] = node_func($file, $conditional, $ast, $function, $current_class, $namespace);
                $summary['functions']++;
                $current_function = $function;
                $current_scope = $function;
                // Not $done=true here since nested function declarations are allowed
                break;
            case \ast\AST_CLOSURE:
                $summary['closures']++;
                $current_scope = "{closure}";
                break;
            case \ast\AST_CALL:
                // Looks odd to check for AST_CALL in pass1, but we need to see if a function calls func_get_arg/func_get_args/func_num_args
                $found = false;
                $call = $ast->children[0];
                if ($call->kind == \ast\AST_NAME) {
                    $func_name = strtolower($call->children[0]);
                    if ($func_name == 'func_get_args' || $func_name == 'func_get_arg' || $func_name == 'func_num_args') {
                        if (!empty($current_class)) {
                            $classes[$lc]['methods'][strtolower($current_function)]['optional'] = 999999;
                        } else {
                            $functions[strtolower($current_function)]['optional'] = 999999;
                        }
                    }
                }
                if ($bc_checks) {
                    bc_check($file, $ast);
                }
                break;
            case \ast\AST_STATIC_CALL:
                // Indicate whether a class calls its parent constructor
                $call = $ast->children[0];
                if ($call->kind == \ast\AST_NAME) {
                    $func_name = strtolower($call->children[0]);
                    if ($func_name == 'parent') {
                        $meth = strtolower($ast->children[1]);
                        if ($meth == '__construct') {
                            $classes[strtolower($current_class)]['pc_called'] = true;
                        }
                    }
                }
                break;
            case \ast\AST_RETURN:
            case \ast\AST_PRINT:
            case \ast\AST_ECHO:
            case \ast\AST_STATIC_CALL:
            case \ast\AST_METHOD_CALL:
                if ($bc_checks) {
                    bc_check($file, $ast);
                }
                break;
        }
        if (!$done) {
            foreach ($ast->children as $child) {
                $namespace = pass1($file, $namespace, $conditional, $child, $current_scope, $current_class, $current_function);
            }
        }
    }
    return $namespace;
}
Example #3
0
function pass1($file, $namespace, $conditional, $ast, $current_scope, $current_class = null, $current_function = null)
{
    global $classes, $functions, $namespace_map, $summary;
    $done = false;
    if ($ast instanceof \ast\Node) {
        switch ($ast->kind) {
            case \ast\AST_NAMESPACE:
                $namespace = (string) $ast->children[0] . '\\';
                break;
            case \ast\AST_IF:
                $conditional = true;
                $summary['conditionals']++;
                break;
            case \ast\AST_USE:
                foreach ($ast->children as $elem) {
                    $target = $elem->children[0];
                    if (empty($elem->children[1])) {
                        if (($pos = strrpos($target, '\\')) !== false) {
                            $alias = substr($target, $pos + 1);
                        } else {
                            $alias = $target;
                        }
                    } else {
                        $alias = $elem->children[1];
                    }
                    $namespace_map[$ast->flags][$file][strtolower($alias)] = $target;
                }
                break;
            case \ast\AST_CLASS:
                if (!empty($classes[strtolower($namespace . $ast->name)])) {
                    for ($i = 1;; $i++) {
                        if (empty($classes[$i . ":" . strtolower($namespace . $ast->name)])) {
                            break;
                        }
                    }
                    $current_class = $i . ":" . $namespace . $ast->name;
                } else {
                    $current_class = $namespace . $ast->name;
                }
                if (!empty($ast->children[0])) {
                    $parent = $ast->children[0]->children[0];
                    if ($ast->children[0]->flags & \ast\flags\NAME_NOT_FQ) {
                        if (($pos = strpos($parent, '\\')) !== false) {
                            // extends A\B
                            // check if we have a namespace alias for A
                            if (!empty($namespace_map[T_CLASS][$file][strtolower(substr($parent, 0, $pos))])) {
                                $parent = $namespace_map[T_CLASS][$file][strtolower(substr($parent, 0, $pos))] . substr($parent, $pos);
                                goto done;
                            }
                        }
                        $parent = $namespace_map[T_CLASS][$file][strtolower($parent)] ?? $namespace . $parent;
                        done:
                    }
                } else {
                    $parent = null;
                }
                $classes[strtolower($current_class)] = ['file' => $file, 'namespace' => $namespace, 'conditional' => $conditional, 'flags' => $ast->flags, 'lineno' => $ast->lineno, 'endLineno' => $ast->endLineno, 'name' => $namespace . $ast->name, 'docComment' => $ast->docComment, 'parent' => $parent, 'type' => '', 'properties' => [], 'constants' => [], 'traits' => [], 'interfaces' => [], 'methods' => []];
                $classes[strtolower($current_class)]['interfaces'] = array_merge($classes[strtolower($current_class)]['interfaces'], node_namelist($file, $ast->children[1], $namespace));
                $summary['classes']++;
                break;
            case \ast\AST_USE_TRAIT:
                $classes[strtolower($current_class)]['traits'] = array_merge($classes[strtolower($current_class)]['traits'], node_namelist($file, $ast->children[0], $namespace));
                $summary['traits']++;
                break;
            case \ast\AST_METHOD:
                if (!empty($classes[strtolower($current_class)]['methods'][strtolower($ast->name)])) {
                    for ($i = 1;; $i++) {
                        if (empty($classes[strtolower($current_class)]['methods'][$i . ':' . strtolower($ast->name)])) {
                            break;
                        }
                    }
                    $method = $i . ':' . $ast->name;
                } else {
                    $method = $ast->name;
                }
                $classes[strtolower($current_class)]['methods'][strtolower($method)] = node_func($file, $conditional, $ast, "{$current_class}::{$method}", $current_class, $namespace);
                if (!($classes[strtolower($current_class)]['methods'][strtolower($method)]['flags'] & \ast\flags\MODIFIER_STATIC)) {
                    add_var_scope("{$current_class}::{$method}", 'this', $current_class);
                }
                $summary['methods']++;
                $current_function = $method;
                $current_scope = "{$current_class}::{$method}";
                break;
            case \ast\AST_PROP_DECL:
                if (empty($current_class)) {
                    Log::err(Log::EFATAL, "Invalid property declaration", $file, $ast->lineno);
                }
                foreach ($ast->children as $node) {
                    $classes[strtolower($current_class)]['properties'][$node->children[0]] = ['flags' => $ast->flags, 'name' => $node->children[0], 'lineno' => $node->lineno, 'value' => node_type($file, $namespace, $node->children[1], $current_scope, $current_class)];
                }
                $done = true;
                break;
            case \ast\AST_CLASS_CONST_DECL:
                if (empty($current_class)) {
                    Log::err(Log::EFATAL, "Invalid constant declaration", $file, $ast->lineno);
                }
                foreach ($ast->children as $node) {
                    $classes[strtolower($current_class)]['constants'][$node->children[0]] = ['name' => $node->children[0], 'lineno' => $node->lineno, 'value' => $node->children[1]];
                }
                $done = true;
                break;
            case \ast\AST_FUNC_DECL:
                if (!empty($functions[strtolower($namespace . $ast->name)])) {
                    for ($i = 1;; $i++) {
                        if (empty($functions[$i . ":" . strtolower($namespace . $ast->name)])) {
                            break;
                        }
                    }
                    $function = $i . ':' . $namespace . $ast->name;
                } else {
                    $function = $namespace . $ast->name;
                }
                $functions[strtolower($function)] = node_func($file, $conditional, $ast, $function, $current_class, $namespace);
                $summary['functions']++;
                $current_function = $function;
                $current_scope = $function;
                // Not $done=true here since nested function declarations are allowed
                break;
            case \ast\AST_CLOSURE:
                $summary['closures']++;
                $current_scope = "{closure}";
                break;
            case \ast\AST_CALL:
                // Looks odd to check for AST_CALL in pass1, but we need to see if a function calls func_get_arg/func_get_args/func_num_args
                $found = false;
                $call = $ast->children[0];
                if ($call->kind == \ast\AST_NAME) {
                    $func_name = strtolower($call->children[0]);
                    if ($func_name == 'func_get_args' || $func_name == 'func_get_arg' || $func_name == 'func_num_args') {
                        if (!empty($current_class)) {
                            $classes[strtolower($current_class)]['methods'][strtolower($current_function)]['optional'] = 999999;
                        } else {
                            $functions[strtolower($current_function)]['optional'] = 999999;
                        }
                    }
                }
                break;
        }
        if (!$done) {
            foreach ($ast->children as $child) {
                $namespace = pass1($file, $namespace, $conditional, $child, $current_scope, $current_class, $current_function);
            }
        }
    }
    return $namespace;
}