nlopess Fri Sep 8 16:37:20 2006 UTC Modified files: /php-src/scripts/dev check_parameters.php Log: MFB
http://cvs.php.net/viewvc.cgi/php-src/scripts/dev/check_parameters.php?r1=1.1&r2=1.2&diff_format=u Index: php-src/scripts/dev/check_parameters.php diff -u /dev/null php-src/scripts/dev/check_parameters.php:1.2 --- /dev/null Fri Sep 8 16:37:20 2006 +++ php-src/scripts/dev/check_parameters.php Fri Sep 8 16:37:19 2006 @@ -0,0 +1,336 @@ +<?php +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2006 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | [EMAIL PROTECTED] so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Nuno Lopes <[EMAIL PROTECTED]> | + +----------------------------------------------------------------------+ +*/ + +/* $Id: check_parameters.php,v 1.2 2006/09/08 16:37:19 nlopess Exp $ */ + + +define('REPORT_LEVEL', 2); // 0 reports less false-positives. up to level 5. +define('VERSION', '6'); // minimum is 5.2 +define('PHPDIR', dirname(__FILE__) . '/../..'); + + +// be sure you have enough memory and stack for PHP. pcre will push the limits! +ini_set('pcre.backtrack_limit', 10000000); + + +// ------------------------ end of config ---------------------------- + + +$API_params = array( + 'a' => array('zval**'), // array as zval* + 'b' => array('zend_bool*'), // boolean + 'C' => array('zend_class_entry**'), // class + 'd' => array('double*'), // double + 'f' => array('zend_fcall_info*', 'zend_fcall_info_cache*'), // function + 'h' => array('HashTable**'), // array as an HashTable* + 'l' => array('long*'), // long + 'o' => array('zval**'), //object + 'O' => array('zval**', 'zend_class_entry*'), // object of given type + 'r' => array('zval**'), // resource + 's' => array('char**', 'int*'), // string + 'z' => array('zval**'), // zval* + 'Z' => array('zval***') // zval** +); + +// specific to PHP >= 6 +if (version_compare(VERSION, '6', 'ge')) { + $API_params['S'] = $API_params['s']; // binary string + $API_params['t'] = array('zstr*', 'int*', 'zend_uchar*'); // text + $API_params['T'] = $API_params['t']; + $API_params['u'] = array('UChar**', 'int*'); // unicode + $API_params['U'] = $API_params['u']; +} + + +/** reports an error, according to its level */ +function error($str, $level = 0) +{ + global $current_file, $current_function, $line; + + if ($level <= REPORT_LEVEL) { + echo substr($current_file, strlen(PHPDIR)+1) . " [$line] $current_function : $str\n"; + } +} + + +/** this updates the global var $line (for error reporting) */ +function update_lineno($offset) +{ + global $lines_offset, $line; + + $left = 0; + $right = $count = count($lines_offset)-1; + + // a nice binary search :) + do { + $mid = intval(($left + $right)/2); + $val = $lines_offset[$mid]; + + if ($val < $offset) { + if (++$mid > $count || $lines_offset[$mid] > $offset) { + $line = $mid; + return; + } else { + $left = $mid; + } + } else if ($val > $offset) { + if ($lines_offset[--$mid] < $offset) { + $line = $mid+1; + return; + } else { + $right = $mid; + } + } else { + $line = $mid+1; + return; + } + } while (true); +} + + +/** parses the sources and fetches its vars name, type and if they are initialized or not */ +function get_vars($txt) +{ + $ret = array(); + preg_match_all('/((?:(?:unsigned|struct)\s+)?\w+)(?:\s*(\*+)\s+|\s+(\**))(\w+(?:\[\s*\w*\s*\])?)\s*(?:(=)[^,;]+)?((?:\s*,\s*\**\s*\w+(?:\[\s*\w*\s*\])?\s*(?:=[^,;]+)?)*)\s*;/S', $txt, $m, PREG_SET_ORDER); + + foreach ($m as $x) { + // the first parameter is special + if (!in_array($x[1], array('else', 'endif', 'return'))) // hack to skip reserved words + $ret[$x[4]] = array($x[1] . $x[2] . $x[3], $x[5]); + + // are there more vars? + if ($x[6]) { + preg_match_all('/(\**)\s*(\w+(?:\[\s*\w*\s*\])?)\s*(=?)/S', $x[6], $y, PREG_SET_ORDER); + foreach ($y as $z) { + $ret[$z[2]] = array($x[1] . $z[1], $z[3]); + } + } + } + +// if ($GLOBALS['current_function'] == 'for_debugging') { print_r($m);print_r($ret); } + return $ret; +} + + +/** run diagnostic checks against one var. */ +function check_param($db, $idx, $exp, $optional) +{ + global $error_few_vars_given; + + if ($idx >= count($db)) { + if (!$error_few_vars_given) { + error("too few variables passed to function"); + $error_few_vars_given = true; + } + return; + } elseif ($db[$idx][0] === '**dummy**') { + return; + } + + if ($db[$idx][1] != $exp) { + error("{$db[$idx][0]}: expected '$exp' but got '{$db[$idx][1]}' [".($idx+1).']'); + } + + if ($optional && !$db[$idx][2]) { + error("optional var not initialized: {$db[$idx][0]} [".($idx+1).']', 1); + + } elseif (!$optional && $db[$idx][2]) { + error("not optional var is initialized: {$db[$idx][0]} [".($idx+1).']', 2); + } +} + + +/** fetch params passed to zend_parse_params*() */ +function get_params($vars, $str) +{ + $ret = array(); + preg_match_all('/(?:\([^)]+\))?(&?)([\w>.()-]+(?:\[\w+\])?)\s*,?((?:\)*\s*=)?)/S', $str, $m, PREG_SET_ORDER); + + foreach ($m as $x) { + $name = $x[2]; + + // little hack for last parameter + if (strpos($name, '(') === false) { + $name = rtrim($name, ')'); + } + + if (empty($vars[$name][0])) { + error("variable not found: '$name'", 3); + $ret[][] = '**dummy**'; + + } else { + $ret[] = array($name, $vars[$name][0] . ($x[1] ? '*' : ''), $vars[$name][1]); + } + + // the end (yes, this is a little hack :P) + if ($x[3]) { + break; + } + } + +// if ($GLOBALS['current_function'] == 'for_debugging') { var_dump($m); var_dump($ret); } + return $ret; +} + + +/** run tests on a function. the code is passed in $txt */ +function check_function($name, $txt, $offset) +{ + global $API_params; + + if (preg_match_all('/zend_parse_parameters(?:_ex\s*\([^,]+,[^,]+|\s*\([^,]+),\s*"([^"]*)"\s*,\s*([^{;]*)/S', $txt, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { + + $GLOBALS['current_function'] = $name; + + foreach ($matches as $m) { + $GLOBALS['error_few_vars_given'] = false; + update_lineno($offset + $m[2][1]); + + $vars = get_vars(substr($txt, 0, $m[0][1])); // limit var search to current location + $params = get_params($vars, $m[2][0]); + $optional = $varargs = false; + $last_last_char = $last_char = ''; + $j = -1; + $len = strlen($m[1][0]); + + for ($i = 0; $i < $len; ++$i) { + switch($char = $m[1][0][$i]) { + // separator for optional parameters + case '|': + if ($optional) { + error("more than one optional separator at char #$i"); + } else { + $optional = true; + if ($i == $len-1) { + error("unnecessary optional separator"); + } + } + break; + + // separate_zval_if_not_ref + case '/': + if (!in_array($last_char, array('r', 'z'))) { + error("the '/' specifier cannot be applied to '$last_char'"); + } + break; + + // nullable arguments + case '!': + if (!in_array($last_char, array('a', 'C', 'f', 'h', 'o', 'O', 'r', 's', 't', 'z', 'Z'))) { + error("the '!' specifier cannot be applied to '$last_char'"); + } + break; + + case '&': + if (version_compare(VERSION, '6', 'ge')) { + if ($last_char == 's' || ($last_last_char == 's' && $last_char == '!')) { + check_param($params, ++$j, 'UConverter*', $optional); + + } else { + error("the '&' specifier cannot be applied to '$last_char'"); + } + } else { + error("unknown char ('&') at column $i"); + } + break; + + case '+': + case '*': + if (version_compare(VERSION, '6', 'ge')) { + if ($varargs) { + error("A varargs specifier can only be used once. repeated char at column $i"); + } else { + check_param($params, ++$j, 'zval****', $optional); + check_param($params, ++$j, 'int*', $optional); + $varargs = true; + } + } else { + error("unknown char ('$char') at column $i"); + } + break; + + default: + if (isset($API_params[$char])) { + foreach($API_params[$char] as $exp) { + check_param($params, ++$j, $exp, $optional); + } + } else { + error("unknown char ('$char') at column $i"); + } + } + + $last_last_char = $last_char; + $last_char = $char; + } + } + } +} + + +/** the main recursion function. splits files in functions and calls the other functions */ +function recurse($path) +{ + foreach (scandir($path) as $file) { + if ($file == '.' || $file == '..' || $file == 'CVS') continue; + + $file = "$path/$file"; + if (is_dir($file)) { + recurse($file); + continue; + } + + // parse only .c and .cpp files + if (substr_compare($file, '.c', -2) && substr_compare($file, '.cpp', -4)) continue; + + $txt = file_get_contents($file); + // remove comments (but preserve the number of lines) + $txt = preg_replace(array('@//[EMAIL PROTECTED]', '@/\*.*\*/@SsUe'), array('', 'preg_replace("/[^\r\n]+/S", "", \'$0\')'), $txt); + + + $split = preg_split('/PHP_(?:NAMED_)?(?:FUNCTION|METHOD)\s*\((\w+(?:,\s*\w+)?)\)/S', $txt, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE); + + if (count($split) < 2) continue; // no functions defined on this file + array_shift($split); // the first part isn't relevant + + + // generate the line offsets array + $j = 0; + $lines = preg_split("/(\r\n?|\n)/S", $txt, -1, PREG_SPLIT_DELIM_CAPTURE); + $lines_offset = array(); + + for ($i = 0; $i < count($lines); ++$i) { + $j += strlen($lines[$i]) + strlen(@$lines[++$i]); + $lines_offset[] = $j; + } + + $GLOBALS['lines_offset'] = $lines_offset; + $GLOBALS['current_file'] = $file; + + + for ($i = 0; $i < count($split); $i+=2) { + // if the /* }}} */ comment is found use it to reduce false positives + // TODO: check the other indexes + list($f) = preg_split('@/\*\s*}}}\s*\*/@S', $split[$i+1][0]); + check_function(preg_replace('/\s*,\s*/S', '::', $split[$i][0]), $f, $split[$i][1]); + } + } +} + +recurse(PHPDIR);
-- PHP CVS Mailing List (http://www.php.net/) To unsubscribe, visit: http://www.php.net/unsub.php