Example #1
0
function node_type($file, $namespace, $node, $current_scope, $current_class, &$taint = null, $check_var_exists = true)
{
    global $classes, $functions, $scope, $namespace_map, $internal_arginfo;
    if (!$node instanceof \ast\Node) {
        if ($node === null) {
            return '';
        }
        return type_map(gettype($node));
    } else {
        if ($node->kind == \ast\AST_ARRAY) {
            return 'array';
        } else {
            if ($node->kind == \ast\AST_BINARY_OP) {
                $taint = var_taint_check($file, $node, $current_scope);
                switch ($node->flags) {
                    // Always a string from a concat
                    case \ast\flags\BINARY_CONCAT:
                        $temp_taint = false;
                        node_type($file, $namespace, $node->children[0], $current_scope, $current_class, $temp_taint);
                        if ($temp_taint) {
                            $taint = true;
                            return 'string';
                        }
                        node_type($file, $namespace, $node->children[1], $current_scope, $current_class, $temp_taint);
                        if ($temp_taint) {
                            $taint = true;
                        }
                        return 'string';
                        break;
                        // Boolean unless invalid operands
                    // Boolean unless invalid operands
                    case \ast\flags\BINARY_IS_IDENTICAL:
                    case \ast\flags\BINARY_IS_NOT_IDENTICAL:
                    case \ast\flags\BINARY_IS_EQUAL:
                    case \ast\flags\BINARY_IS_NOT_EQUAL:
                    case \ast\flags\BINARY_IS_SMALLER:
                    case \ast\flags\BINARY_IS_SMALLER_OR_EQUAL:
                        $taint = false;
                        return 'bool';
                        break;
                        // Add is special because you can add arrays
                    // Add is special because you can add arrays
                    case \ast\flags\BINARY_ADD:
                        $temp = node_type($file, $namespace, $node->children[0], $current_scope, $current_class);
                        if (!$temp) {
                            $left = '';
                        } else {
                            $left = type_map($temp);
                        }
                        $temp = node_type($file, $namespace, $node->children[1], $current_scope, $current_class);
                        if (!$temp) {
                            $right = '';
                        } else {
                            $right = type_map($temp);
                        }
                        if ($left == 'array' && $right == 'array') {
                            return 'array';
                        } else {
                            if ($left == 'array' && !type_check($right, 'array')) {
                                Log::err(Log::ETYPE, "invalid operator: left operand is array and right is not", $file, $node->lineno);
                                return '';
                            } else {
                                if ($right == 'array' && !type_check($left, 'array')) {
                                    Log::err(Log::ETYPE, "invalid operator: right operand is array and left is not", $file, $node->lineno);
                                    return '';
                                } else {
                                    if ($left == 'int' && $right == 'int') {
                                        return 'int';
                                    } else {
                                        if ($left == 'float' || $right == 'float') {
                                            return 'float';
                                        } else {
                                            if ($left == 'array' || $right == 'array') {
                                                // If it is a '+' and we know one side is an array and the other is unknown, assume array
                                                return 'array';
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        return 'int|float';
                        $taint = false;
                        break;
                        // Everything else should be an int/float
                    // Everything else should be an int/float
                    default:
                        $temp = node_type($file, $namespace, $node->children[0], $current_scope, $current_class);
                        if (!$temp) {
                            $left = '';
                        } else {
                            $left = type_map($temp);
                        }
                        $temp = node_type($file, $namespace, $node->children[1], $current_scope, $current_class);
                        if (!$temp) {
                            $right = '';
                        } else {
                            $right = type_map($temp);
                        }
                        if ($left == 'array' || $right == 'array') {
                            Log::err(Log::ETYPE, "invalid array operator", $file, $node->lineno);
                            return '';
                        } else {
                            if ($left == 'int' && $right == 'int') {
                                return 'int';
                            } else {
                                if ($left == 'float' || $right == 'float') {
                                    return 'float';
                                }
                            }
                        }
                        return 'int|float';
                        $taint = false;
                        break;
                }
            } else {
                if ($node->kind == \ast\AST_CAST) {
                    $taint = var_taint_check($file, $node->children[0], $current_scope);
                    switch ($node->flags) {
                        case \ast\flags\TYPE_NULL:
                            return 'null';
                            break;
                        case \ast\flags\TYPE_BOOL:
                            $taint = false;
                            return 'bool';
                            break;
                        case \ast\flags\TYPE_LONG:
                            $taint = false;
                            return 'int';
                            break;
                        case \ast\flags\TYPE_DOUBLE:
                            $taint = false;
                            return 'float';
                            break;
                        case \ast\flags\TYPE_STRING:
                            return 'string';
                            break;
                        case \ast\flags\TYPE_ARRAY:
                            return 'array';
                            break;
                        case \ast\flags\TYPE_OBJECT:
                            return 'stdClass';
                            break;
                        default:
                            Log::err(Log::EFATAL, "Unknown type (" . $node->flags . ") in cast");
                    }
                } else {
                    if ($node->kind == \ast\AST_NEW) {
                        $class_name = find_class_name($file, $node, $namespace, $current_class, $current_scope);
                        if ($class_name) {
                            return $classes[strtolower($class_name)]['type'];
                        }
                        return 'object';
                    } else {
                        if ($node->kind == \ast\AST_DIM) {
                            $taint = var_taint_check($file, $node->children[0], $current_scope);
                            // TODO: Do something smart with array elements
                            return '';
                        } else {
                            if ($node->kind == \ast\AST_VAR) {
                                return var_type($file, $node, $current_scope, $taint, $check_var_exists);
                            } else {
                                if ($node->kind == \ast\AST_ENCAPS_LIST) {
                                    foreach ($node->children as $encap) {
                                        if ($encap instanceof \ast\Node) {
                                            if (var_taint_check($file, $encap, $current_scope)) {
                                                $taint = true;
                                            }
                                        }
                                    }
                                    return "string";
                                } else {
                                    if ($node->kind == \ast\AST_CONST) {
                                        if ($node->children[0]->kind == \ast\AST_NAME) {
                                            if (defined($node->children[0]->children[0])) {
                                                return type_map(gettype(constant($node->children[0]->children[0])));
                                            } else {
                                                // Todo: user-defined constant
                                            }
                                        }
                                    } else {
                                        if ($node->kind == \ast\AST_PROP) {
                                            if ($node->children[0]->kind == \ast\AST_VAR) {
                                                $class_name = find_class_name($file, $node, $namespace, $current_class, $current_scope);
                                                if ($class_name && !$node->children[1] instanceof \ast\Node) {
                                                    if (empty($classes[strtolower($class_name)]['properties'][$node->children[1]])) {
                                                        return '';
                                                    }
                                                    return $classes[strtolower($class_name)]['properties'][$node->children[1]]['value'];
                                                }
                                            }
                                        } else {
                                            if ($node->kind == \ast\AST_CALL) {
                                                if ($node->children[0]->kind == \ast\AST_NAME) {
                                                    $func_name = $node->children[0]->children[0];
                                                    if ($node->children[0]->flags & \ast\flags\NAME_NOT_FQ) {
                                                        $func = $namespace_map[T_FUNCTION][$file][strtolower($namespace . $func_name)] ?? $namespace_map[T_FUNCTION][$file][strtolower($func_name)] ?? $functions[strtolower($namespace . $func_name)] ?? $functions[strtolower($func_name)] ?? null;
                                                    } else {
                                                        $func = $functions[strtolower($func_name)] ?? null;
                                                    }
                                                    if ($func['file'] == 'internal' && empty($func['ret'])) {
                                                        if (!empty($internal_arginfo[$func_name])) {
                                                            return $internal_arginfo[$func_name][0] ?? '';
                                                        }
                                                    } else {
                                                        return $func['ret'] ?? '';
                                                    }
                                                } else {
                                                    // TODO: Handle $func() and other cases that get here
                                                }
                                            } else {
                                                if ($node->kind == \ast\AST_STATIC_CALL) {
                                                    $class_name = find_class_name($file, $node, $namespace, $current_class, $current_scope);
                                                    $method = find_method($class_name, $node->children[1]);
                                                    if ($method) {
                                                        return $method['ret'] ?? '';
                                                    }
                                                } else {
                                                    if ($node->kind == \ast\AST_METHOD_CALL) {
                                                        $class_name = find_class_name($file, $node, $namespace, $current_class, $current_scope);
                                                        if ($class_name) {
                                                            $method_name = $node->children[1];
                                                            $method = find_method($class_name, $method_name);
                                                            if ($method === false) {
                                                                Log::err(Log::EUNDEF, "call to undeclared method {$class_name}->{$method_name}()", $file, $node->lineno);
                                                            } else {
                                                                if ($method != 'dynamic') {
                                                                    return $method['ret'];
                                                                }
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    return '';
}
Example #2
0
File: pass1.php Project: 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");
}
Example #3
0
File: pass2.php Project: nikic/phan
function node_type($file, $namespace, $node, $current_scope, $current_class, &$taint = null, $check_var_exists = true)
{
    global $classes, $functions, $scope, $namespace_map, $internal_arginfo;
    if (!$node instanceof \ast\Node) {
        if ($node === null) {
            return '';
        }
        return type_map(gettype($node));
    } else {
        if ($node->kind == \ast\AST_ARRAY) {
            if (!empty($node->children) && $node->children[0] instanceof \ast\Node && $node->children[0]->kind == \ast\AST_ARRAY_ELEM) {
                // Check the first 5 (completely arbitrary) elements and assume the rest are the same type
                $etypes = [];
                for ($i = 0; $i < 5; $i++) {
                    if (empty($node->children[$i])) {
                        break;
                    }
                    if ($node->children[$i]->children[0] instanceof \ast\Node) {
                        $etypes[] = node_type($file, $namespace, $node->children[$i]->children[0], $current_scope, $current_class, $temp_taint);
                    } else {
                        $etypes[] = type_map(gettype($node->children[$i]->children[0]));
                    }
                }
                $types = array_unique($etypes);
                if (count($types) == 1 && !empty($types[0])) {
                    return mkgenerics($types[0]);
                }
            }
            return 'array';
        } else {
            if ($node->kind == \ast\AST_BINARY_OP || $node->kind == \ast\AST_GREATER || $node->kind == \ast\AST_GREATER_EQUAL) {
                if ($node->kind == \ast\AST_BINARY_OP) {
                    $node_flags = $node->flags;
                } else {
                    $node_flags = $node->kind;
                }
                $taint = var_taint_check($file, $node, $current_scope);
                switch ($node_flags) {
                    // Always a string from a concat
                    case \ast\flags\BINARY_CONCAT:
                        $temp_taint = false;
                        node_type($file, $namespace, $node->children[0], $current_scope, $current_class, $temp_taint);
                        if ($temp_taint) {
                            $taint = true;
                            return 'string';
                        }
                        node_type($file, $namespace, $node->children[1], $current_scope, $current_class, $temp_taint);
                        if ($temp_taint) {
                            $taint = true;
                        }
                        return 'string';
                        break;
                        // Boolean unless invalid operands
                    // Boolean unless invalid operands
                    case \ast\flags\BINARY_IS_IDENTICAL:
                    case \ast\flags\BINARY_IS_NOT_IDENTICAL:
                    case \ast\flags\BINARY_IS_EQUAL:
                    case \ast\flags\BINARY_IS_NOT_EQUAL:
                    case \ast\flags\BINARY_IS_SMALLER:
                    case \ast\flags\BINARY_IS_SMALLER_OR_EQUAL:
                    case \ast\AST_GREATER:
                    case \ast\AST_GREATER_EQUAL:
                        $temp = node_type($file, $namespace, $node->children[0], $current_scope, $current_class);
                        if (!$temp) {
                            $left = '';
                        } else {
                            $left = type_map($temp);
                        }
                        $temp = node_type($file, $namespace, $node->children[1], $current_scope, $current_class);
                        if (!$temp) {
                            $right = '';
                        } else {
                            $right = type_map($temp);
                        }
                        $taint = false;
                        // If we have generics and no non-generics on the left and the right is not array-like ...
                        if (!empty(generics($left)) && empty(nongenerics($left)) && !type_check($right, 'array')) {
                            Log::err(Log::ETYPE, "array to {$right} comparison", $file, $node->lineno);
                        } else {
                            // and the same for the right side
                            if (!empty(generics($right)) && empty(nongenerics($right)) && !type_check($left, 'array')) {
                                Log::err(Log::ETYPE, "{$left} to array comparison", $file, $node->lineno);
                            }
                        }
                        return 'bool';
                        break;
                        // Add is special because you can add arrays
                    // Add is special because you can add arrays
                    case \ast\flags\BINARY_ADD:
                        $temp = node_type($file, $namespace, $node->children[0], $current_scope, $current_class);
                        if (!$temp) {
                            $left = '';
                        } else {
                            $left = type_map($temp);
                        }
                        $temp = node_type($file, $namespace, $node->children[1], $current_scope, $current_class);
                        if (!$temp) {
                            $right = '';
                        } else {
                            $right = type_map($temp);
                        }
                        // fast-track common cases
                        if ($left == 'int' && $right == 'int') {
                            return 'int';
                        }
                        if (($left == 'int' || $left == 'float') && ($right == 'int' || $right == 'float')) {
                            return 'float';
                        }
                        $left_is_array = !empty(generics($left)) && empty(nongenerics($left));
                        $right_is_array = !empty(generics($right)) && empty(nongenerics($right));
                        if ($left_is_array && !type_check($right, 'array')) {
                            Log::err(Log::ETYPE, "invalid operator: left operand is array and right is not", $file, $node->lineno);
                            return '';
                        } else {
                            if ($right_is_array && !type_check($left, 'array')) {
                                Log::err(Log::ETYPE, "invalid operator: right operand is array and left is not", $file, $node->lineno);
                                return '';
                            } else {
                                if ($left_is_array || $right_is_array) {
                                    // If it is a '+' and we know one side is an array and the other is unknown, assume array
                                    return 'array';
                                }
                            }
                        }
                        return 'int|float';
                        $taint = false;
                        break;
                        // Everything else should be an int/float
                    // Everything else should be an int/float
                    default:
                        $temp = node_type($file, $namespace, $node->children[0], $current_scope, $current_class);
                        if (!$temp) {
                            $left = '';
                        } else {
                            $left = type_map($temp);
                        }
                        $temp = node_type($file, $namespace, $node->children[1], $current_scope, $current_class);
                        if (!$temp) {
                            $right = '';
                        } else {
                            $right = type_map($temp);
                        }
                        if ($left == 'array' || $right == 'array') {
                            Log::err(Log::ETYPE, "invalid array operator", $file, $node->lineno);
                            return '';
                        } else {
                            if ($left == 'int' && $right == 'int') {
                                return 'int';
                            } else {
                                if ($left == 'float' || $right == 'float') {
                                    return 'float';
                                }
                            }
                        }
                        return 'int|float';
                        $taint = false;
                        break;
                }
            } else {
                if ($node->kind == \ast\AST_CAST) {
                    $taint = var_taint_check($file, $node->children[0], $current_scope);
                    switch ($node->flags) {
                        case \ast\flags\TYPE_NULL:
                            return 'null';
                            break;
                        case \ast\flags\TYPE_BOOL:
                            $taint = false;
                            return 'bool';
                            break;
                        case \ast\flags\TYPE_LONG:
                            $taint = false;
                            return 'int';
                            break;
                        case \ast\flags\TYPE_DOUBLE:
                            $taint = false;
                            return 'float';
                            break;
                        case \ast\flags\TYPE_STRING:
                            return 'string';
                            break;
                        case \ast\flags\TYPE_ARRAY:
                            return 'array';
                            break;
                        case \ast\flags\TYPE_OBJECT:
                            return 'object';
                            break;
                        default:
                            Log::err(Log::EFATAL, "Unknown type (" . $node->flags . ") in cast");
                    }
                } else {
                    if ($node->kind == \ast\AST_NEW) {
                        $class_name = find_class_name($file, $node, $namespace, $current_class, $current_scope);
                        if ($class_name) {
                            return $classes[strtolower($class_name)]['type'];
                        }
                        return 'object';
                    } else {
                        if ($node->kind == \ast\AST_DIM) {
                            $taint = var_taint_check($file, $node->children[0], $current_scope);
                            $type = node_type($file, $namespace, $node->children[0], $current_scope, $current_class);
                            if (!empty($type)) {
                                $gen = generics($type);
                                if (empty($gen)) {
                                    if ($type !== 'null' && !type_check($type, 'string|ArrayAccess')) {
                                        // array offsets work on strings, unfortunately
                                        // Double check that any classes in the type don't have ArrayAccess
                                        $ok = false;
                                        foreach (explode('|', $type) as $t) {
                                            if (!empty($t) && !is_native_type($t)) {
                                                if (!empty($classes[strtolower($t)]['type'])) {
                                                    if (strpos('|' . $classes[strtolower($t)]['type'] . '|', '|ArrayAccess|') !== false) {
                                                        $ok = true;
                                                        break;
                                                    }
                                                }
                                            }
                                        }
                                        if (!$ok) {
                                            Log::err(Log::ETYPE, "Suspicious array access to {$type}", $file, $node->lineno);
                                        }
                                    }
                                    return '';
                                }
                            } else {
                                return '';
                            }
                            return $gen;
                        } else {
                            if ($node->kind == \ast\AST_VAR) {
                                return var_type($file, $node, $current_scope, $taint, $check_var_exists);
                            } else {
                                if ($node->kind == \ast\AST_ENCAPS_LIST) {
                                    foreach ($node->children as $encap) {
                                        if ($encap instanceof \ast\Node) {
                                            if (var_taint_check($file, $encap, $current_scope)) {
                                                $taint = true;
                                            }
                                        }
                                    }
                                    return "string";
                                } else {
                                    if ($node->kind == \ast\AST_CONST) {
                                        if ($node->children[0]->kind == \ast\AST_NAME) {
                                            if (defined($node->children[0]->children[0])) {
                                                return type_map(gettype(constant($node->children[0]->children[0])));
                                            } else {
                                                // Todo: user-defined constant
                                            }
                                        }
                                    } else {
                                        if ($node->kind == \ast\AST_CLASS_CONST) {
                                            if ($node->children[1] == 'class') {
                                                return 'string';
                                            }
                                            // class name fetch
                                            $class_name = find_class_name($file, $node, $namespace, $current_class, $current_scope);
                                            if (!$class_name) {
                                                return '';
                                            }
                                            $ltemp = strtolower($class_name);
                                            while ($ltemp && !array_key_exists($node->children[1], $classes[$ltemp]['constants'])) {
                                                $ltemp = strtolower($classes[$ltemp]['parent']);
                                                if (empty($classes[$ltemp])) {
                                                    return '';
                                                }
                                                // undeclared class - will be caught elsewhere
                                            }
                                            if (!$ltemp || !array_key_exists($node->children[1], $classes[$ltemp]['constants'])) {
                                                Log::err(Log::EUNDEF, "can't access undeclared constant {$class_name}::{$node->children[1]}", $file, $node->lineno);
                                                return '';
                                            }
                                            return $classes[$ltemp]['constants'][$node->children[1]]['type'];
                                        } else {
                                            if ($node->kind == \ast\AST_PROP) {
                                                if ($node->children[0]->kind == \ast\AST_VAR) {
                                                    $class_name = find_class_name($file, $node, $namespace, $current_class, $current_scope);
                                                    if ($class_name && !$node->children[1] instanceof \ast\Node) {
                                                        $ltemp = find_property($file, $node, $class_name, $node->children[1], $class_name, false);
                                                        if (empty($ltemp)) {
                                                            return '';
                                                        }
                                                        return $classes[$ltemp]['properties'][$node->children[1]]['type'];
                                                    }
                                                }
                                            } else {
                                                if ($node->kind == \ast\AST_STATIC_PROP) {
                                                    if ($node->children[0]->kind == \ast\AST_NAME) {
                                                        $class_name = qualified_name($file, $node->children[0], $namespace);
                                                        if ($class_name && !$node->children[1] instanceof \ast\Node) {
                                                            $ltemp = find_property($file, $node, $class_name, $node->children[1], $class_name, false);
                                                            if (empty($ltemp)) {
                                                                return '';
                                                            }
                                                            return $classes[$ltemp]['properties'][$node->children[1]]['type'];
                                                        }
                                                    }
                                                } else {
                                                    if ($node->kind == \ast\AST_CALL) {
                                                        if ($node->children[0]->kind == \ast\AST_NAME) {
                                                            $func_name = $node->children[0]->children[0];
                                                            if ($node->children[0]->flags & \ast\flags\NAME_NOT_FQ) {
                                                                $func = $namespace_map[T_FUNCTION][$file][strtolower($namespace . $func_name)] ?? $namespace_map[T_FUNCTION][$file][strtolower($func_name)] ?? $functions[strtolower($namespace . $func_name)] ?? $functions[strtolower($func_name)] ?? null;
                                                            } else {
                                                                $func = $functions[strtolower($func_name)] ?? null;
                                                            }
                                                            if ($func['file'] == 'internal' && empty($func['ret'])) {
                                                                if (!empty($internal_arginfo[$func_name])) {
                                                                    return $internal_arginfo[$func_name][0] ?? '';
                                                                }
                                                            } else {
                                                                return $func['ret'] ?? '';
                                                            }
                                                        } else {
                                                            // TODO: Handle $func() and other cases that get here
                                                        }
                                                    } else {
                                                        if ($node->kind == \ast\AST_STATIC_CALL) {
                                                            $class_name = find_class_name($file, $node, $namespace, $current_class, $current_scope);
                                                            $method = find_method($class_name, $node->children[1]);
                                                            if ($method) {
                                                                return $method['ret'] ?? '';
                                                            }
                                                        } else {
                                                            if ($node->kind == \ast\AST_METHOD_CALL) {
                                                                $class_name = find_class_name($file, $node, $namespace, $current_class, $current_scope);
                                                                if ($class_name) {
                                                                    $method_name = $node->children[1];
                                                                    $method = find_method($class_name, $method_name);
                                                                    if ($method === false) {
                                                                        Log::err(Log::EUNDEF, "call to undeclared method {$class_name}->{$method_name}()", $file, $node->lineno);
                                                                    } else {
                                                                        if ($method != 'dynamic') {
                                                                            return $method['ret'];
                                                                        }
                                                                    }
                                                                }
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    return '';
}
Example #4
0
function node_func($file, $conditional, $node, $current_scope, $current_class, $namespace = '')
{
    global $scope;
    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 ($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'])) {
                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, $current_class);
                    if ($type === "NULL") {
                        add_type($current_scope, $v['name'], $type);
                        if (!empty($result['params'][$k]['type'])) {
                            $result['params'][$k]['type'] .= '|NULL';
                        }
                    } else {
                        if ($scope[$current_scope]['vars'][$v['name']]['type'] !== '') {
                            // Does the default value match the declared type?
                            if (!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);
                            }
                        }
                    }
                }
                $i++;
            }
        }
        return $result;
    }
    assert(false, "{$node} was not an \\ast\\Node");
}
function item_display($station, $itemId, $item, $lvl = 0)
{
    global $Db, $hrwidth, $pos_fuels, $xpandall;
    $type = $Db->getTypeFromTypeId($item["typeID"]);
    $name = $type["typeName"];
    /* New field - rawQuantity
    
    Items in the AssetList (and ContractItems) now include a rawQuantity attribute if the quantity in the DB is negative. Negative quantities are in fact codes, -1 indicates that the item is a singleton (non-stackable). If the item happens to be a Blueprint, -1 is an Original and -2 is a Blueprint Copy. For further information about negative quantities see this devblog http://www.eveonline.com/devblog.asp?a=blog&nbid=2324 */
    if (strpos($name, " Blueprint") !== false && isset($item["rawQuantity"]) && (int) $item["rawQuantity"] == -2) {
        $name .= " (Copy)";
    }
    $output = "";
    if ($pos_fuels && !contains_pos_fuel($item)) {
        return;
    }
    if ($type == "") {
        $name = "<b><span style='font-size:80%;'>[UNKNOWN ITEM TYPE " . $item["typeID"] . "]</span></b>";
    } else {
        type_check($type, $station);
    }
    $space = "";
    for ($i = 0; $i < $lvl; $i++) {
        $space .= "&nbsp;";
    }
    if (isset($item["contents"])) {
        $output .= "{$space}<a href=\"#\" onclick=\"return toggle_visibility('i" . $itemId . "');\">";
        $output .= "{$name} [" . count($item["contents"]) . "]</a><br>\n";
        $output .= "<div id=\"i" . $itemId . "\" style=\"" . ($xpandall && (!$pos_fuels || !is_space($item)) ? "display:block;" : "display:none;") . "\">\n<HR align=left>\n";
        foreach ($item["contents"] as $itemIdC => $itemC) {
            $output .= item_display($station, $itemIdC, $itemC, $lvl + 1);
        }
        $output .= "<HR align=left></div>";
    } else {
        if (!$pos_fuels || $pos_fuels && is_pos_fuel($type)) {
            $output .= $space . ($item["quantity"] == "1" ? "" : $item["quantity"] . " x ") . $name . "<br>\n";
        }
    }
    return $output;
}
Example #6
0
function arglist_type_check($file, $arglist, $func, $current_scope) : 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, $arg, $current_scope, $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, $arg, $current_scope, $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, $arg, $current_scope, $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']];
                }
            }
        }
        // TODO: specific object checks here. this is turning object:name into just object
        //       (and also 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'])) {
            if (!empty($param['name'])) {
                $paramstr = '(' . trim($param['name'], '&=') . ')';
            } else {
                $paramstr = '';
            }
            if (empty($arg_type)) {
                $arg_type = 'mixed';
            }
            if ($func['file'] == 'internal') {
                $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;
}
Example #7
0
 function writeSI()
 {
     $index = array();
     $this->stringIndex = gather_strings($this->payload, $index, $this->payload);
     if (count($this->stringIndex) === 0) {
         $this->writeValue(EMPTYINDEX, CHAR, TRUE);
         return $this;
     }
     $this->stringIndexType = type_check(count($this->stringIndex));
     $this->writeValue(count($this->stringIndex), $this->stringIndexType);
     foreach ($this->stringIndex as $s) {
         $this->writeString($s);
     }
     return $this;
 }
Example #8
0
function node_type($file, $namespace, $node, $current_scope, &$taint = null, $check_var_exists = true)
{
    global $classes, $functions, $namespace_map, $internal_arginfo;
    if (!$node instanceof \ast\Node) {
        if ($node === null) {
            return '';
        }
        return type_map(gettype($node));
    } else {
        if ($node->kind == \ast\AST_ARRAY) {
            return 'array';
        } else {
            if ($node->kind == \ast\AST_BINARY_OP) {
                $taint = var_taint_check($file, $node, $current_scope);
                switch ($node->flags) {
                    // Always a string from a concat
                    case \ast\flags\BINARY_CONCAT:
                        $temp_taint = false;
                        node_type($file, $namespace, $node->children[0], $current_scope, $temp_taint);
                        if ($temp_taint) {
                            $taint = true;
                            return 'string';
                        }
                        node_type($file, $namespace, $node->children[1], $current_scope, $temp_taint);
                        if ($temp_taint) {
                            $taint = true;
                        }
                        return 'string';
                        break;
                        // Boolean unless invalid operands
                    // Boolean unless invalid operands
                    case \ast\flags\BINARY_IS_IDENTICAL:
                    case \ast\flags\BINARY_IS_NOT_IDENTICAL:
                    case \ast\flags\BINARY_IS_EQUAL:
                    case \ast\flags\BINARY_IS_NOT_EQUAL:
                    case \ast\flags\BINARY_IS_SMALLER:
                    case \ast\flags\BINARY_IS_SMALLER_OR_EQUAL:
                        $taint = false;
                        return 'bool';
                        break;
                        // Everything else should be an int/float
                    // Everything else should be an int/float
                    default:
                        $left = type_map(node_type($file, $namespace, $node->children[0], $current_scope));
                        $right = type_map(node_type($file, $namespace, $node->children[1], $current_scope));
                        if ($left == 'array' && $right == 'array') {
                            return 'array';
                        } else {
                            if ($left == 'array' && !type_check($right, 'array')) {
                                Log::err(Log::ETYPE, "invalid operator: left operand is array and right is not", $file, $node->lineno);
                                return 'mixed';
                            } else {
                                if ($right == 'array' && !type_check($left, 'array')) {
                                    Log::err(Log::ETYPE, "invalid operator: right operand is array and left is not", $file, $node->lineno);
                                    return 'mixed';
                                } else {
                                    if ($left == 'int' && $right == 'int') {
                                        return 'int';
                                    } else {
                                        if ($left == 'float' || $right == 'float') {
                                            return 'float';
                                        }
                                    }
                                }
                            }
                        }
                        return 'int|float';
                        $taint = false;
                        break;
                }
            } else {
                if ($node->kind == \ast\AST_CAST) {
                    $taint = var_taint_check($file, $node->children[0], $current_scope);
                    switch ($node->flags) {
                        case \ast\flags\TYPE_NULL:
                            return 'null';
                            break;
                        case \ast\flags\TYPE_BOOL:
                            $taint = false;
                            return 'bool';
                            break;
                        case \ast\flags\TYPE_LONG:
                            $taint = false;
                            return 'int';
                            break;
                        case \ast\flags\TYPE_DOUBLE:
                            $taint = false;
                            return 'float';
                            break;
                        case \ast\flags\TYPE_STRING:
                            return 'string';
                            break;
                        case \ast\flags\TYPE_ARRAY:
                            return 'array';
                            break;
                        case \ast\flags\TYPE_OBJECT:
                            return 'stdClass';
                            break;
                        default:
                            Log::err(Log::EFATAL, "Unknown type (" . $node->flags . ") in cast");
                    }
                } else {
                    if ($node->kind == \ast\AST_NEW) {
                        if ($node->children[0]->kind == \ast\AST_NAME) {
                            $name = var_name($node->children[0]);
                            if ($name == 'self' || $name == 'static') {
                                list($class, ) = explode('::', $current_scope);
                                $name = $class;
                            }
                            $found = null;
                            if ($node->children[0]->flags & \ast\flags\NAME_NOT_FQ) {
                                if (!empty($namespace_map[T_CLASS][$file][strtolower($name)])) {
                                    if (!empty($classes[strtolower($namespace_map[T_CLASS][$file][strtolower($name)])])) {
                                        $found = $classes[strtolower($namespace_map[T_CLASS][$file][strtolower($name)])];
                                    }
                                }
                                if (!$found && empty($classes[strtolower($namespace . $name)]) && empty($classes[strtolower($name)])) {
                                    Log::err(Log::EUNDEF, "Trying to instantiate undeclared class {$name}", $file, $node->lineno);
                                }
                                $found = $found ?? $classes[strtolower($namespace . $name)] ?? $classes[strtolower($name)] ?? null;
                            } else {
                                if (empty($classes[strtolower($name)])) {
                                    Log::err(Log::EUNDEF, "Trying to instantiate undeclared class {$name}", $file, $node->lineno);
                                } else {
                                    $found = $classes[strtolower($name)];
                                }
                            }
                            if ($found) {
                                return $found['type'];
                            } else {
                                return $name;
                            }
                        }
                        return 'object';
                    } else {
                        if ($node->kind == \ast\AST_DIM) {
                            $taint = var_taint_check($file, $node->children[0], $current_scope);
                            // TODO: Do something smart with array elements
                            return 'mixed';
                        } else {
                            if ($node->kind == \ast\AST_VAR) {
                                return var_type($file, $node, $current_scope, $taint, $check_var_exists);
                            } else {
                                if ($node->kind == \ast\AST_ENCAPS_LIST) {
                                    foreach ($node->children as $encap) {
                                        if ($encap instanceof \ast\Node) {
                                            if (var_taint_check($file, $encap, $current_scope)) {
                                                $taint = true;
                                            }
                                        }
                                    }
                                    return "string";
                                } else {
                                    if ($node->kind == \ast\AST_CONST) {
                                        if ($node->children[0]->kind == \ast\AST_NAME) {
                                            if (defined($node->children[0]->children[0])) {
                                                return type_map(gettype(constant($node->children[0]->children[0])));
                                            } else {
                                                // User-defined constant
                                            }
                                        }
                                    } else {
                                        if ($node->kind == \ast\AST_CALL) {
                                            if ($node->children[0]->kind == \ast\AST_NAME) {
                                                $func_name = $node->children[0]->children[0];
                                                if ($node->children[0]->flags & \ast\flags\NAME_NOT_FQ) {
                                                    $func = $namespace_map[T_FUNCTION][$file][strtolower($namespace . $func_name)] ?? $namespace_map[T_FUNCTION][$file][strtolower($func_name)] ?? $functions[strtolower($namespace . $func_name)] ?? $functions[strtolower($func_name)] ?? null;
                                                } else {
                                                    $func = $functions[strtolower($func_name)] ?? null;
                                                }
                                                if ($func['file'] == 'internal' && empty($func['ret'])) {
                                                    if (!empty($internal_arginfo[$func_name])) {
                                                        return $internal_arginfo[$func_name][0] ?? 'mixed';
                                                    }
                                                } else {
                                                    return $func['ret'] ?? 'mixed';
                                                }
                                            } else {
                                                // TODO: Handle $func() and other cases that get here
                                            }
                                        } else {
                                            if ($node->kind == \ast\AST_STATIC_CALL) {
                                                // If the called static method has a return type defined, use that
                                                $call = $node->children[0];
                                                if ($call->kind == \ast\AST_NAME) {
                                                    // Simple static function call
                                                    $class_name = $call->children[0];
                                                    $method_name = $node->children[1];
                                                    $class = find_class($call, $namespace, $namespace_map[T_CLASS][$file] ?? []);
                                                    if (!$class) {
                                                        Log::err(Log::EUNDEF, "static call to undeclared class {$class_name}", $file, $call->lineno);
                                                        return 'mixed';
                                                    }
                                                    $method = find_method($class['name'], $method_name);
                                                    if ($method) {
                                                        return $method['ret'] ?? 'mixed';
                                                    }
                                                } else {
                                                    // Weird dynamic static call - give up
                                                    return 'mixed';
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    return "mixed";
}
Example #9
0
 static function toTemplate($payload)
 {
     $type = type_check($payload);
     if ($type === 0x80) {
         return array(type_gcd($payload));
     } else {
         if ($type === 0x9) {
             $ret = array();
             foreach ($payload as $k => $v) {
                 $ret[$k] = self::toTemplate($v);
             }
             return $ret;
         } else {
             if ($type === 0x1b) {
                 $ret = array();
                 foreach (get_object_vars($payload) as $k => $v) {
                     $ret[$k] = self::toTemplate($v);
                 }
                 return $ret;
             } else {
                 if ($type === 0x21) {
                     return 0x20;
                 } else {
                     return $type;
                 }
             }
         }
     }
 }