PageRenderTime 44ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/scripts/dev/check_parameters.php

http://github.com/infusion/PHP
PHP | 373 lines | 260 code | 70 blank | 43 comment | 75 complexity | c8f0a9ffac4a0dda4adbf9a6755f6ab9 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, LGPL-2.1, BSD-3-Clause
  1. <?php
  2. /*
  3. +----------------------------------------------------------------------+
  4. | PHP Version 5 |
  5. +----------------------------------------------------------------------+
  6. | Copyright (c) 1997-2007 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: check_parameters.php 226204 2007-01-01 19:32:10Z iliaa $ */
  20. define('REPORT_LEVEL', 2); // 0 reports less false-positives. up to level 5.
  21. define('VERSION', '5.2'); // 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. }