コード例 #1
0
ファイル: util.php プロジェクト: nikic/phan
function check_classes(&$classes, $pc_required = [])
{
    global $namespace_map;
    foreach ($classes as $name => $class) {
        if (strpos($name, ':') !== false) {
            list(, $class_name) = explode(':', $name, 2);
            if ($class['flags'] & \ast\flags\CLASS_INTERFACE) {
                $class_str = "Interface";
            } else {
                if ($class['flags'] & \ast\flags\CLASS_TRAIT) {
                    $class_str = "Trait";
                } else {
                    $class_str = "Class";
                }
            }
            $orig = $classes[strtolower($class_name)];
            if ($orig['flags'] & \ast\flags\CLASS_INTERFACE) {
                $orig_str = "Interface";
            } else {
                if ($orig['flags'] & \ast\flags\CLASS_TRAIT) {
                    $orig_str = "Trait";
                } else {
                    $orig_str = "Class";
                }
            }
            if ($orig['file'] == 'internal') {
                Log::err(Log::EREDEF, "{$class_str} {$class_name} defined at {$class['file']}:{$class['lineno']} was previously defined as {$orig_str} {$class_name} internally", $class['file'], $class['lineno']);
            } else {
                Log::err(Log::EREDEF, "{$class_str} {$class_name} defined at {$class['file']}:{$class['lineno']} was previously defined as {$orig_str} {$class_name} at {$orig['file']}:{$orig['lineno']}", $class['file'], $class['lineno']);
            }
        } else {
            if ($class['file'] !== 'internal') {
                $parents = [];
                $temp = $class;
                while (!empty($temp['parent'])) {
                    if (empty($classes[strtolower($temp['parent'])])) {
                        Log::err(Log::EUNDEF, "Trying to inherit from unknown class {$temp['parent']}", $class['file'], $class['lineno']);
                        break;
                    } else {
                        if ($classes[strtolower($temp['parent'])]['flags'] & \ast\flags\CLASS_FINAL) {
                            Log::err(Log::ETYPE, "{$class['name']} may not inherit from final class {$classes[strtolower($temp['parent'])]['name']}", $class['file'], $class['lineno']);
                        }
                    }
                    $temp = $classes[strtolower($temp['parent'])];
                    $parents[] = $temp['name'];
                    if (!empty($temp['interfaces'])) {
                        $parents = array_merge($parents, $temp['interfaces']);
                    }
                }
                $types = [$class['name']];
                if (!empty($class['interfaces'])) {
                    foreach ($class['interfaces'] as $interface) {
                        if (empty($classes[strtolower($interface)])) {
                            Log::err(Log::EUNDEF, "Trying to implement unknown interface {$interface}", $class['file'], $class['lineno']);
                        } else {
                            if (!($classes[strtolower($interface)]['flags'] & \ast\flags\CLASS_INTERFACE)) {
                                Log::err(Log::ETYPE, "Trying to implement interface {$interface} which is not an interface", $class['file'], $class['lineno']);
                            }
                        }
                    }
                    $types = array_merge($types, $class['interfaces']);
                }
                if (!empty($parents)) {
                    $types = array_merge($types, $parents);
                }
                // Fill in type from inheritance tree and interfaces
                $classes[$name]['type'] = merge_type($classes[$name]['type'], implode('|', array_unique($types)));
                foreach ($pc_required as $c) {
                    if ($class['name'] != $c && in_array($c, $types)) {
                        if (!$class['pc_called']) {
                            Log::err(Log::ETYPE, "{$class['name']} extends {$c} but doesn't call parent::__construct()", $class['file'], $class['lineno']);
                        }
                    }
                }
                foreach ($class['methods'] as $k => $method) {
                    if (!($class['methods'][$k]['flags'] & \ast\flags\MODIFIER_STATIC)) {
                        if ($class['flags'] & \ast\flags\CLASS_TRAIT) {
                            // We don't know the type yet for methods in a trait
                            add_var_scope("{$class['name']}::{$method['name']}", 'this', '');
                        } else {
                            add_var_scope("{$class['name']}::{$method['name']}", 'this', $classes[$name]['type']);
                        }
                    }
                }
            }
        }
    }
}
コード例 #2
0
ファイル: pass2.php プロジェクト: nguyentamvinhlong/phan
function arglist_type_check($file, $namespace, $arglist, $func, $current_scope, $current_class) : array
{
    global $internal_arginfo, $scope, $tainted_by;
    $errs = [];
    $fn = $func['scope'] ?? $func['name'];
    foreach ($arglist->children as $k => $arg) {
        $taint = false;
        $tainted_by = '';
        if (empty($func['params'][$k])) {
            break;
        }
        $param = $func['params'][$k];
        $argno = $k + 1;
        $arg_name = false;
        if ($param['flags'] & \ast\flags\PARAM_REF) {
            if (!$arg instanceof \ast\Node || $arg->kind != \ast\AST_VAR && $arg->kind != \ast\AST_DIM && $arg->kind != \ast\AST_PROP) {
                $errs[] = "Only variables can be passed by reference at arg#{$argno} of {$fn}()";
            } else {
                $arg_name = var_name($arg);
            }
        }
        // For user functions, add the types of the args to the receiving function's scope
        if ($func['file'] != 'internal') {
            if (empty($scope[$fn]['vars'][$param['name']])) {
                $scope[$fn]['vars'][$param['name']] = ['type' => '', 'tainted' => false, 'tainted_by' => ''];
            }
            // If it is by-ref link it back to the local variable name
            if ($param['flags'] & \ast\flags\PARAM_REF) {
                $arg_type = node_type($file, $namespace, $arg, $current_scope, $current_class, $taint, false);
                if (!empty($scope[$current_scope]['vars'][$arg_name])) {
                    $scope[$fn]['vars'][$param['name']] =& $scope[$current_scope]['vars'][$arg_name];
                } else {
                    $scope[$fn]['vars'][$param['name']]['type'] = $arg_type;
                }
            } else {
                $arg_type = node_type($file, $namespace, $arg, $current_scope, $current_class, $taint);
                if (!empty($arg_type)) {
                    add_type($fn, $param['name'], $arg_type);
                }
            }
            if ($taint) {
                $scope[$fn]['vars'][$param['name']]['tainted'] = true;
                $scope[$fn]['vars'][$param['name']]['tainted_by'] = $tainted_by;
            } else {
                $scope[$fn]['vars'][$param['name']]['tainted'] = false;
                $scope[$fn]['vars'][$param['name']]['tainted_by'] = '';
            }
        } else {
            $arg_type = node_type($file, $namespace, $arg, $current_scope, $current_class, $taint, !($param['flags'] & \ast\flags\PARAM_REF));
        }
        // For all functions, add the param to the local scope if pass-by-ref
        // and make it an actual ref for user functions
        if ($param['flags'] & \ast\flags\PARAM_REF) {
            if ($func['file'] == 'internal') {
                if (empty($scope[$current_scope]['vars'][$arg_name])) {
                    add_var_scope($current_scope, $arg_name, $arg_type);
                }
            } else {
                if (empty($scope[$current_scope]['vars'][$arg_name])) {
                    if (!array_key_exists($current_scope, $scope)) {
                        $scope[$current_scope] = [];
                    }
                    if (!array_key_exists('vars', $scope[$current_scope])) {
                        $scope[$current_scope]['vars'] = [];
                    }
                    $scope[$current_scope]['vars'][$arg_name] =& $scope[$fn]['vars'][$param['name']];
                }
            }
        }
        // turn callable:{closure n} into just callable
        if (strpos($arg_type, ':') !== false) {
            list($arg_type, ) = explode(':', $arg_type, 2);
        }
        if (!type_check($arg_type, $param['type'], $namespace)) {
            if (!empty($param['name'])) {
                $paramstr = '(' . trim($param['name'], '&=') . ')';
            } else {
                $paramstr = '';
            }
            if (empty($arg_type)) {
                $arg_type = '';
            }
            if ($func['file'] == 'internal') {
                if (!($param['flags'] & \ast\flags\PARAM_REF)) {
                    $errs[] = "arg#{$argno}{$paramstr} is {$arg_type} but {$func['name']}() takes {$param['type']}";
                }
            } else {
                $errs[] = "arg#{$argno}{$paramstr} is {$arg_type} but {$func['name']}() takes {$param['type']} defined at {$func['file']}:{$func['lineno']}";
            }
        }
    }
    return $errs;
}
コード例 #3
0
ファイル: pass2.php プロジェクト: royopa/phan
function arglist_type_check($file, $namespace, $arglist, $func, $current_scope, $current_class) : array
{
    global $classes, $internal_arginfo, $scope, $tainted_by;
    $errs = [];
    $fn = $func['scope'] ?? $func['name'];
    foreach ($arglist->children as $k => $arg) {
        $taint = false;
        $tainted_by = '';
        if (empty($func['params'][$k])) {
            break;
        }
        $param = $func['params'][$k];
        $argno = $k + 1;
        $arg_name = false;
        if ($param['flags'] & \ast\flags\PARAM_REF) {
            if (!$arg instanceof \ast\Node || $arg->kind != \ast\AST_VAR && $arg->kind != \ast\AST_DIM && $arg->kind != \ast\AST_PROP && $arg->kind != \ast\AST_STATIC_PROP) {
                $errs[] = "Only variables can be passed by reference at arg#{$argno} of {$fn}()";
            } else {
                $arg_name = var_name($arg);
                if ($arg->kind == \ast\AST_STATIC_PROP) {
                    if ($arg_name == 'self' || $arg_name == 'static' || $arg_name == 'parent') {
                        Log::err(Log::ESTATIC, "Using {$arg_name}:: when not in object context", $file, $arg->lineno);
                    }
                }
            }
        }
        // For user functions, add the types of the args to the receiving function's scope
        if ($func['file'] != 'internal') {
            if (empty($scope[$fn]['vars'][$param['name']])) {
                $scope[$fn]['vars'][$param['name']] = ['type' => '', 'tainted' => false, 'tainted_by' => ''];
            }
            // If it is by-ref link it back to the local variable name
            if ($param['flags'] & \ast\flags\PARAM_REF) {
                $arg_type = node_type($file, $namespace, $arg, $current_scope, $current_class, $taint, false);
                if ($arg->kind == \ast\AST_STATIC_PROP && $arg->children[0]->kind == \ast\AST_NAME) {
                    $class_name = $arg->children[0]->children[0];
                    if ($class_name == 'self' || $class_name == 'static' || $class_name == 'parent') {
                        if ($current_class) {
                            if ($class_name == 'static') {
                                $class_name = $current_class['name'];
                            }
                            if ($class_name == 'self') {
                                if ($current_scope != 'global') {
                                    list($class_name, ) = explode('::', $current_scope);
                                } else {
                                    $class_name = $current_class['name'];
                                }
                            } else {
                                if ($class_name == 'parent') {
                                    $class_name = $current_class['parent'];
                                }
                            }
                            $static_call_ok = true;
                        } else {
                            $class_name = '';
                        }
                    } else {
                        $class_name = qualified_name($file, $arg->children[0], $namespace);
                    }
                    if ($class_name) {
                        if (!$arg->children[1] instanceof \ast\Node) {
                            if (empty($classes[strtolower($class_name)]['properties'][$arg->children[1]])) {
                                Log::err(Log::ESTATIC, "Access to undeclared static property: {$class_name}::\${$arg->children[1]}", $file, $arg->lineno);
                            } else {
                                $scope[$fn]['vars'][$param['name']] =& $classes[strtolower($class_name)]['properties'][$arg->children[1]];
                            }
                        }
                    }
                } else {
                    if (!empty($scope[$current_scope]['vars'][$arg_name])) {
                        $scope[$fn]['vars'][$param['name']] =& $scope[$current_scope]['vars'][$arg_name];
                    } else {
                        $scope[$fn]['vars'][$param['name']]['type'] = $arg_type;
                    }
                }
            } else {
                $arg_type = node_type($file, $namespace, $arg, $current_scope, $current_class, $taint);
                if (!empty($arg_type)) {
                    add_type($fn, $param['name'], $arg_type);
                }
            }
            if ($taint) {
                $scope[$fn]['vars'][$param['name']]['tainted'] = true;
                $scope[$fn]['vars'][$param['name']]['tainted_by'] = $tainted_by;
            } else {
                $scope[$fn]['vars'][$param['name']]['tainted'] = false;
                $scope[$fn]['vars'][$param['name']]['tainted_by'] = '';
            }
        } else {
            $arg_type = node_type($file, $namespace, $arg, $current_scope, $current_class, $taint, !($param['flags'] & \ast\flags\PARAM_REF));
        }
        // For all functions, add the param to the local scope if pass-by-ref
        // and make it an actual ref for user functions
        if ($param['flags'] & \ast\flags\PARAM_REF) {
            if ($func['file'] == 'internal') {
                if (empty($scope[$current_scope]['vars'][$arg_name])) {
                    add_var_scope($current_scope, $arg_name, $arg_type);
                }
            } else {
                if (empty($scope[$current_scope]['vars'][$arg_name])) {
                    if (!array_key_exists($current_scope, $scope)) {
                        $scope[$current_scope] = [];
                    }
                    if (!array_key_exists('vars', $scope[$current_scope])) {
                        $scope[$current_scope]['vars'] = [];
                    }
                    $scope[$current_scope]['vars'][$arg_name] =& $scope[$fn]['vars'][$param['name']];
                }
            }
        }
        // turn callable:{closure n} into just callable
        if (strpos($arg_type, ':') !== false) {
            list($arg_type, ) = explode(':', $arg_type, 2);
        }
        // if we have a single non-native type, expand it
        if (!empty($arg_type) && !is_native_type($arg_type)) {
            if (!empty($classes[strtolower($arg_type)]['type'])) {
                $arg_type = $classes[strtolower($arg_type)]['type'];
            }
        }
        if (!type_check(all_types($arg_type), all_types($param['type']), $namespace)) {
            if (!empty($param['name'])) {
                $paramstr = '(' . trim($param['name'], '&=') . ')';
            } else {
                $paramstr = '';
            }
            if (empty($arg_type)) {
                $arg_type = '';
            }
            if ($func['file'] == 'internal') {
                if (!($param['flags'] & \ast\flags\PARAM_REF)) {
                    $errs[] = "arg#{$argno}{$paramstr} is {$arg_type} but {$func['name']}() takes {$param['type']}";
                }
            } else {
                $errs[] = "arg#{$argno}{$paramstr} is {$arg_type} but {$func['name']}() takes {$param['type']} defined at {$func['file']}:{$func['lineno']}";
            }
        }
    }
    return $errs;
}
コード例 #4
0
ファイル: pass1.php プロジェクト: nguyentamvinhlong/phan
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;
}
コード例 #5
0
ファイル: pass1.php プロジェクト: royopa/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 ($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, '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);
                if (!($classes[$lc]['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);
                }
                $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);
                        }
                        $classes[$lc]['properties'][$node->children[0]]['dtype'] = $dc['vars'][$i]['type'];
                        $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'] .= '|' . 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) {
                    if ($ast->children[0] instanceof \ast\Node && $ast->children[0]->children[0] instanceof \ast\Node && $ast->children[0]->children[0]->children[0] instanceof \ast\Node) {
                        // $foo->$bar['baz']()
                        if ($ast->children[0]->kind == \ast\AST_DIM && $ast->children[0]->children[0]->kind == \ast\AST_PROP && $ast->children[0]->children[0]->children[0]->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);
                            }
                        }
                        // Foo::$bar['baz']()
                        if ($ast->children[0]->kind == \ast\AST_DIM && $ast->children[0]->children[0]->kind == \ast\AST_STATIC_PROP && $ast->children[0]->children[0]->children[0]->kind == \ast\AST_NAME) {
                            $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;
        }
        if (!$done) {
            foreach ($ast->children as $child) {
                $namespace = pass1($file, $namespace, $conditional, $child, $current_scope, $current_class, $current_function);
            }
        }
    }
    return $namespace;
}
コード例 #6
0
ファイル: pass1.php プロジェクト: yut148/phan
function node_func($file, $conditional, $node, $current_scope, $current_class, $namespace = '')
{
    global $scope, $classes;
    if ($node instanceof \ast\Node) {
        $req = $opt = 0;
        $dc = ['return' => '', 'params' => []];
        if (!empty($node->docComment)) {
            $dc = parse_doc_comment($node->docComment);
        }
        $result = ['file' => $file, 'namespace' => $namespace, 'scope' => $current_scope, 'conditional' => $conditional, 'flags' => $node->flags, 'lineno' => $node->lineno, 'endLineno' => $node->endLineno, 'name' => strpos($current_scope, '::') === false ? $namespace . $node->name : $node->name, 'docComment' => $node->docComment, 'params' => node_paramlist($file, $node->children[0], $req, $opt, $dc, $namespace), 'required' => $req, 'optional' => $opt, 'ret' => '', 'oret' => '', 'ast' => $node->children[2]];
        if (!empty($dc['deprecated'])) {
            $result['deprecated'] = true;
        }
        if ($node->children[3] !== null) {
            $result['oret'] = ast_node_type($file, $node->children[3], $namespace);
            // Original return type
            $result['ret'] = ast_node_type($file, $node->children[3], $namespace);
            // This one changes as we walk the tree
        } else {
            // Check if the docComment has a return value specified
            if (!empty($dc['return'])) {
                // We can't actually figure out 'static' at this point, but fill it in regardless. It will be partially correct
                if ($dc['return'] == 'static' || $dc['return'] == 'self' || $dc['return'] == '$this') {
                    if (strpos($current_scope, '::') !== false) {
                        list($dc['return'], ) = explode('::', $current_scope);
                    }
                }
                $result['oret'] = $dc['return'];
                $result['ret'] = $dc['return'];
            }
        }
        // Add params to local scope for user functions
        if ($file != 'internal') {
            $i = 1;
            foreach ($result['params'] as $k => $v) {
                if (empty($v['type'])) {
                    // If there is no type specified in PHP, check for a docComment
                    // We assume order in the docComment matches the parameter order in the code
                    if (!empty($dc['params'][$k]['type'])) {
                        $scope[$current_scope]['vars'][$v['name']] = ['type' => $dc['params'][$k]['type'], 'tainted' => false, 'tainted_by' => '', 'param' => $i];
                    } else {
                        $scope[$current_scope]['vars'][$v['name']] = ['type' => '', 'tainted' => false, 'tainted_by' => '', 'param' => $i];
                    }
                } else {
                    $scope[$current_scope]['vars'][$v['name']] = ['type' => $v['type'], 'tainted' => false, 'tainted_by' => '', 'param' => $i];
                }
                if (array_key_exists('def', $v)) {
                    $type = node_type($file, $namespace, $v['def'], $current_scope, empty($current_class) ? null : $classes[strtolower($current_class)]);
                    if ($scope[$current_scope]['vars'][$v['name']]['type'] !== '') {
                        // Does the default value match the declared type?
                        if ($type !== 'null' && !type_check($type, $scope[$current_scope]['vars'][$v['name']]['type'])) {
                            Log::err(Log::ETYPE, "Default value for {$scope[$current_scope]['vars'][$v['name']]['type']} \${$v['name']} can't be {$type}", $file, $node->lineno);
                        }
                    }
                    add_type($current_scope, $v['name'], strtolower($type));
                    // If we have no other type info about a parameter, just because it has a default value of null
                    // doesn't mean that is its type. Any type can default to null
                    if ($type === 'null' && !empty($result['params'][$k]['type'])) {
                        $result['params'][$k]['type'] = merge_type($result['params'][$k]['type'], strtolower($type));
                    }
                }
                $i++;
            }
            if (!empty($dc['vars'])) {
                foreach ($dc['vars'] as $var) {
                    add_var_scope($current_scope, $var['name'], $var['type']);
                }
            }
        }
        return $result;
    }
    assert(false, "{$node} was not an \\ast\\Node");
}