Back to index

php5  5.3.10
check_parameters.php
Go to the documentation of this file.
00001 <?php
00002 /*
00003   +----------------------------------------------------------------------+
00004   | PHP Version 5                                                        |
00005   +----------------------------------------------------------------------+
00006   | Copyright (c) 1997-2007 The PHP Group                                |
00007   +----------------------------------------------------------------------+
00008   | This source file is subject to version 3.01 of the PHP license,      |
00009   | that is bundled with this package in the file LICENSE, and is        |
00010   | available through the world-wide-web at the following url:           |
00011   | http://www.php.net/license/3_01.txt                                  |
00012   | If you did not receive a copy of the PHP license and are unable to   |
00013   | obtain it through the world-wide-web, please send a note to          |
00014   | license@php.net so we can mail you a copy immediately.               |
00015   +----------------------------------------------------------------------+
00016   | Author: Nuno Lopes <nlopess@php.net>                                 |
00017   +----------------------------------------------------------------------+
00018 */
00019 
00020 /* $Id: check_parameters.php 226204 2007-01-01 19:32:10Z iliaa $ */
00021 
00022 
00023 define('REPORT_LEVEL', 2); // 0 reports less false-positives. up to level 5.
00024 define('VERSION', '5.2');  // minimum is 5.2
00025 define('PHPDIR', realpath(dirname(__FILE__) . '/../..'));
00026 
00027 
00028 // be sure you have enough memory and stack for PHP. pcre will push the limits!
00029 ini_set('pcre.backtrack_limit', 10000000);
00030 
00031 
00032 // ------------------------ end of config ----------------------------
00033 
00034 
00035 $API_params = array(
00036        'a' => array('zval**'), // array as zval*
00037        'b' => array('zend_bool*'), // boolean
00038        'C' => array('zend_class_entry**'), // class
00039        'd' => array('double*'), // double
00040        'f' => array('zend_fcall_info*', 'zend_fcall_info_cache*'), // function
00041        'h' => array('HashTable**'), // array as an HashTable*
00042        'l' => array('long*'), // long
00043        'o' => array('zval**'), //object
00044        'O' => array('zval**', 'zend_class_entry*'), // object of given type
00045        'r' => array('zval**'), // resource
00046        's' => array('char**', 'int*'), // string
00047        'z' => array('zval**'), // zval*
00048        'Z' => array('zval***') // zval**
00049 );
00050 
00051 // specific to PHP >= 6
00052 if (version_compare(VERSION, '6', 'ge')) {
00053        $API_params['S'] = $API_params['s']; // binary string
00054        $API_params['t'] = array('zstr*', 'int*', 'zend_uchar*'); // text
00055        $API_params['T'] = $API_params['t'];
00056        $API_params['u'] = array('UChar**', 'int*'); // unicode
00057        $API_params['U'] = $API_params['u'];
00058 }
00059 
00060 
00062 function error($str, $level = 0)
00063 {
00064        global $current_file, $current_function, $line;
00065 
00066        if ($level <= REPORT_LEVEL) {
00067               if (strpos($current_file,PHPDIR) === 0) {
00068                      $filename = substr($current_file, strlen(PHPDIR)+1); 
00069               } else {
00070                      $filename = $current_file;
00071               }
00072               echo $filename , " [$line] $current_function : $str\n";
00073        }
00074 }
00075 
00076 
00078 function update_lineno($offset)
00079 {
00080        global $lines_offset, $line;
00081 
00082        $left  = 0;
00083        $right = $count = count($lines_offset)-1;
00084 
00085        // a nice binary search :)
00086        do {
00087               $mid = intval(($left + $right)/2);
00088               $val = $lines_offset[$mid];
00089 
00090               if ($val < $offset) {
00091                      if (++$mid > $count || $lines_offset[$mid] > $offset) {
00092                             $line = $mid;
00093                             return;
00094                      } else {
00095                             $left = $mid;
00096                      }
00097               } else if ($val > $offset) {
00098                      if ($lines_offset[--$mid] < $offset) {
00099                             $line = $mid+1;
00100                             return;
00101                      } else {
00102                             $right = $mid;
00103                      }
00104               } else {
00105                      $line = $mid+1;
00106                      return;
00107               }
00108        } while (true);
00109 }
00110 
00111 
00113 function get_vars($txt)
00114 {
00115        $ret =  array();
00116        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);
00117 
00118        foreach ($m as $x) {
00119               // the first parameter is special
00120               if (!in_array($x[1], array('else', 'endif', 'return'))) // hack to skip reserved words
00121                      $ret[$x[4]] = array($x[1] . $x[2] . $x[3], $x[5]);
00122 
00123               // are there more vars?
00124               if ($x[6]) {
00125                      preg_match_all('/(\**)\s*(\w+(?:\[\s*\w*\s*\])?)\s*(=?)/S', $x[6], $y, PREG_SET_ORDER);
00126                      foreach ($y as $z) {
00127                             $ret[$z[2]] = array($x[1] . $z[1], $z[3]);
00128                      }
00129               }
00130        }
00131 
00132 //     if ($GLOBALS['current_function'] == 'for_debugging') { print_r($m);print_r($ret); }
00133        return $ret;
00134 }
00135 
00136 
00138 function check_param($db, $idx, $exp, $optional)
00139 {
00140        global $error_few_vars_given;
00141 
00142        if ($idx >= count($db)) {
00143               if (!$error_few_vars_given) {
00144                      error("too few variables passed to function");
00145                      $error_few_vars_given = true;
00146               }
00147               return;
00148        } elseif ($db[$idx][0] === '**dummy**') {
00149               return;
00150        }
00151 
00152        if ($db[$idx][1] != $exp) {
00153               error("{$db[$idx][0]}: expected '$exp' but got '{$db[$idx][1]}' [".($idx+1).']');
00154        }
00155 
00156        if ($optional && !$db[$idx][2]) {
00157               error("optional var not initialized: {$db[$idx][0]} [".($idx+1).']', 1);
00158 
00159        } elseif (!$optional && $db[$idx][2]) {
00160               error("not optional var is initialized: {$db[$idx][0]} [".($idx+1).']', 2);
00161        }
00162 }
00163 
00164 
00166 function get_params($vars, $str)
00167 {
00168        $ret = array();
00169        preg_match_all('/(?:\([^)]+\))?(&?)([\w>.()-]+(?:\[\w+\])?)\s*,?((?:\)*\s*=)?)/S', $str, $m, PREG_SET_ORDER);
00170 
00171        foreach ($m as $x) {
00172               $name = $x[2];
00173 
00174               // little hack for last parameter
00175               if (strpos($name, '(') === false) {
00176                      $name = rtrim($name, ')');
00177               }
00178 
00179               if (empty($vars[$name][0])) {
00180                      error("variable not found: '$name'", 3);
00181                      $ret[][] = '**dummy**';
00182 
00183               } else {
00184                      $ret[] = array($name, $vars[$name][0] . ($x[1] ? '*' : ''), $vars[$name][1]);
00185               }
00186 
00187               // the end (yes, this is a little hack :P)
00188               if ($x[3]) {
00189                      break;
00190               }
00191        }
00192 
00193 //     if ($GLOBALS['current_function'] == 'for_debugging') { var_dump($m); var_dump($ret); }
00194        return $ret;
00195 }
00196 
00197 
00199 function check_function($name, $txt, $offset)
00200 {
00201        global $API_params;
00202 
00203        if (preg_match_all('/zend_parse_parameters(?:_ex\s*\([^,]+,[^,]+|\s*\([^,]+),\s*"([^"]*)"\s*,\s*([^{;]*)/S', $txt, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
00204 
00205               $GLOBALS['current_function'] = $name;
00206 
00207               foreach ($matches as $m) {
00208                      $GLOBALS['error_few_vars_given'] = false;
00209                      update_lineno($offset + $m[2][1]);
00210 
00211                      $vars = get_vars(substr($txt, 0, $m[0][1])); // limit var search to current location
00212                      $params = get_params($vars, $m[2][0]);
00213                      $optional = $varargs = false;
00214                      $last_last_char = $last_char = '';
00215                      $j = -1;
00216                      $len = strlen($m[1][0]);
00217 
00218                      for ($i = 0; $i < $len; ++$i) {
00219                             switch($char = $m[1][0][$i]) {
00220                                    // separator for optional parameters
00221                                    case '|':
00222                                           if ($optional) {
00223                                                  error("more than one optional separator at char #$i");
00224                                           } else {
00225                                                  $optional = true;
00226                                                  if ($i == $len-1) {
00227                                                         error("unnecessary optional separator");
00228                                                  }
00229                                           }
00230                                    break;
00231 
00232                                    // separate_zval_if_not_ref
00233                                    case '/':
00234                                           if (!in_array($last_char, array('r', 'z'))) {
00235                                                  error("the '/' specifier cannot be applied to '$last_char'");
00236                                           }
00237                                    break;
00238 
00239                                    // nullable arguments
00240                                    case '!':
00241                                           if (!in_array($last_char, array('a', 'C', 'f', 'h', 'o', 'O', 'r', 's', 't', 'z', 'Z'))) {
00242                                                  error("the '!' specifier cannot be applied to '$last_char'");
00243                                           }
00244                                    break;
00245 
00246                                    case '&':
00247                                           if (version_compare(VERSION, '6', 'ge')) {
00248                                                  if ($last_char == 's' || ($last_last_char == 's' && $last_char == '!')) {
00249                                                         check_param($params, ++$j, 'UConverter*', $optional);
00250 
00251                                                  } else {
00252                                                         error("the '&' specifier cannot be applied to '$last_char'");
00253                                                  }
00254                                           } else {
00255                                                  error("unknown char ('&') at column $i");
00256                                           }
00257                                    break;
00258 
00259                                    case '+':
00260                                    case '*':
00261                                           if (version_compare(VERSION, '6', 'ge')) {
00262                                                  if ($varargs) {
00263                                                         error("A varargs specifier can only be used once. repeated char at column $i");
00264                                                  } else {
00265                                                         check_param($params, ++$j, 'zval****', $optional);
00266                                                         check_param($params, ++$j, 'int*', $optional);
00267                                                         $varargs = true;
00268                                                  }
00269                                           } else {
00270                                                  error("unknown char ('$char') at column $i");
00271                                           }
00272                                    break;
00273 
00274                                    default:
00275                                           if (isset($API_params[$char])) {
00276                                                  foreach($API_params[$char] as $exp) {
00277                                                         check_param($params, ++$j, $exp, $optional);
00278                                                  }
00279                                           } else {
00280                                                  error("unknown char ('$char') at column $i");
00281                                           }
00282                             }
00283 
00284                             $last_last_char = $last_char;
00285                             $last_char = $char;
00286                      }
00287               }
00288        }
00289 }
00290 
00291 
00293 function recurse($path)
00294 {
00295        foreach (scandir($path) as $file) {
00296               if ($file == '.' || $file == '..' || $file == 'CVS') continue;
00297 
00298               $file = "$path/$file";
00299               if (is_dir($file)) {
00300                      recurse($file);
00301                      continue;
00302               }
00303 
00304               // parse only .c and .cpp files
00305               if (substr_compare($file, '.c', -2) && substr_compare($file, '.cpp', -4)) continue;
00306 
00307               $txt = file_get_contents($file);
00308               // remove comments (but preserve the number of lines)
00309               $txt = preg_replace(array('@//.*@S', '@/\*.*\*/@SsUe'), array('', 'preg_replace("/[^\r\n]+/S", "", \'$0\')'), $txt);
00310 
00311 
00312               $split = preg_split('/PHP_(?:NAMED_)?(?:FUNCTION|METHOD)\s*\((\w+(?:,\s*\w+)?)\)/S', $txt, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE);
00313 
00314               if (count($split) < 2) continue; // no functions defined on this file
00315               array_shift($split); // the first part isn't relevant
00316 
00317 
00318               // generate the line offsets array
00319               $j = 0;
00320               $lines = preg_split("/(\r\n?|\n)/S", $txt, -1, PREG_SPLIT_DELIM_CAPTURE);
00321               $lines_offset = array();
00322 
00323               for ($i = 0; $i < count($lines); ++$i) {
00324                      $j += strlen($lines[$i]) + strlen(@$lines[++$i]);
00325                      $lines_offset[] = $j;
00326               }
00327 
00328               $GLOBALS['lines_offset'] = $lines_offset;
00329               $GLOBALS['current_file'] = $file;
00330 
00331 
00332               for ($i = 0; $i < count($split); $i+=2) {
00333                      // if the /* }}} */ comment is found use it to reduce false positives
00334                      // TODO: check the other indexes
00335                      list($f) = preg_split('@/\*\s*}}}\s*\*/@S', $split[$i+1][0]);
00336                      check_function(preg_replace('/\s*,\s*/S', '::', $split[$i][0]), $f, $split[$i][1]);
00337               }
00338        }
00339 }
00340 
00341 $dirs = array();
00342 
00343 if (isset($argc) && $argc > 1) {
00344        if ($argv[1] == '-h' || $argv[1] == '-help' || $argv[1] == '--help') {
00345               echo <<<HELP
00346 Synopsis:
00347     php check_parameters.php [directories]
00348 
00349 HELP;
00350               exit(0);
00351        }
00352        for ($i = 1; $i < $argc; $i++) {
00353               $dirs[] = $argv[$i];
00354        }
00355 } else { 
00356        $dirs[] = PHPDIR;
00357 }
00358 
00359 foreach($dirs as $dir) {
00360        if (is_dir($dir)) {
00361               if (!is_readable($dir)) {
00362                      echo "ERROR: directory '", $dir ,"' is not readable\n";
00363                      exit(1);
00364               }
00365        } else {
00366               echo "ERROR: bogus directory '", $dir ,"'\n";
00367               exit(1);
00368        }
00369 }
00370 
00371 foreach ($dirs as $dir) {
00372        recurse(realpath($dir));
00373 }