Created the scanner app, along with two simple modules. The parser is now event driven... use of the parsed_objects array is now deprecated and will be removed once the old function call report that relies on it is replaced. Mantis: 2691

git-svn-id: file:///srv/svn/scanner/trunk@2 a0501263-5b7a-4423-a8ba-1edf086583e7
This commit is contained in:
Correl Roush 2008-02-13 22:39:27 +00:00
parent 4244ed6d8b
commit d53e4e74f1
6 changed files with 241 additions and 33 deletions

12
modules/functions.php Normal file
View file

@ -0,0 +1,12 @@
<?php
class FunctionsModule extends ScannerModule {
function FunctionsModule() {
$this->ScannerModule();
}
function preScan( $filename ) {
parent::preScan( $filename );
}
}
$modules[] = new FunctionsModule();
?>

28
modules/lint.php Normal file
View file

@ -0,0 +1,28 @@
<?php
class LintModule extends ScannerModule {
function LintModule() {
$this->ScannerModule();
}
function preScan( $filename ) {
parent::preScan( $filename );
$output = array();
exec( "php -l '$filename' 2>/dev/null", $output, $result );
if( $result != 0 ) {
foreach( $output as $linterror ) {
$matches = array();
if( preg_match( '/error:.*?on line (\d+)$/i', $linterror, $matches ) == 0 ) { continue; }
$this->fault(
array(
'file' => $filename,
'line' => $matches[1]
),
FAULT_MAJOR,
$linterror
);
}
}
}
}
$modules[] = new LintModule();
?>

45
modules/pattern.php Normal file
View file

@ -0,0 +1,45 @@
<?php
class PatternModule extends ScannerModule {
var $filters = array(
array(
'type' => PHPPARSER_EXPRESSION,
'desc' => 'Echoing Sql',
'level' => FAULT_MEDIUM,
'pattern' => '/echo[\(\s].*?\$sql/i'
),
array(
'type' => PHPPARSER_LANGUAGE_CONSTRUCT,
'desc' => 'Evil Eval',
'level' => FAULT_MEDIUM,
'pattern' => '/^eval$/i'
),
array(
'type' => PHPPARSER_FUNCTION_CALL,
'desc' => 'PRINT_R or VAR_DUMP',
'level' => FAULT_MEDIUM,
'pattern' => '/^(print_r|var_dump)$/i'
),
array(
'type' => PHPPARSER_EXPRESSION,
'desc' => 'Developer Email',
'level' => FAULT_MINOR,
'pattern' => '/(?<!dev|qa)@payquik\.com/'
),
);
function PatternModule() {
$this->ScannerModule();
}
function parserCallback( $object ) {
foreach( $this->filters as $filter ) {
if( $object['type'] == $filter['type'] ) {
if( preg_match( $filter['pattern'], $object['name'] ) > 0 ) {
$this->fault( $object, $filter['level'], "Triggered Filter '{$filter['desc']}'" );
}
}
}
}
}
$modules[] = new PatternModule();
?>

View file

@ -16,8 +16,6 @@ define( 'PHPPARSER_INCLUDE', 4 );
define( 'PHPPARSER_EXPRESSION', 5 );
define( 'PHPPARSER_LANGUAGE_CONSTRUCT', 6 );
class PHPParser {
var $fetch_mode;
@ -25,11 +23,13 @@ class PHPParser {
var $internal_functions;
var $defined_functions;
var $parsed_objects;
var $callbacks;
function PHPParser( $fetch_mode = PHPPARSER_FETCH_ALL, $functionlist = array() ) {
$this->reset( $fetch_mode );
$this->internal_functions = get_defined_functions(); $this->internal_functions = $this->internal_functions['internal'];
$this->addFunctionDefinitions( $functionlist );
$this->callbacks = array();
}
function reset( $fetch_mode = null ) {
@ -47,6 +47,28 @@ class PHPParser {
}
}
function registerCallback( $function_name, $fetch_mode = PHPPARSER_FETCH_ALL ) {
if( function_exists( $function_name ) ) {
$this->callbacks[] = array(
'function' => $function_name,
'fetch' => $fetch_mode
);
return count( $this->callbacks );
} else {
return false;
}
}
function foundObject( $object ) {
if( !is_array( $object ) ) {
// Now how the hell did that happen?
return false;
}
foreach( $this->callbacks as $callback ) {
call_user_func( $callback['function'], $object );
}
$this->parsed_objects[] = $object;
}
function parseFile( $file ) {
if( !file_exists( $file ) || !is_readable( $file ) ) { return false; }
@ -76,7 +98,7 @@ class PHPParser {
foreach( $tokens as $token ) {
//echo ( is_string( $token ) ? 'CHAR: ' . $token : token_name( $token[0] ) . ': ' . $token[1] ) . "\n";
if( !in_array( 0, $open_blocks ) ) {
echo "PARSER ERROR: LOST ZERO BLOCK AT FILE {$this->file_name} LINE $line\n";
//echo "PARSER ERROR: LOST ZERO BLOCK AT FILE {$this->file_name} LINE $line\n";
$open_blocks = array_merge( array(0), $open_blocks );
}
$classname = isset( $class['name'] ) ? $class['name'] : '';
@ -121,7 +143,7 @@ class PHPParser {
&& $last_token != T_NEW
//&& ( (bool)($this->fetch_mode & PHPPARSER_FETCH_INTERNAL) && !in_array( $string, $this->internal_functions ) )
) {
$this->parsed_objects[] = array(
$this->foundObject( array(
'type' => PHPPARSER_FUNCTION_CALL,
'name' => strtolower( $string ),
'file' => $this->file_name,
@ -131,23 +153,23 @@ class PHPParser {
'in_class' => $classname,
'in_function' => $functionname,
'open_blocks' => $open_blocks
);
) );
}
break;
}
if( !in_array( $token, array( '(', ')' ) ) ) { $last_token = null; }
if( (bool)($this->fetch_mode & PHPPARSER_FETCH_EXPRESSIONS) && in_array( $token, array( '{', '}', ';', '=', '?', ':' ) ) && strlen( trim( $expression ) ) > 0 ) {
$this->parsed_objects[] = array(
'type' => PHPPARSER_EXPRESSION,
'name' => trim( $expression ),
'file' => $this->file_name,
'line' => $line,
'block' => $block,
'depth' => $depth,
'in_class' => $classname,
'in_function' => $functionname,
'open_blocks' => $open_blocks
);
$this->foundObject( array(
'type' => PHPPARSER_EXPRESSION,
'name' => trim( $expression ),
'file' => $this->file_name,
'line' => $line,
'block' => $block,
'depth' => $depth,
'in_class' => $classname,
'in_function' => $functionname,
'open_blocks' => $open_blocks
) );
$expression = '';
} else { $expression .= $token; }
} else {
@ -165,7 +187,7 @@ class PHPParser {
switch( $last_token ) {
case T_CLASS:
if( (bool)($this->fetch_mode & PHPPARSER_FETCH_CLASSES) ) {
$this->parsed_objects[] = array(
$this->foundObject( array(
'type' => PHPPARSER_CLASS_DEF,
'name' => strtolower( $string ),
'file' => $this->file_name,
@ -174,13 +196,13 @@ class PHPParser {
'depth' => $depth,
'in_class' => $classname,
'in_function' => $functionname
);
) );
}
$class = array( 'name' => $text, 'block' => $block );
break;
case T_FUNCTION:
if( (bool)($this->fetch_mode & PHPPARSER_FETCH_FUNCTIONS) && empty( $classname ) ) { // Not interested in member function definitions
$this->parsed_objects[] = array(
$this->foundObject( array(
'type' => PHPPARSER_FUNCTION_DEF,
'name' => strtolower( $string ),
'file' => $this->file_name,
@ -189,7 +211,7 @@ class PHPParser {
'depth' => $depth,
'in_class' => $classname,
'in_function' => $functionname
);
) );
}
$function = array( 'name' => $text, 'block' => $block );
break;
@ -202,7 +224,7 @@ class PHPParser {
case T_REQUIRE:
case T_REQUIRE_ONCE:
if( (bool)($this->fetch_mode & PHPPARSER_FETCH_INCLUDES) ) {
$this->parsed_objects[] = array(
$this->foundObject( array(
'type' => PHPPARSER_INCLUDE,
'name' => trim( str_replace( '\'', '', $text ) ),
'file' => $this->file_name,
@ -211,7 +233,7 @@ class PHPParser {
'depth' => $depth,
'in_class' => $classname,
'in_function' => $functionname
);
) );
}
break;
}
@ -236,7 +258,7 @@ class PHPParser {
//*/
case T_EVAL:
if( (bool)($this->fetch_mode & PHPPARSER_FETCH_INTERNAL) ) {
$this->parsed_objects[] = array(
$this->foundObject( array(
'type' => PHPPARSER_FUNCTION_CALL,
'name' => $text,
'file' => $this->file_name,
@ -245,7 +267,7 @@ class PHPParser {
'depth' => $depth,
'in_class' => $classname,
'in_function' => $functionname
);
) );
}
break;
}
@ -266,7 +288,7 @@ class PHPParser {
T_REQUIRE_ONCE,
T_UNSET
) ) ) {
$this->parsed_objects[] = array(
$this->foundObject( array(
'type' => PHPPARSER_LANGUAGE_CONSTRUCT,
'name' => $text,
'file' => $this->file_name,
@ -275,7 +297,7 @@ class PHPParser {
'depth' => $depth,
'in_class' => $classname,
'in_function' => $functionname
);
) );
}
$expression .= ( in_array( $id, array(
/* Values */

98
scanner.php Normal file
View file

@ -0,0 +1,98 @@
<?php
require_once( 'parser.php' );
/*
define( 'FAULT_MINOR', 0 );
define( 'FAULT_MEDIUM', 1 );
define( 'FAULT_MAJOR', 2 );
*/
$modules = array();
class ScannerModule {
var $faults;
var $blame;
function ScannerModule() {
$this->faults = array();
$this->blame = array();
}
function fault( $object, $level, $reason = '' ) {
$this->faults[] = array(
'object' => $object,
'level' => $level,
'reason' => $reason,
'svn' => $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]
);
}
}
}
function postScan( $filename ) {
}
}
function _callback( $object ) {
global $modules;
foreach( $modules as $module ) {
$module->parserCallback( $object );
}
}
// 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}" );
}
}
$parser = new PHPParser( PHPPARSER_FETCH_EXPRESSIONS | PHPPARSER_FETCH_CALLS | PHPPARSER_FETCH_INTERNAL | PHPPARSER_FETCH_CONSTRUCTS );
$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" );
}
foreach( $files as $file ) {
foreach( $modules as $module ) { $module->preScan( $file ); }
$parser->parseFile( $file );
}
foreach( $modules as $module ) {
foreach( $module->faults as $fault ) {
$svn = serialize( $fault['svn'] );
printf( "%s %s - %s\n -- %s:%d, %s r%d\n",
get_class( $module ),
$fault['level'],
$fault['reason'],
$fault['object']['file'],
$fault['object']['line'],
isset( $fault['svn']['author'] ) ? $fault['svn']['author'] : '?',
isset( $fault['svn']['revision'] ) ? $fault['svn']['revision'] : '?'
);
}
}
?>

View file

@ -1,5 +1,4 @@
<?php
require_once( 'parser.php' );
$filters = array(
@ -25,19 +24,22 @@ $filters = array(
),
);
$parser = new PHPParser( PHPPARSER_FETCH_EXPRESSIONS | PHPPARSER_FETCH_CALLS | PHPPARSER_FETCH_INTERNAL | PHPPARSER_FETCH_CONSTRUCTS );
$parser->parseFile( __FILE__ );
function test( $object ) {
global $filters;
foreach( $parser->parsed_objects as $object ) {
foreach( $filters as $filter ) {
if( $object['type'] == $filter['type'] ) {
if( preg_match( $filter['pattern'], $object['name'] ) > 0 ) {
echo "Triggered Filter '{$filter['desc']}' at line {$object['line']}\n";
echo "fn: Triggered Filter '{$filter['desc']}' at line {$object['line']}\n";
}
}
}
}
$parser = new PHPParser( PHPPARSER_FETCH_EXPRESSIONS | PHPPARSER_FETCH_CALLS | PHPPARSER_FETCH_INTERNAL | PHPPARSER_FETCH_CONSTRUCTS );
$parser->registerCallback( 'test' );
$parser->parseFile( __FILE__ );
$sql = "select * from failure";
echo "Here's the $sql!\n";
mail( 'correl@payquik.com', 'subject', 'stuffs' );
@ -45,6 +47,7 @@ eval( "echo \"here's eval!\n\";" );
print_r( $sql );
var_dump( $sql );
echo "done\n";
}}}
/* OUTPUT: