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:
Correl Roush 2008-02-14 21:27:50 +00:00
parent d53e4e74f1
commit 5531fb7a24
3 changed files with 236 additions and 46 deletions

View file

@ -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;
?>

View file

@ -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(

View file

@ -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 ) {