Exemplo n.º 1
0
Arquivo: util.php Projeto: 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']);
                        }
                    }
                }
            }
        }
    }
}
Exemplo n.º 2
0
Arquivo: pass1.php Projeto: nikic/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) {
                    if (empty($scope[$current_scope]['vars'][$var['name']])) {
                        $scope[$current_scope]['vars'][$var['name']] = ['type' => $var['type'], 'tainted' => false, 'tainted_by' => ''];
                    } else {
                        add_type($current_scope, $var['name'], $var['type']);
                    }
                }
            }
        }
        return $result;
    }
    assert(false, "{$node} was not an \\ast\\Node");
}
Exemplo n.º 3
0
function var_assign($file, $namespace, $ast, $current_scope, $current_class, &$vars)
{
    global $classes, $functions, $scope;
    $left = $ast->children[0];
    $right = $ast->children[1];
    $left_type = $right_type = null;
    $parent = $ast;
    $taint = false;
    // Deal with $a=$b=$c=1; and trickle the right-most value to the top through recursion
    if ($right instanceof \ast\Node && $right->kind == \ast\AST_ASSIGN) {
        $right_type = var_assign($file, $namespace, $right, $current_scope, $current_class, $vars);
    }
    if ($left instanceof \ast\Node && $left->kind != \ast\AST_VAR && $left->kind != \ast\AST_STATIC_PROP) {
        // Walk multi-level arrays and chained stuff
        // eg. $var->prop[1][2]->prop
        while ($left instanceof \ast\Node && $left->kind != \ast\AST_VAR) {
            $parent = $left;
            $left = $left->children[0];
        }
    }
    if ($left == null) {
        // No variable name for assignment??
        // This is generally for something like list(,$var) = [1,2]
        return $right_type;
    }
    if (!is_object($left)) {
        if ($left == "self") {
            // TODO: Looks like a self::$var assignment - do something smart here
            return $right_type;
        } else {
            if ($left == 'static') {
                // TODO: static::$prop assignment
                return $right_type;
            } else {
                return $right_type;
            }
        }
    } else {
        if ($left->kind == \ast\AST_LIST) {
            return '';
        }
    }
    // DEBUG
    if (!$left instanceof \ast\Node) {
        echo "Check this {$file}\n" . ast_dump($left) . "\n" . ast_dump($parent) . "\n";
    }
    if ($left->kind == \ast\AST_STATIC_PROP && $left->children[0]->kind == \ast\AST_NAME) {
        if ($left->children[1] instanceof \ast\Node) {
            // This is some sort of self::${$key}  thing, give up
            return $right_type;
        }
        if ($left->children[0]->flags & \ast\flags\NAME_NOT_FQ) {
            $left_name = $namespace . $left->children[0]->children[0] . '::' . $left->children[1];
        } else {
            $left_name = $left->children[0]->children[0] . '::' . $left->children[1];
        }
    } else {
        $left_name = $left->children[0];
    }
    if ($right instanceof \ast\Node && $right->kind == \ast\AST_CLOSURE) {
        $right_type = 'callable:{closure ' . $right->id . '}';
        $vars[$left_name]['type'] = $right_type;
        $vars[$left_name]['tainted'] = false;
        $vars[$left_name]['tainted_by'] = '';
        return $right_type;
    }
    if (!$left_type && $right_type) {
        $left_type = $right_type;
    } else {
        if (!$left_type) {
            // We didn't figure out the type simply by looking at the left side of the assignment, check the right
            $right_type = node_type($file, $namespace, $right, $current_scope, $current_class, $taint);
            $left_type = $right_type;
        }
    }
    if ($parent->kind == \ast\AST_DIM && $left->kind == \ast\AST_VAR) {
        // Generics check - can't really do this without some special hint forcing a strict generics type
        /*
        if(!($left->children[0] instanceof \ast\Node)) {
        	if($right_type === "NULL") return ''; // You can assign null to any generic
        	$var_type = $scope[$current_scope]['vars'][$left->children[0]]['type'] ?? '';
        	if(!empty($var_type) && !nongenerics($var_type) && strpos($var_type, '[]') !== false) {
        		if(!type_check($right_type, generics($var_type))) {
        			Log::err(Log::ETYPE, "Assigning {$right_type} to \${$left->children[0]} which is {$var_type}", $file, $ast->lineno);
        			return '';
        		}
        	} else  {
        		$left_type = mkgenerics($right_type);
        	}
        }
        */
        $left_type = mkgenerics($right_type);
    }
    // $var->prop = ...
    if ($parent->kind == \ast\AST_PROP && $left->kind == \ast\AST_VAR) {
        // Check for $$var-> weirdness
        if (!$left->children[0] instanceof \ast\Node) {
            $prop = $parent->children[1];
            // Check for $var->$...
            if (!$prop instanceof \ast\Node) {
                $lclass = '';
                if ($left->children[0] == 'this') {
                    // $this->prop =
                    $class_name = $current_class['name'];
                } else {
                    $class_name = find_class_name($file, $parent, $namespace, $current_class, $current_scope);
                }
                $lclass = strtolower($class_name);
                if (empty($lclass) || empty($classes[$lclass])) {
                    return '';
                }
                $ltemp = find_property($file, $ast, $class_name, $prop, $current_class);
                if ($ltemp === false) {
                    return $right_type;
                }
                if (!empty($ltemp)) {
                    $lclass = $ltemp;
                }
                if (empty($classes[$lclass]['properties'][$prop])) {
                    $classes[$lclass]['properties'][$prop] = ['flags' => \ast\flags\MODIFIER_PUBLIC, 'name' => $prop, 'lineno' => 0, 'type' => $right_type];
                } else {
                    if (!empty($classes[$lclass]['properties'][$prop]['dtype'])) {
                        if ($ast->children[0]->kind == \ast\AST_DIM) {
                            $right_type = mkgenerics($right_type);
                        }
                        if (!type_check(all_types($right_type), all_types($classes[$lclass]['properties'][$prop]['dtype']))) {
                            Log::err(Log::ETYPE, "property is declared to be {$classes[$lclass]['properties'][$prop]['dtype']} but was assigned {$right_type}", $file, $ast->lineno);
                        }
                    }
                    $classes[$lclass]['properties'][$prop]['type'] = merge_type($classes[$lclass]['properties'][$prop]['type'], $right_type);
                }
                return $right_type;
            }
        }
    }
    if ($left_name instanceof \ast\Node) {
        // TODO: Deal with $$var
    } else {
        $vars[$left_name]['type'] = $left_type ?? '';
        $vars[$left_name]['tainted'] = $taint;
        $vars[$left_name]['tainted_by'] = $taint ? "{$file}:{$left->lineno}" : '';
    }
    return $right_type;
}
Exemplo n.º 4
0
function var_assign($file, $namespace, $ast, $current_scope, $current_class, &$vars)
{
    global $classes, $functions, $scope;
    $left = $ast->children[0];
    $right = $ast->children[1];
    $left_type = $right_type = null;
    $parent = $ast;
    $taint = false;
    // Deal with $a=$b=$c=1; and trickle the right-most value to the top through recursion
    if ($right instanceof \ast\Node && $right->kind == \ast\AST_ASSIGN) {
        $right_type = var_assign($file, $namespace, $right, $current_scope, $current_class, $vars);
    }
    if ($left instanceof \ast\Node && $left->kind != \ast\AST_VAR && $left->kind != \ast\AST_STATIC_PROP) {
        // Walk multi-level arrays and chained stuff
        // eg. $var->prop[1][2]->prop
        while ($left instanceof \ast\Node && $left->kind != \ast\AST_VAR) {
            $parent = $left;
            $left = $left->children[0];
        }
        // If we see $var[..] then we know $var is an array
        #		if($parent->kind == \ast\AST_DIM) $left_type = 'array'; // TODO: unless it was already a string, then it is a string offset
        #		else if($parent->kind == \ast\AST_LIST) $left_type = ''; // TODO: Properly handle list($a) = [$b]
        #		else if($parent->kind == \ast\AST_PROP) { $left_type = "object"; }
    }
    if ($left == null) {
        // No variable name for assignment??
        // This is generally for something like list(,$var) = [1,2]
        return $right_type;
    }
    if (!is_object($left)) {
        if ($left == "self") {
            // TODO: Looks like a self::$var assignment - do something smart here
            return $right_type;
        } else {
            if ($left == 'static') {
                // TODO: static::$prop assignment
                return $right_type;
            } else {
                return $right_type;
            }
        }
    }
    // DEBUG
    if (!$left instanceof \ast\Node) {
        echo "Check this {$file}\n" . ast_dump($left) . "\n" . ast_dump($parent) . "\n";
    }
    if ($left->kind == \ast\AST_STATIC_PROP && $left->children[0]->kind == \ast\AST_NAME) {
        if ($left->children[1] instanceof \ast\Node) {
            // This is some sort of self::${$key}  thing, give up
            return $right_type;
        }
        if ($left->children[0]->flags & \ast\flags\NAME_NOT_FQ) {
            $left_name = $namespace . $left->children[0]->children[0] . '::' . $left->children[1];
        } else {
            $left_name = $left->children[0]->children[0] . '::' . $left->children[1];
        }
    } else {
        $left_name = $left->children[0];
    }
    if ($right instanceof \ast\Node && $right->kind == \ast\AST_CLOSURE) {
        $right_type = 'callable:{closure ' . $right->id . '}';
        $vars[$left_name]['type'] = $right_type;
        $vars[$left_name]['tainted'] = false;
        $vars[$left_name]['tainted_by'] = '';
        return $right_type;
    }
    if (!$left_type && $right_type) {
        $left_type = $right_type;
    } else {
        if (!$left_type) {
            // We didn't figure out the type simply by looking at the left side of the assignment, check the right
            $right_type = node_type($file, $namespace, $right, $current_scope, $current_class, $taint);
            $left_type = $right_type;
        }
    }
    // $var->prop = ...
    if ($parent->kind == \ast\AST_PROP && $left->kind == \ast\AST_VAR) {
        // Check for $$var-> weirdness
        if (!$left->children[0] instanceof \ast\Node) {
            $prop = $parent->children[1];
            // Check for $var->$...
            if (!$prop instanceof \ast\Node) {
                if ($left->children[0] == 'this') {
                    // $this->prop =
                    $lclass = strtolower($current_class['name']);
                    if (empty($classes[$lclass]['properties'][$prop])) {
                        $classes[$lclass]['properties'][$prop] = ['flags' => \ast\flags\MODIFIER_PUBLIC, 'name' => $prop, 'lineno' => 0, 'value' => $right_type];
                    } else {
                        $classes[$lclass]['properties'][$prop]['value'] = merge_type($classes[$lclass]['properties'][$prop]['value'], $right_type);
                    }
                    return $right_type;
                } else {
                    // $var->prop =
                    $temp = node_type($file, $namespace, $left, $current_scope, $current_class, $taint);
                    if (!is_native_type($temp)) {
                        $lclass = strtolower($temp);
                        if (!empty($classes[$lclass])) {
                            if (empty($classes[$lclass]['properties'][$prop])) {
                                $classes[$lclass]['properties'][$prop] = ['flags' => \ast\flags\MODIFIER_PUBLIC, 'name' => $prop, 'lineno' => 0, 'value' => $right_type];
                            } else {
                                $classes[$lclass]['properties'][$prop]['value'] = merge_type($classes[$lclass]['properties'][$prop]['value'], $right_type);
                            }
                            return $right_type;
                        }
                    }
                }
            }
        }
    }
    if ($left_name instanceof \ast\Node) {
        // TODO: Deal with $$var
    } else {
        $vars[$left_name]['type'] = $left_type ?? '';
        $vars[$left_name]['tainted'] = $taint;
        $vars[$left_name]['tainted_by'] = $taint ? "{$file}:{$left->lineno}" : '';
    }
    return $right_type;
}
Exemplo n.º 5
0
function add_var_scope(string $cs, string $name, string $type, $replace_type = false)
{
    global $scope;
    if (empty($name)) {
        return;
    }
    if (!array_key_exists($cs, $scope)) {
        $scope[$cs] = [];
    }
    if (!array_key_exists('vars', $scope[$cs])) {
        $scope[$cs]['vars'] = [];
    }
    if (array_key_exists($name, $scope[$cs]['vars'])) {
        if ($replace_type) {
            $scope[$cs]['vars'][$name]['type'] = $type;
        } else {
            // If we previously have seen a specific type for this variable and now get a ''
            // which means we don't know a type, we at least know it can be something else, so
            // add in a 'mixed' here
            if ($type === '' && !empty($scope[$cs]['vars'][$name]['type'])) {
                $type = 'mixed';
            }
            $scope[$cs]['vars'][$name]['type'] = merge_type($scope[$cs]['vars'][$name]['type'], $type);
        }
    } else {
        $scope[$cs]['vars'][$name] = ['type' => $type, 'tainted' => false, 'tainted_by' => ''];
    }
}