Exemple #1
0
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;
}
Exemple #2
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;
}