function expand_interfaces($interfaces, $new) { global $classes; $interfaces = array_merge($interfaces, $new); foreach ($new as $interface) { if (empty($classes[strtolower($interface)])) { continue; } $node = $classes[strtolower($interface)]; if (!empty($node['interfaces'])) { $interfaces = expand_interfaces($interfaces, $node['interfaces']); } } return $interfaces; }
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'] = expand_interfaces($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) { if (!empty($node->docComment)) { $dc = parse_doc_comment($node->docComment); } $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; }