You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

373 lines
10 KiB

20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
  1. <?php
  2. /*
  3. +----------------------------------------------------------------------+
  4. | PHP Version 5 |
  5. +----------------------------------------------------------------------+
  6. | Copyright (c) 1997-2006 The PHP Group |
  7. +----------------------------------------------------------------------+
  8. | This source file is subject to version 3.01 of the PHP license, |
  9. | that is bundled with this package in the file LICENSE, and is |
  10. | available through the world-wide-web at the following url: |
  11. | http://www.php.net/license/3_01.txt |
  12. | If you did not receive a copy of the PHP license and are unable to |
  13. | obtain it through the world-wide-web, please send a note to |
  14. | license@php.net so we can mail you a copy immediately. |
  15. +----------------------------------------------------------------------+
  16. | Author: Nuno Lopes <nlopess@php.net> |
  17. +----------------------------------------------------------------------+
  18. */
  19. /* $Id$ */
  20. define('REPORT_LEVEL', 2); // 0 reports less false-positives. up to level 5.
  21. define('VERSION', '6'); // minimum is 5.2
  22. define('PHPDIR', realpath(dirname(__FILE__) . '/../..'));
  23. // be sure you have enough memory and stack for PHP. pcre will push the limits!
  24. ini_set('pcre.backtrack_limit', 10000000);
  25. // ------------------------ end of config ----------------------------
  26. $API_params = array(
  27. 'a' => array('zval**'), // array as zval*
  28. 'b' => array('zend_bool*'), // boolean
  29. 'C' => array('zend_class_entry**'), // class
  30. 'd' => array('double*'), // double
  31. 'f' => array('zend_fcall_info*', 'zend_fcall_info_cache*'), // function
  32. 'h' => array('HashTable**'), // array as an HashTable*
  33. 'l' => array('long*'), // long
  34. 'o' => array('zval**'), //object
  35. 'O' => array('zval**', 'zend_class_entry*'), // object of given type
  36. 'r' => array('zval**'), // resource
  37. 's' => array('char**', 'int*'), // string
  38. 'z' => array('zval**'), // zval*
  39. 'Z' => array('zval***') // zval**
  40. );
  41. // specific to PHP >= 6
  42. if (version_compare(VERSION, '6', 'ge')) {
  43. $API_params['S'] = $API_params['s']; // binary string
  44. $API_params['t'] = array('zstr*', 'int*', 'zend_uchar*'); // text
  45. $API_params['T'] = $API_params['t'];
  46. $API_params['u'] = array('UChar**', 'int*'); // unicode
  47. $API_params['U'] = $API_params['u'];
  48. }
  49. /** reports an error, according to its level */
  50. function error($str, $level = 0)
  51. {
  52. global $current_file, $current_function, $line;
  53. if ($level <= REPORT_LEVEL) {
  54. if (strpos($current_file,PHPDIR) === 0) {
  55. $filename = substr($current_file, strlen(PHPDIR)+1);
  56. } else {
  57. $filename = $current_file;
  58. }
  59. echo $filename , " [$line] $current_function : $str\n";
  60. }
  61. }
  62. /** this updates the global var $line (for error reporting) */
  63. function update_lineno($offset)
  64. {
  65. global $lines_offset, $line;
  66. $left = 0;
  67. $right = $count = count($lines_offset)-1;
  68. // a nice binary search :)
  69. do {
  70. $mid = intval(($left + $right)/2);
  71. $val = $lines_offset[$mid];
  72. if ($val < $offset) {
  73. if (++$mid > $count || $lines_offset[$mid] > $offset) {
  74. $line = $mid;
  75. return;
  76. } else {
  77. $left = $mid;
  78. }
  79. } else if ($val > $offset) {
  80. if ($lines_offset[--$mid] < $offset) {
  81. $line = $mid+1;
  82. return;
  83. } else {
  84. $right = $mid;
  85. }
  86. } else {
  87. $line = $mid+1;
  88. return;
  89. }
  90. } while (true);
  91. }
  92. /** parses the sources and fetches its vars name, type and if they are initialized or not */
  93. function get_vars($txt)
  94. {
  95. $ret = array();
  96. 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);
  97. foreach ($m as $x) {
  98. // the first parameter is special
  99. if (!in_array($x[1], array('else', 'endif', 'return'))) // hack to skip reserved words
  100. $ret[$x[4]] = array($x[1] . $x[2] . $x[3], $x[5]);
  101. // are there more vars?
  102. if ($x[6]) {
  103. preg_match_all('/(\**)\s*(\w+(?:\[\s*\w*\s*\])?)\s*(=?)/S', $x[6], $y, PREG_SET_ORDER);
  104. foreach ($y as $z) {
  105. $ret[$z[2]] = array($x[1] . $z[1], $z[3]);
  106. }
  107. }
  108. }
  109. // if ($GLOBALS['current_function'] == 'for_debugging') { print_r($m);print_r($ret); }
  110. return $ret;
  111. }
  112. /** run diagnostic checks against one var. */
  113. function check_param($db, $idx, $exp, $optional)
  114. {
  115. global $error_few_vars_given;
  116. if ($idx >= count($db)) {
  117. if (!$error_few_vars_given) {
  118. error("too few variables passed to function");
  119. $error_few_vars_given = true;
  120. }
  121. return;
  122. } elseif ($db[$idx][0] === '**dummy**') {
  123. return;
  124. }
  125. if ($db[$idx][1] != $exp) {
  126. error("{$db[$idx][0]}: expected '$exp' but got '{$db[$idx][1]}' [".($idx+1).']');
  127. }
  128. if ($optional && !$db[$idx][2]) {
  129. error("optional var not initialized: {$db[$idx][0]} [".($idx+1).']', 1);
  130. } elseif (!$optional && $db[$idx][2]) {
  131. error("not optional var is initialized: {$db[$idx][0]} [".($idx+1).']', 2);
  132. }
  133. }
  134. /** fetch params passed to zend_parse_params*() */
  135. function get_params($vars, $str)
  136. {
  137. $ret = array();
  138. preg_match_all('/(?:\([^)]+\))?(&?)([\w>.()-]+(?:\[\w+\])?)\s*,?((?:\)*\s*=)?)/S', $str, $m, PREG_SET_ORDER);
  139. foreach ($m as $x) {
  140. $name = $x[2];
  141. // little hack for last parameter
  142. if (strpos($name, '(') === false) {
  143. $name = rtrim($name, ')');
  144. }
  145. if (empty($vars[$name][0])) {
  146. error("variable not found: '$name'", 3);
  147. $ret[][] = '**dummy**';
  148. } else {
  149. $ret[] = array($name, $vars[$name][0] . ($x[1] ? '*' : ''), $vars[$name][1]);
  150. }
  151. // the end (yes, this is a little hack :P)
  152. if ($x[3]) {
  153. break;
  154. }
  155. }
  156. // if ($GLOBALS['current_function'] == 'for_debugging') { var_dump($m); var_dump($ret); }
  157. return $ret;
  158. }
  159. /** run tests on a function. the code is passed in $txt */
  160. function check_function($name, $txt, $offset)
  161. {
  162. global $API_params;
  163. if (preg_match_all('/zend_parse_parameters(?:_ex\s*\([^,]+,[^,]+|\s*\([^,]+),\s*"([^"]*)"\s*,\s*([^{;]*)/S', $txt, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
  164. $GLOBALS['current_function'] = $name;
  165. foreach ($matches as $m) {
  166. $GLOBALS['error_few_vars_given'] = false;
  167. update_lineno($offset + $m[2][1]);
  168. $vars = get_vars(substr($txt, 0, $m[0][1])); // limit var search to current location
  169. $params = get_params($vars, $m[2][0]);
  170. $optional = $varargs = false;
  171. $last_last_char = $last_char = '';
  172. $j = -1;
  173. $len = strlen($m[1][0]);
  174. for ($i = 0; $i < $len; ++$i) {
  175. switch($char = $m[1][0][$i]) {
  176. // separator for optional parameters
  177. case '|':
  178. if ($optional) {
  179. error("more than one optional separator at char #$i");
  180. } else {
  181. $optional = true;
  182. if ($i == $len-1) {
  183. error("unnecessary optional separator");
  184. }
  185. }
  186. break;
  187. // separate_zval_if_not_ref
  188. case '/':
  189. if (!in_array($last_char, array('r', 'z'))) {
  190. error("the '/' specifier cannot be applied to '$last_char'");
  191. }
  192. break;
  193. // nullable arguments
  194. case '!':
  195. if (!in_array($last_char, array('a', 'C', 'f', 'h', 'o', 'O', 'r', 's', 't', 'z', 'Z'))) {
  196. error("the '!' specifier cannot be applied to '$last_char'");
  197. }
  198. break;
  199. case '&':
  200. if (version_compare(VERSION, '6', 'ge')) {
  201. if ($last_char == 's' || ($last_last_char == 's' && $last_char == '!')) {
  202. check_param($params, ++$j, 'UConverter*', $optional);
  203. } else {
  204. error("the '&' specifier cannot be applied to '$last_char'");
  205. }
  206. } else {
  207. error("unknown char ('&') at column $i");
  208. }
  209. break;
  210. case '+':
  211. case '*':
  212. if (version_compare(VERSION, '6', 'ge')) {
  213. if ($varargs) {
  214. error("A varargs specifier can only be used once. repeated char at column $i");
  215. } else {
  216. check_param($params, ++$j, 'zval****', $optional);
  217. check_param($params, ++$j, 'int*', $optional);
  218. $varargs = true;
  219. }
  220. } else {
  221. error("unknown char ('$char') at column $i");
  222. }
  223. break;
  224. default:
  225. if (isset($API_params[$char])) {
  226. foreach($API_params[$char] as $exp) {
  227. check_param($params, ++$j, $exp, $optional);
  228. }
  229. } else {
  230. error("unknown char ('$char') at column $i");
  231. }
  232. }
  233. $last_last_char = $last_char;
  234. $last_char = $char;
  235. }
  236. }
  237. }
  238. }
  239. /** the main recursion function. splits files in functions and calls the other functions */
  240. function recurse($path)
  241. {
  242. foreach (scandir($path) as $file) {
  243. if ($file == '.' || $file == '..' || $file == 'CVS') continue;
  244. $file = "$path/$file";
  245. if (is_dir($file)) {
  246. recurse($file);
  247. continue;
  248. }
  249. // parse only .c and .cpp files
  250. if (substr_compare($file, '.c', -2) && substr_compare($file, '.cpp', -4)) continue;
  251. $txt = file_get_contents($file);
  252. // remove comments (but preserve the number of lines)
  253. $txt = preg_replace(array('@//.*@S', '@/\*.*\*/@SsUe'), array('', 'preg_replace("/[^\r\n]+/S", "", \'$0\')'), $txt);
  254. $split = preg_split('/PHP_(?:NAMED_)?(?:FUNCTION|METHOD)\s*\((\w+(?:,\s*\w+)?)\)/S', $txt, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE);
  255. if (count($split) < 2) continue; // no functions defined on this file
  256. array_shift($split); // the first part isn't relevant
  257. // generate the line offsets array
  258. $j = 0;
  259. $lines = preg_split("/(\r\n?|\n)/S", $txt, -1, PREG_SPLIT_DELIM_CAPTURE);
  260. $lines_offset = array();
  261. for ($i = 0; $i < count($lines); ++$i) {
  262. $j += strlen($lines[$i]) + strlen(@$lines[++$i]);
  263. $lines_offset[] = $j;
  264. }
  265. $GLOBALS['lines_offset'] = $lines_offset;
  266. $GLOBALS['current_file'] = $file;
  267. for ($i = 0; $i < count($split); $i+=2) {
  268. // if the /* }}} */ comment is found use it to reduce false positives
  269. // TODO: check the other indexes
  270. list($f) = preg_split('@/\*\s*}}}\s*\*/@S', $split[$i+1][0]);
  271. check_function(preg_replace('/\s*,\s*/S', '::', $split[$i][0]), $f, $split[$i][1]);
  272. }
  273. }
  274. }
  275. $dirs = array();
  276. if (isset($argc) && $argc > 1) {
  277. if ($argv[1] == '-h' || $argv[1] == '-help' || $argv[1] == '--help') {
  278. echo <<<HELP
  279. Synopsis:
  280. php check_parameters.php [directories]
  281. HELP;
  282. exit(0);
  283. }
  284. for ($i = 1; $i < $argc; $i++) {
  285. $dirs[] = $argv[$i];
  286. }
  287. } else {
  288. $dirs[] = PHPDIR;
  289. }
  290. foreach($dirs as $dir) {
  291. if (is_dir($dir)) {
  292. if (!is_readable($dir)) {
  293. echo "ERROR: directory '", $dir ,"' is not readable\n";
  294. exit(1);
  295. }
  296. } else {
  297. echo "ERROR: bogus directory '", $dir ,"'\n";
  298. exit(1);
  299. }
  300. }
  301. foreach ($dirs as $dir) {
  302. recurse(realpath($dir));
  303. }