Ported the function call scanner. Built in some command line arguments. Parser no longer maintains the memory-demolishing parsed objects array or incorrectly reports eval as a function call. Scanner reports progress to stderr, results to stdout. Mantis: 2691
git-svn-id: file:///srv/svn/scanner/trunk@3 a0501263-5b7a-4423-a8ba-1edf086583e7
This commit is contained in:
parent
d53e4e74f1
commit
5531fb7a24
3 changed files with 236 additions and 46 deletions
|
@ -1,12 +1,147 @@
|
|||
<?php
|
||||
$FunctionsModule_functions = array();
|
||||
$FunctionsModule_local_functions = array();
|
||||
$FunctionsModule_instance = new FunctionsModule();
|
||||
|
||||
function FunctionsModule_callback_global( $object ) {
|
||||
global $FunctionsModule_functions;
|
||||
$FunctionsModule_functions[$object['name']] = array(
|
||||
'used' => 0,
|
||||
'file' => $object['file']
|
||||
);
|
||||
}
|
||||
function FunctionsModule_callback_local( $object ) {
|
||||
global $FunctionsModule_local_functions;
|
||||
$file = filename( $object['file'] );
|
||||
if( !isset( $FunctionsModule_local_functions[$file] ) ) {
|
||||
$FunctionsModule_local_functions[$file] = array();
|
||||
}
|
||||
$FunctionsModule_local_functions[$file][] = array(
|
||||
'used' => 0,
|
||||
'file' => $object['file']
|
||||
);
|
||||
}
|
||||
|
||||
class FunctionsModule extends ScannerModule {
|
||||
var $include_paths;
|
||||
var $internal_functions;
|
||||
var $included_files;
|
||||
|
||||
function FunctionsModule() {
|
||||
$this->ScannerModule();
|
||||
// Preload library function declarations
|
||||
$library_folders = array(
|
||||
'libs',
|
||||
'inc',
|
||||
'incs',
|
||||
'includes'
|
||||
);
|
||||
global $base_path;
|
||||
$parser = new PHPParser( PHPPARSER_FETCH_FUNCTIONS );
|
||||
$parser->registerCallback( 'FunctionsModule_callback_global' );
|
||||
$parser->registerCallback( 'FunctionsModule_callback_local' );
|
||||
$this->include_paths = array();
|
||||
$paths = array();
|
||||
$path = realpath( $base_path );
|
||||
if( !in_array( $path, $paths ) ) {
|
||||
$paths[] = $path;
|
||||
$contents = scandir( $path );
|
||||
foreach( $contents as $node ) {
|
||||
if(
|
||||
is_dir( "$path/$node" )
|
||||
&& in_array( strtolower( $node ), $library_folders )
|
||||
&& !in_array( "$path/$node", $this->include_paths )
|
||||
) {
|
||||
$this->include_paths[] = "$path/$node";
|
||||
}
|
||||
}
|
||||
}
|
||||
$include_files = array();
|
||||
$functions = array();
|
||||
foreach( $this->include_paths as $path ) {
|
||||
exec( "find $path -iname '*.php' 2>/dev/null", $output, $result );
|
||||
$include_files = array_merge( $include_files, $output );
|
||||
}
|
||||
err( "Parsing function libraries...\n" );
|
||||
$counter = 0; $total = count( $include_files ); $lastpct = 0;
|
||||
foreach( $include_files as $file ) {
|
||||
$counter++;
|
||||
if( $counter == 1 ) { err( 0 ); }
|
||||
else {
|
||||
$pct = intval( $counter / $total * 100 );
|
||||
if( $pct != $lastpct && $pct % 2 == 0 ) {
|
||||
err( $pct % 10 == 0 ? $pct : '.' );
|
||||
$lastpct = $pct;
|
||||
}
|
||||
}
|
||||
$parser->parseFile( $file );
|
||||
}
|
||||
err( "\n" );
|
||||
$this->internal_functions = get_defined_functions();
|
||||
$this->internal_functions = $this->internal_functions['internal'];
|
||||
}
|
||||
function parserCallback( $object ) {
|
||||
global $FunctionsModule_functions;
|
||||
global $FunctionsModule_local_functions;
|
||||
switch( $object['type'] ) {
|
||||
case PHPPARSER_INCLUDE:
|
||||
$this->included_files[] = $object;
|
||||
if( $object['name'] == 'global.php' ) {
|
||||
$object['name'] = 'libs/security/lib_security_input.php';
|
||||
$this->included_files[] = $object;
|
||||
$object['name'] = 'libs/get/lib_get_portal.php';
|
||||
$this->included_files[] = $object;
|
||||
$object['name'] = 'libs/logging/lib_logging_errors.php';
|
||||
$this->included_files[] = $object;
|
||||
}
|
||||
break;
|
||||
case PHPPARSER_FUNCTION_CALL:
|
||||
if( !in_array( $object['name'], array_keys( $FunctionsModule_functions ) ) ) {
|
||||
if(
|
||||
!in_array( $object['name'], $this->internal_functions )
|
||||
&& !in_array( $object['name'], $FunctionsModule_local_functions[filename( $object['file'] )] )
|
||||
) {
|
||||
$this->fault( $object, FAULT_MAJOR, "Undefined function '{$object['name']}'" );
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$include = filename( $FunctionsModule_functions[$object['name']]['file'] );
|
||||
if( $include != filename( $object['file'] ) ) {
|
||||
//$functions[$object['name']]['used']++;
|
||||
//if( !isset( $file_requires[$file]['libs'][$include] ) ) { $file_requires[$file]['libs'][$include] = array( 'lines' => array(), 'calls' => array() ); }
|
||||
//$lib =& $file_requires[$file]['libs'][$include];
|
||||
|
||||
$bad = $warning = true;
|
||||
foreach( $this->included_files as $libinc ) {
|
||||
if( $libinc['name'] != $include || !in_array( $libinc['in_function'], array( '', $object['in_function'] ) ) ) { continue; }
|
||||
$inc = $libinc;
|
||||
$bad = false;
|
||||
$warning = !$bad && !in_array( $libinc['block'], $object['open_blocks'] );
|
||||
if( !$bad && !$warning ) { break; }
|
||||
}
|
||||
$open_blocks = is_array( $object['open_blocks'] ) ? implode( ',', $object['open_blocks'] ) : '';
|
||||
$info = "called[l={$object['line']};b={$object['block']};d={$object['depth']}]" . ( !$bad ? ", required[l={$inc['line']};b={$inc['block']};d={$inc['depth']}] open[{$open_blocks}]" : '' );
|
||||
if( $bad === true ) {
|
||||
$this->fault( $object, FAULT_MAJOR, "Undefined function '{$object['name']}' $info" );
|
||||
} elseif( $warning === true ) {
|
||||
$this->fault( $object, FAULT_MEDIUM, "Potentially undefined function '{$object['name']}' $info" );
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
function preScan( $filename ) {
|
||||
parent::preScan( $filename );
|
||||
global $FunctionsModule_local_functions;
|
||||
$FunctionsModule_local_functions = array();
|
||||
$FunctionsModule_local_functions[filename( $filename)] = array();
|
||||
$parser = new PHPParser( PHPPARSER_FETCH_FUNCTIONS );
|
||||
$parser->registerCallback( 'FunctionsModule_callback_local' );
|
||||
$parser->parse( $filename );
|
||||
$this->included_files = array();
|
||||
}
|
||||
}
|
||||
|
||||
$modules[] = new FunctionsModule();
|
||||
$modules[] = $FunctionsModule_instance;
|
||||
?>
|
||||
|
|
19
parser.php
19
parser.php
|
@ -22,7 +22,6 @@ class PHPParser {
|
|||
var $file_name;
|
||||
var $internal_functions;
|
||||
var $defined_functions;
|
||||
var $parsed_objects;
|
||||
var $callbacks;
|
||||
|
||||
function PHPParser( $fetch_mode = PHPPARSER_FETCH_ALL, $functionlist = array() ) {
|
||||
|
@ -38,7 +37,6 @@ class PHPParser {
|
|||
}
|
||||
$this->file_name = '';
|
||||
$this->defined_functions = array();
|
||||
$this->parsed_objects = array();
|
||||
}
|
||||
|
||||
function addFunctionDefinitions( $functionlist ) {
|
||||
|
@ -67,7 +65,6 @@ class PHPParser {
|
|||
foreach( $this->callbacks as $callback ) {
|
||||
call_user_func( $callback['function'], $object );
|
||||
}
|
||||
$this->parsed_objects[] = $object;
|
||||
}
|
||||
|
||||
function parseFile( $file ) {
|
||||
|
@ -125,7 +122,7 @@ class PHPParser {
|
|||
case '}':
|
||||
array_pop( $open_blocks );
|
||||
$depth = count( $open_blocks ) - 1;
|
||||
$block = $depth == 0 ? 0 : $open_blocks[$depth];
|
||||
$block = $depth == 0 ? 0 : ( isset( $open_blocks[$depth] ) ? $open_blocks[$depth] : 0 );
|
||||
if( !empty( $class ) && $class['block'] == $block ) { $class = $functionname = null; }
|
||||
if( !empty( $function ) && $function['block'] == $block ) { $function = $functionname = null; }
|
||||
if( in_array( $block, $switch ) ) {
|
||||
|
@ -256,20 +253,6 @@ class PHPParser {
|
|||
$depth = count( $open_blocks ) - 1;
|
||||
break;
|
||||
//*/
|
||||
case T_EVAL:
|
||||
if( (bool)($this->fetch_mode & PHPPARSER_FETCH_INTERNAL) ) {
|
||||
$this->foundObject( array(
|
||||
'type' => PHPPARSER_FUNCTION_CALL,
|
||||
'name' => $text,
|
||||
'file' => $this->file_name,
|
||||
'line' => $line,
|
||||
'block' => $block,
|
||||
'depth' => $depth,
|
||||
'in_class' => $classname,
|
||||
'in_function' => $functionname
|
||||
) );
|
||||
}
|
||||
break;
|
||||
}
|
||||
if( !in_array( $id, array( T_WHITESPACE, /*T_COMMENT, T_DOC_COMMENT,*/ T_STRING ) ) ) { $string = null; $last_token = $id; }
|
||||
if( (bool)($this->fetch_mode & PHPPARSER_FETCH_CONSTRUCTS) && in_array( $id, array(
|
||||
|
|
126
scanner.php
126
scanner.php
|
@ -1,13 +1,27 @@
|
|||
<?php
|
||||
require_once( 'parser.php' );
|
||||
|
||||
/*
|
||||
define( 'FAULT_MINOR', 0 );
|
||||
define( 'FAULT_MEDIUM', 1 );
|
||||
define( 'FAULT_MAJOR', 2 );
|
||||
*/
|
||||
|
||||
$modules = array();
|
||||
$stderr = fopen( 'php://stderr', 'w' );
|
||||
$config = array(
|
||||
'svn' => false,
|
||||
'modules' => array(),
|
||||
);
|
||||
$help = "Usage: {$argv[0]} [options] file|path [file|path ...]
|
||||
Options:
|
||||
-h Display this usage information
|
||||
--help
|
||||
|
||||
-m modulename Loads the requested scanning module. If this
|
||||
--module modulename parameter is not specified, all available modules
|
||||
will be loaded.
|
||||
|
||||
--svn Enables SVN integration
|
||||
";
|
||||
|
||||
class ScannerModule {
|
||||
var $faults;
|
||||
|
@ -16,29 +30,35 @@ class ScannerModule {
|
|||
function ScannerModule() {
|
||||
$this->faults = array();
|
||||
$this->blame = array();
|
||||
err( "Initializing " . get_class( $this ) . "...\n" );
|
||||
}
|
||||
function fault( $object, $level, $reason = '' ) {
|
||||
global $config;
|
||||
$object['file'] = filename( $object['file'] );
|
||||
$this->faults[] = array(
|
||||
'object' => $object,
|
||||
'level' => $level,
|
||||
'reason' => $reason,
|
||||
'svn' => $this->blame[$object['line']]
|
||||
'svn' => ( $config['svn'] === true ) ? $this->blame[$object['line']] : ''
|
||||
);
|
||||
}
|
||||
function parserCallback( $object ) {
|
||||
}
|
||||
function preScan( $filename ) {
|
||||
$this->blame = array();
|
||||
$output = array();
|
||||
exec( "svn blame '$filename' 2>/dev/null", $output, $result );
|
||||
if( $result == 0 ) {
|
||||
foreach( $output as $line => $text ) {
|
||||
$matches = array();
|
||||
preg_match( '/^\s*(\d+)\s+([^\s]+)/', $text, $matches );
|
||||
$this->blame[$line + 1] = array(
|
||||
'author' => $matches[2],
|
||||
'revision' => $matches[1]
|
||||
);
|
||||
global $config;
|
||||
if( $config['svn'] === true ) {
|
||||
$this->blame = array();
|
||||
$output = array();
|
||||
exec( "svn blame '$filename' 2>/dev/null", $output, $result );
|
||||
if( $result == 0 ) {
|
||||
foreach( $output as $line => $text ) {
|
||||
$matches = array();
|
||||
preg_match( '/^\s*(\d+)\s+([^\s]+)/', $text, $matches );
|
||||
$this->blame[$line + 1] = array(
|
||||
'author' => $matches[2],
|
||||
'revision' => $matches[1]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,33 +72,85 @@ function _callback( $object ) {
|
|||
$module->parserCallback( $object );
|
||||
}
|
||||
}
|
||||
function filename( $filename ) {
|
||||
global $base_path;
|
||||
$filename = realpath( $filename );
|
||||
if( strpos( $filename, $base_path ) === 0 ) {
|
||||
$filename = substr( $filename, strlen( $base_path ) );
|
||||
}
|
||||
return $filename;
|
||||
}
|
||||
function err( $string ) {
|
||||
global $stderr;
|
||||
fputs( $stderr, $string );
|
||||
}
|
||||
|
||||
|
||||
// Handle application arguments
|
||||
$files = array();
|
||||
$base_path = false;
|
||||
for( $i = 1; $i < $argc; $i++ ) {
|
||||
switch( $argv[$i] ) {
|
||||
case '-h':
|
||||
case '--help':
|
||||
die( $help );
|
||||
break;
|
||||
case '-m':
|
||||
case '--module':
|
||||
$config['modules'][] = $argv[++$i];
|
||||
break;
|
||||
case '--svn':
|
||||
$config['svn'] = true;
|
||||
break;
|
||||
default:
|
||||
if( file_exists( $argv[$i] ) && strtolower( substr( $argv[$i], -4 ) ) == '.php' ) {
|
||||
$base_path = ( $base_path === false ) ? realpath( dirname( $argv[$i] ) ) . '/' : $base_path;
|
||||
$files[] = $argv[$i];
|
||||
} else if( is_dir( $argv[$i] ) ) {
|
||||
$base_path = ( $base_path === false ) ? realpath( $argv[$i] ) . '/' : $base_path;
|
||||
exec( "find $base_path -iname '*.php' 2>/dev/null", $output, $result );
|
||||
$files = array_merge( $files, $output );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if( count( $files ) == 0 ) {
|
||||
die( $help );
|
||||
}
|
||||
|
||||
// Dig into the modules folder and load up what we find
|
||||
$module_files = scandir( 'modules' );
|
||||
foreach( $module_files as $module_file ) {
|
||||
if( strtolower( substr( $module_file, -4 ) ) == '.php' ) {
|
||||
require_once( "modules/{$module_file}" );
|
||||
$module = substr( $module_file, 0, strlen( $module_file ) - 4 );
|
||||
if(
|
||||
count( $modules ) == 0
|
||||
|| ( in_array( $module, $config['modules'] ) )
|
||||
) {
|
||||
require_once( "modules/{$module_file}" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$parser = new PHPParser( PHPPARSER_FETCH_EXPRESSIONS | PHPPARSER_FETCH_CALLS | PHPPARSER_FETCH_INTERNAL | PHPPARSER_FETCH_CONSTRUCTS );
|
||||
$parser = new PHPParser();
|
||||
$parser->registerCallback( '_callback' );
|
||||
|
||||
$files = array();
|
||||
for( $i = 1; $i < $argc; $i++ ) {
|
||||
if( file_exists( $argv[$i] ) && strtolower( substr( $argv[$i], -4 ) ) == '.php' ) {
|
||||
$files[] = $argv[$i];
|
||||
}
|
||||
}
|
||||
|
||||
if( count( $files ) == 0 ) {
|
||||
die( "No files were specified.\n" );
|
||||
}
|
||||
|
||||
err( "Parsing files...\n" );
|
||||
$counter = 0; $total = count( $files ); $lastpct = 0;
|
||||
foreach( $files as $file ) {
|
||||
$counter++;
|
||||
if( $counter == 1 ) { err( 0 ); }
|
||||
else {
|
||||
$pct = intval( $counter / $total * 100 );
|
||||
if( $pct != $lastpct && $pct % 2 == 0 ) {
|
||||
err( $pct % 10 == 0 ? $pct : '.' );
|
||||
$lastpct = $pct;
|
||||
}
|
||||
}
|
||||
foreach( $modules as $module ) { $module->preScan( $file ); }
|
||||
$parser->parseFile( $file );
|
||||
}
|
||||
err( "\n" );
|
||||
|
||||
foreach( $modules as $module ) {
|
||||
foreach( $module->faults as $fault ) {
|
||||
|
|
Loading…
Reference in a new issue