Exemplo n.º 1
0
function arg_check(string $file, $namespace, $ast, string $func_name, $func, string $current_scope, $current_class, string $class_name = '')
{
    global $internal_arginfo, $functions, $scope;
    $ok = false;
    $varargs = false;
    $taint = false;
    // Are we calling it with the right number of args?
    if ($ast->kind == \ast\AST_CALL || $ast->kind == \ast\AST_NEW) {
        $arglist = $ast->children[1];
    } else {
        $arglist = $ast->children[2];
    }
    $argcount = count($arglist->children);
    // Special common cases where we want slightly better multi-signature error messages
    if ($func['file'] == 'internal') {
        switch ($func['name']) {
            case 'join':
            case 'implode':
                // (string glue, array pieces), (array pieces, string glue) or (array pieces)
                if ($argcount == 1) {
                    // If we have just one arg it must be an array
                    if (($arg_type = node_type($file, $namespace, $arglist->children[0], $current_scope, $current_class)) != 'array') {
                        Log::err(Log::ETYPE, "arg#1(pieces) is {$arg_type} but {$func['name']}() takes array when passed only 1 arg", $file, $ast->lineno);
                    }
                    return;
                } else {
                    if ($argcount == 2) {
                        $arg1_type = node_type($file, $namespace, $arglist->children[0], $current_scope, $current_class);
                        $arg2_type = node_type($file, $namespace, $arglist->children[1], $current_scope, $current_class);
                        if ($arg1_type == 'array') {
                            if (!type_check($arg2_type, 'string')) {
                                Log::err(Log::ETYPE, "arg#2(glue) is {$arg2_type} but {$func['name']}() takes string when arg#1 is array", $file, $ast->lineno);
                            }
                        } else {
                            if ($arg1_type == 'string') {
                                if (!type_check($arg2_type, 'array')) {
                                    Log::err(Log::ETYPE, "arg#2(pieces) is {$arg2_type} but {$func['name']}() takes array when arg#1 is string", $file, $ast->lineno);
                                }
                            }
                        }
                        return;
                    }
                }
                // Any other arg counts we will let the regular checks handle
                break;
            case 'strtok':
                // (string str, string token) or (string token)
                if ($argcount == 1) {
                    // If we have just one arg it must be a string token
                    if (($arg_type = node_type($file, $namespace, $arglist->children[0], $current_scope, $current_class)) != 'string') {
                        Log::err(Log::ETYPE, "arg#1(token) is {$arg_type} but {$func['name']}() takes string when passed only one arg", $file, $ast->lineno);
                        return;
                    }
                }
                // The arginfo check will handle the other case
                break;
            case 'min':
            case 'max':
                if ($argcount == 1) {
                    // If we have just one arg it must be an array
                    if (($arg_type = node_type($file, $namespace, $arglist->children[0], $current_scope, $current_class)) != 'array') {
                        Log::err(Log::ETYPE, "arg#1(values) is {$arg_type} but {$func['name']}() takes array when passed only one arg", $file, $ast->lineno);
                        return;
                    }
                }
                $varargs = true;
                // The arginfo check will handle the other case
                break;
            default:
                if (internal_varargs_check($func['name'])) {
                    $varargs = true;
                }
                break;
        }
    } else {
        foreach ($func['params'] as $param) {
            if ($param['flags'] & \ast\flags\PARAM_VARIADIC) {
                $varargs = true;
            }
        }
    }
    $fn = $func['scope'] ?? $func['name'];
    if ($argcount < $func['required']) {
        $err = true;
        $alt = 1;
        // Check if there is an alternate signature that is ok
        while (!empty($functions["{$func['name']} {$alt}"])) {
            if ($argcount < $functions["{$func['name']} {$alt}"]['required']) {
                $alt++;
            } else {
                $err = false;
                break;
            }
        }
        if ($err) {
            if ($func['file'] == 'internal') {
                Log::err(Log::EPARAM, "call with {$argcount} arg(s) to {$func['name']}() that requires {$func['required']} arg(s)", $file, $ast->lineno);
            } else {
                Log::err(Log::EPARAM, "call with {$argcount} arg(s) to {$func['name']}() that requires {$func['required']} arg(s) defined at {$func['file']}:{$func['lineno']}", $file, $ast->lineno);
            }
        }
    }
    if (!$varargs && $argcount > $func['required'] + $func['optional']) {
        $err = true;
        $alt = 1;
        // Check if there is an alternate signature that is ok
        while (!empty($functions["{$func['name']} {$alt}"])) {
            if ($argcount > $functions["{$func['name']} {$alt}"]['required'] + $functions["{$func['name']} {$alt}"]['optional']) {
                $alt++;
            } else {
                $err = false;
                break;
            }
        }
        if ($err) {
            $max = $func['required'] + $func['optional'];
            if ($func['file'] == 'internal') {
                Log::err(Log::EPARAM, "call with {$argcount} arg(s) to {$func['name']}() that only takes {$max} arg(s)", $file, $ast->lineno);
            } else {
                Log::err(Log::EPARAM, "call with {$argcount} arg(s) to {$func['name']}() that only takes {$max} arg(s) defined at {$func['file']}:{$func['lineno']}", $file, $ast->lineno);
            }
        }
    }
    // Are the types right?
    // Check if we have any alternate arginfo signatures
    // Checking the alternates before the main to make the final error messages, if any, refer to the main signature
    $errs = [];
    $alt = 1;
    while (!empty($functions["{$func['name']} {$alt}"])) {
        $errs = arglist_type_check($file, $namespace, $arglist, $functions["{$func['name']} {$alt}"], $current_scope, $current_class);
        $alt++;
        if (empty($errs)) {
            break;
        }
    }
    if ($alt == 1 || $alt > 1 && !empty($errs)) {
        $errs = arglist_type_check($file, $namespace, $arglist, $func, $current_scope, $current_class);
    }
    foreach ($errs as $err) {
        Log::err(Log::ETYPE, $err, $file, $ast->lineno);
    }
}
Exemplo n.º 2
0
Arquivo: pass2.php Projeto: nikic/phan
function arg_check(string $file, $namespace, $ast, string $func_name, $func, string $current_scope, $current_class, string $class_name = '')
{
    global $internal_arginfo, $classes, $functions, $scope;
    $ok = false;
    $varargs = false;
    $unpack = false;
    $taint = false;
    if ($ast->kind == \ast\AST_CALL || $ast->kind == \ast\AST_NEW) {
        $arglist = $ast->children[1];
    } else {
        $arglist = $ast->children[2];
    }
    $argcount = count($arglist->children);
    // Special common cases where we want slightly better multi-signature error messages
    if ($func['file'] == 'internal') {
        switch ($func['name']) {
            case 'join':
            case 'implode':
                // (string glue, array pieces), (array pieces, string glue) or (array pieces)
                if ($argcount == 1) {
                    // If we have just one arg it must be an array
                    $arg_type = node_type($file, $namespace, $arglist->children[0], $current_scope, $current_class);
                    if (!type_check($arg_type, 'array')) {
                        Log::err(Log::EPARAM, "arg#1(pieces) is {$arg_type} but {$func['name']}() takes array when passed only 1 arg", $file, $ast->lineno);
                    }
                    return;
                } else {
                    if ($argcount == 2) {
                        $arg1_type = node_type($file, $namespace, $arglist->children[0], $current_scope, $current_class);
                        $arg2_type = node_type($file, $namespace, $arglist->children[1], $current_scope, $current_class);
                        if ($arg1_type == 'array') {
                            if (!type_check($arg2_type, 'string')) {
                                Log::err(Log::EPARAM, "arg#2(glue) is {$arg2_type} but {$func['name']}() takes string when arg#1 is array", $file, $ast->lineno);
                            }
                        } else {
                            if ($arg1_type == 'string') {
                                if (!type_check($arg2_type, 'array')) {
                                    Log::err(Log::EPARAM, "arg#2(pieces) is {$arg2_type} but {$func['name']}() takes array when arg#1 is string", $file, $ast->lineno);
                                }
                            }
                        }
                        return;
                    }
                }
                // Any other arg counts we will let the regular checks handle
                break;
            case 'array_udiff':
            case 'array_diff_uassoc':
            case 'array_uintersect_assoc':
            case 'array_intersect_ukey':
                if ($argcount < 3) {
                    Log::err(Log::EPARAM, "call with {$argcount} arg(s) to {$func['name']}() which requires {$func['required']} arg(s)", $file, $ast->lineno);
                    return;
                }
                // The last argument must be a callable and there can be a variable number of arrays before it
                $arg_type = node_type($file, $namespace, $arglist->children[$argcount - 1], $current_scope, $current_class);
                if (!type_check($arg_type, 'callable')) {
                    Log::err(Log::EPARAM, "The last argument to {$func['name']} must be a callable", $file, $ast->lineno);
                }
                for ($i = 0; $i < $argcount - 1; $i++) {
                    $arg_type = node_type($file, $namespace, $arglist->children[$i], $current_scope, $current_class);
                    if (!type_check($arg_type, 'array')) {
                        Log::err(Log::EPARAM, "arg#" . ($i + 1) . " is {$arg_type} but {$func['name']}() takes array", $file, $ast->lineno);
                    }
                }
                return;
            case 'array_diff_uassoc':
            case 'array_uintersect_uassoc':
                if ($argcount < 4) {
                    Log::err(Log::EPARAM, "call with {$argcount} arg(s) to {$func['name']}() which requires {$func['required']} arg(s)", $file, $ast->lineno);
                    return;
                }
                // The last 2 arguments must be a callable and there can be a variable number of arrays before it
                $arg_type = node_type($file, $namespace, $arglist->children[$argcount - 1], $current_scope, $current_class);
                if (!type_check($arg_type, 'callable')) {
                    Log::err(Log::EPARAM, "The last argument to {$func['name']} must be a callable", $file, $ast->lineno);
                }
                $arg_type = node_type($file, $namespace, $arglist->children[$argcount - 2], $current_scope, $current_class);
                if (!type_check($arg_type, 'callable')) {
                    Log::err(Log::EPARAM, "The second last argument to {$func['name']} must be a callable", $file, $ast->lineno);
                }
                for ($i = 0; $i < $argcount - 2; $i++) {
                    $arg_type = node_type($file, $namespace, $arglist->children[$i], $current_scope, $current_class);
                    if (!type_check($arg_type, 'array')) {
                        Log::err(Log::EPARAM, "arg#" . ($i + 1) . " is {$arg_type} but {$func['name']}() takes array", $file, $ast->lineno);
                    }
                }
                return;
            case 'strtok':
                // (string str, string token) or (string token)
                if ($argcount == 1) {
                    // If we have just one arg it must be a string token
                    $arg_type = node_type($file, $namespace, $arglist->children[0], $current_scope, $current_class);
                    if (!type_check($arg_type, 'string')) {
                        Log::err(Log::EPARAM, "arg#1(token) is {$arg_type} but {$func['name']}() takes string when passed only one arg", $file, $ast->lineno);
                        return;
                    }
                }
                // The arginfo check will handle the other case
                break;
            case 'min':
            case 'max':
                if ($argcount == 1) {
                    // If we have just one arg it must be an array
                    $arg_type = node_type($file, $namespace, $arglist->children[0], $current_scope, $current_class);
                    if (!type_check($arg_type, 'array')) {
                        Log::err(Log::EPARAM, "arg#1(values) is {$arg_type} but {$func['name']}() takes array when passed only one arg", $file, $ast->lineno);
                        return;
                    }
                }
                $varargs = true;
                // The arginfo check will handle the other case
                break;
            default:
                if (internal_varargs_check($func['name'])) {
                    $varargs = true;
                }
                break;
        }
    } else {
        if (!empty($func['deprecated'])) {
            Log::err(Log::EDEP, "Call to deprecated function {$func['name']}() defined at {$func['file']}:{$func['lineno']}", $file, $ast->lineno);
        }
    }
    if (array_key_exists('params', $func)) {
        foreach ($func['params'] as $param) {
            if ($param['flags'] & \ast\flags\PARAM_VARIADIC) {
                $varargs = true;
            }
        }
    }
    foreach ($arglist->children as $arg) {
        if ($arg instanceof \ast\Node && $arg->kind == \ast\AST_UNPACK) {
            $unpack = true;
        }
    }
    $fn = $func['scope'] ?? $func['name'];
    if (!$unpack && $argcount < $func['required']) {
        $err = true;
        $alt = 1;
        // Check if there is an alternate signature that is ok
        while (!empty($functions["{$func['name']} {$alt}"])) {
            if ($argcount < $functions["{$func['name']} {$alt}"]['required']) {
                $alt++;
            } else {
                $err = false;
                break;
            }
        }
        if ($err) {
            if ($func['file'] == 'internal') {
                Log::err(Log::EPARAM, "call with {$argcount} arg(s) to {$func['name']}() which requires {$func['required']} arg(s)", $file, $ast->lineno);
            } else {
                Log::err(Log::EPARAM, "call with {$argcount} arg(s) to {$func['name']}() which requires {$func['required']} arg(s) defined at {$func['file']}:{$func['lineno']}", $file, $ast->lineno);
            }
        }
    }
    if (!$varargs && $argcount > $func['required'] + $func['optional']) {
        $err = true;
        $alt = 1;
        // Check if there is an alternate signature that is ok
        while (!empty($functions["{$func['name']} {$alt}"])) {
            if ($argcount > $functions["{$func['name']} {$alt}"]['required'] + $functions["{$func['name']} {$alt}"]['optional']) {
                $alt++;
            } else {
                $err = false;
                break;
            }
        }
        // For method calls, we have no way of knowing the actual signature.
        // We may only have the base signature from an interface, for example
        // and the actual called method could have extra optional args
        if ($err && $ast->kind != \ast\AST_METHOD_CALL) {
            $max = $func['required'] + $func['optional'];
            if ($func['file'] == 'internal') {
                Log::err(Log::EPARAM, "call with {$argcount} arg(s) to {$func['name']}() which only takes {$max} arg(s)", $file, $ast->lineno);
            } else {
                Log::err(Log::EPARAM, "call with {$argcount} arg(s) to {$func['name']}() which only takes {$max} arg(s) defined at {$func['file']}:{$func['lineno']}", $file, $ast->lineno);
            }
        }
    }
    // Are the types right?
    // Check if we have any alternate arginfo signatures
    // Checking the alternates before the main to make the final error messages, if any, refer to the main signature
    $errs = [];
    $alt = 1;
    if ($class_name) {
        $lc = strtolower($class_name);
        $lfn = strtolower($func['name']);
        $func['name'] = $class_name . '::' . $func['name'];
        while (!empty($classes[$lc]['methods']["{$lfn} {$alt}"])) {
            $errs = arglist_type_check($file, $namespace, $arglist, $classes[$lc]['methods']["{$lfn} {$alt}"], $current_scope, $current_class);
            $alt++;
            if (empty($errs)) {
                break;
            }
        }
    } else {
        while (!empty($functions["{$func['name']} {$alt}"])) {
            $errs = arglist_type_check($file, $namespace, $arglist, $functions["{$func['name']} {$alt}"], $current_scope, $current_class);
            $alt++;
            if (empty($errs)) {
                break;
            }
        }
    }
    if ($alt == 1 || $alt > 1 && !empty($errs)) {
        $errs = arglist_type_check($file, $namespace, $arglist, $func, $current_scope, $current_class);
    }
    foreach ($errs as $err) {
        Log::err(Log::ETYPE, $err, $file, $ast->lineno);
    }
}