From 4244ed6d8b289dd8d957b79f5a12374974d0f58f Mon Sep 17 00:00:00 2001 From: Correl Roush Date: Thu, 20 Dec 2007 04:49:58 +0000 Subject: [PATCH] Importing code scanner git-svn-id: file:///srv/svn/scanner/trunk@1 a0501263-5b7a-4423-a8ba-1edf086583e7 --- flist2.php | 305 +++++++++++++++++++++++++++++++++++++++++++++++++ parser.php | 330 +++++++++++++++++++++++++++++++++++++++++++++++++++++ test.php | 59 ++++++++++ 3 files changed, 694 insertions(+) create mode 100644 flist2.php create mode 100644 parser.php create mode 100644 test.php diff --git a/flist2.php b/flist2.php new file mode 100644 index 0000000..c160946 --- /dev/null +++ b/flist2.php @@ -0,0 +1,305 @@ +parseFile( $file ); +} err( "\n" ); + +$functions = array(); +$all_local_functions = array(); +foreach( $parser->parsed_objects as $function ) { + $file = str_replace( "$CODE_PATH/", '', $function['file'] ); + if( strpos( $file, 'libs/' ) === 0 ) { + $functions[$function['name']] = array( 'used' => 0, 'file' => $function['file'] ); + } + if( !is_array( $all_local_functions[$file] ) ) { $all_local_functions[$file] = array(); } + $all_local_functions[$file][] = $function['name']; +} +$functions['eval'] = array( 'used' => 0, 'file' => 'Evil Eval' ); + +$php_files = `find {$CODE_PATH}{$SCAN_PATH} -name '*.php'`; +$php_files = split( "\n", $php_files ); + +$counters = array( 'files' => 0, 'errors' => 0, 'warnings' => 0, 'failed' => 0 ); +$file_requires = array(); +err( "Parsing Files\n" ); +$parser->reset( PHPPARSER_FETCH_INCLUDES + PHPPARSER_FETCH_CALLS + PHPPARSER_FETCH_INTERNAL ); +$counter = 0; $total = count( $php_files ); $lastpct = 0; +foreach( $php_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; + } + } + + $file = trim( $file ); + if( empty( $file ) ) { continue; } + $filename = $file; + $file = str_replace( "$CODE_PATH/", '', $file ); + $file_requires[$file] = array( 'parsed' => false, 'bad' => 0, 'warning' => 0, 'libs' => array(), 'errors' => array() ); + + // If the file has bad syntax, don't even bother with it + $output = array(); + exec( "php -l '$filename'", $output, $result ); + if( $result != 0 ) { + $counters['failed']++; + foreach( $output as $linterror ) { + $matches = array(); + if( preg_match( '/error:.*?on line (\d+)$/i', $linterror, $matches ) == 0 ) { continue; } + $file_requires[$file]['errors'][] = array( 'line' => $matches[1], 'message' => $matches[0] ); + } + continue; + } + $file_requires[$file]['parsed'] = true; + + $local_functions = isset( $all_local_functions[$file] ) ? $all_local_functions[$file] : array(); + $includes = array(); + $parser->reset(); + $parser->parseFile( $filename ); + //echo "
", print_r( $parser->parsed_objects ), '
'; + foreach( $parser->parsed_objects as $object ) { + switch( $object['type'] ) { + case PHPPARSER_INCLUDE: + $includes[] = $object; + $current_dir = dirname( $file ); + if( $object['name'] == 'global.php' ) { + $object['name'] = 'libs/security/lib_security_input.php'; + $includes[] = $object; + $object['name'] = 'libs/get/lib_get_portal.php'; + $includes[] = $object; + $object['name'] = 'libs/logging/lib_logging_errors.php'; + $includes[] = $object; + } + $local_functions = array_merge( $local_functions, isset( $all_local_functions[$object['name']] ) ? $all_local_functions[$object['name']] : array() ); + break; + case PHPPARSER_FUNCTION_CALL: + if( !in_array( $object['name'], array_keys( $functions ) ) ) { + if( + !in_array( $object['name'], $parser->internal_functions ) + && !in_array( $object['name'], $local_functions ) + ) { + $file_requires[$file]['errors'][] = array( 'line' => $object['line'], 'message' => "Undefined function '{$object['name']}'" ); + $file_requires[$file]['warnings']++; + } + continue; + } + $include = $functions[$object['name']]['file']; + $include = str_replace( "$CODE_PATH/", '', $include ); + if( $include == $file ) { break; } + $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( $includes as $libinc ) { + if( $libinc['name'] != $include || !in_array( $libinc['in_function'], array( '', $object['in_function'] ) ) ) { continue; } + $inc = $libinc; + $bad = false; + /* Holy shit, what a fucked up check this used to be... + $warning = !$bad && $libinc['depth'] > ( empty( $object['in_function'] ) ? 0 : 1 ) && ( + $object['depth'] < $libinc['depth'] + || ( + $object['depth'] >= $libinc['depth'] + && ( $object['block'] - ( $object['depth'] - $libinc['depth'] ) ) != $libinc['block'] + ) + ); + */ + $warning = !$bad && !in_array( $libinc['block'], $object['open_blocks'] ); + if( !$bad && !$warning ) { break; } + } + $open_blocks = is_array( $object['open_blocks'] ) ? implode( ',', $object['open_blocks'] ) : ''; + $object['bad'] = $bad; + $object['warning'] = $warning; + $object['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}]" : '' ); + $lib['lines'][] = $object['line']; + $lib['calls'][] = $object; + $file_requires[$file]['bad'] += $bad ? 1 : 0; + $file_requires[$file]['warning'] += $warning ? 1 : 0; + break; + } + } + $counters['files']++; + if( $file_requires[$file]['bad'] > 0 ) { $counters['errors']++; } + if( $file_requires[$file]['warning'] > 0 ) { $counters['warnings']++; } + if( !$file_requires[$file]['parsed'] ) { $counters['failed']++; } +} err( "\n" ); +fclose( $stderr ); + +$files =& $php_files; +foreach( $files as $key => $value ) { $files[$key] = str_replace( "$CODE_PATH/", '', $value ); } +function ispath( $string ) { return strpos( $string, '/' ) === false ? false: true; } +function notpath( $string ) { return strpos( $string, '/' ) === false ? true: false; } +sort( $files ); +$files1 = array_filter( $files, 'notpath' ); sort( $files1 ); +$files2 = array_filter( $files, 'ispath' ); sort( $files2 ); +$files = array_merge( $files1, $files2 ); +?> + + + PayQuik Library Function Calls + + + + +

Library Function calls by file

+ files, of which contain errors, contain warnings and failed to parse
+Last updated: +

Legend

+
+
Good files
+
File parsed ok, no warnings or errors
+
Warnings
+
File contains potentially undefined function calls
+
Errors
+
File contains undefined function calls
+
Failed
+
File failed to parse (checked with php -l filename)
+
+

Files:

+ $file ) { + if( !isset( $file_requires[$file] ) ) { continue; } + $failed = !$file_requires[$file]['parsed']; + $bad = $file_requires[$file]['bad']; + $warning = $file_requires[$file]['warning']; + $requires = array_keys( $file_requires[$file]['libs'] ); + ksort( $requires ); + ?> + + + + + + \ No newline at end of file diff --git a/parser.php b/parser.php new file mode 100644 index 0000000..dcc5f0b --- /dev/null +++ b/parser.php @@ -0,0 +1,330 @@ +reset( $fetch_mode ); + $this->internal_functions = get_defined_functions(); $this->internal_functions = $this->internal_functions['internal']; + $this->addFunctionDefinitions( $functionlist ); + } + + function reset( $fetch_mode = null ) { + if( $fetch_mode > 0 ) { + $this->fetch_mode = $fetch_mode; + } + $this->file_name = ''; + $this->defined_functions = array(); + $this->parsed_objects = array(); + } + + function addFunctionDefinitions( $functionlist ) { + if( is_array( $functionlist ) ) { + array_merge( $this->defined_functions, $functionlist ); + } + } + + + function parseFile( $file ) { + if( !file_exists( $file ) || !is_readable( $file ) ) { return false; } + $this->file_name = $file; + $this->parse( file_get_contents( $file ) ); + } + + function parse( $content ) { + $tokens = token_get_all( $content ); + + $line = 1; + $depth = 0; + $block = 0; + $block_count = 0; + + $class = $classname = null; + $function = $functionname = null; + $switch = array(); + $expression = ''; + $line_text = ''; + + $internal_functions = get_defined_functions(); $internal_functions = $internal_functions['internal']; + $local_functions = array(); + $local_classes = array(); + $open_blocks = array( 0 ); + $in_string = false; + 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"; + $open_blocks = array_merge( array(0), $open_blocks ); + } + $classname = isset( $class['name'] ) ? $class['name'] : ''; + $functionname = isset( $function['name'] ) ? $function['name'] : ''; + if( $token == '"' ) { + $in_string = !$in_string; + } + if( $in_string ) { + $expression .= is_string( $token ) ? $token : $token[1]; + $count = preg_match_all( '/\r?(\n|\r)/', is_string( $token ) ? $token : $token[1], $m ); + $line += $count; + continue; + } + if( is_string( $token ) ) { + // Single character token + $text = $token; + switch( $token ) { + case '{': + $block_count++; + $block = $block_count; + array_push( $open_blocks, $block ); + $depth = count( $open_blocks ) - 1; + break; + case '}': + array_pop( $open_blocks ); + $depth = count( $open_blocks ) - 1; + $block = $depth == 0 ? 0 : $open_blocks[$depth]; + if( !empty( $class ) && $class['block'] == $block ) { $class = $functionname = null; } + if( !empty( $function ) && $function['block'] == $block ) { $function = $functionname = null; } + if( in_array( $block, $switch ) ) { + array_pop( $open_blocks ); + $depth = count( $open_blocks ) - 1; + $block = $depth == 0 ? 0 : $open_blocks[$depth-1]; + } + break; + case '(': + if( + (bool)($this->fetch_mode & PHPPARSER_FETCH_CALLS) + && !empty( $string ) + && $last_token != T_FUNCTION + && $last_token != T_OBJECT_OPERATOR + && $last_token != T_NEW + //&& ( (bool)($this->fetch_mode & PHPPARSER_FETCH_INTERNAL) && !in_array( $string, $this->internal_functions ) ) + ) { + $this->parsed_objects[] = array( + 'type' => PHPPARSER_FUNCTION_CALL, + 'name' => strtolower( $string ), + 'file' => $this->file_name, + 'line' => $line, + 'block' => $block, + 'depth' => $depth, + '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 + ); + $expression = ''; + } else { $expression .= $token; } + } else { + list($id, $text) = $token; + switch( $id ) { + case T_CURLY_OPEN: + case T_DOLLAR_OPEN_CURLY_BRACES: + $block_count++; + $block = $block_count; + array_push( $open_blocks, $block ); + $depth = count( $open_blocks ) - 1; + break; + case T_STRING: + $string = $text; + switch( $last_token ) { + case T_CLASS: + if( (bool)($this->fetch_mode & PHPPARSER_FETCH_CLASSES) ) { + $this->parsed_objects[] = array( + 'type' => PHPPARSER_CLASS_DEF, + 'name' => strtolower( $string ), + 'file' => $this->file_name, + 'line' => $line, + 'block' => $block, + '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( + 'type' => PHPPARSER_FUNCTION_DEF, + 'name' => strtolower( $string ), + 'file' => $this->file_name, + 'line' => $line, + 'block' => $block, + 'depth' => $depth, + 'in_class' => $classname, + 'in_function' => $functionname + ); + } + $function = array( 'name' => $text, 'block' => $block ); + break; + } + break; + case T_CONSTANT_ENCAPSED_STRING: + switch( $last_token ) { + case T_INCLUDE: + case T_INCLUDE_ONCE: + case T_REQUIRE: + case T_REQUIRE_ONCE: + if( (bool)($this->fetch_mode & PHPPARSER_FETCH_INCLUDES) ) { + $this->parsed_objects[] = array( + 'type' => PHPPARSER_INCLUDE, + 'name' => trim( str_replace( '\'', '', $text ) ), + 'file' => $this->file_name, + 'line' => $line, + 'block' => $block, + 'depth' => $depth, + 'in_class' => $classname, + 'in_function' => $functionname + ); + } + break; + } + break; + ///* + case T_SWITCH: + $block_count++; + $block = $block_count; + array_push( $open_blocks, $block ); + $depth = count( $open_blocks ) - 1; + $switch[$block] = $block; + break; + case T_CASE: + case T_DEFAULT: + // Each conditional of a switch statement is its own block + array_pop( $open_blocks ); + $block_count++; + $block = $block_count; + array_push( $open_blocks, $block ); + $depth = count( $open_blocks ) - 1; + break; + //*/ + case T_EVAL: + if( (bool)($this->fetch_mode & PHPPARSER_FETCH_INTERNAL) ) { + $this->parsed_objects[] = 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( + T_ARRAY, + T_ECHO, + T_EMPTY, + T_EVAL, + T_EXIT, + T_HALT_COMPILER, + T_INCLUDE, + T_INCLUDE_ONCE, + T_ISSET, + T_LIST, + T_PRINT, + T_REQUIRE, + T_REQUIRE_ONCE, + T_UNSET + ) ) ) { + $this->parsed_objects[] = array( + 'type' => PHPPARSER_LANGUAGE_CONSTRUCT, + 'name' => $text, + 'file' => $this->file_name, + 'line' => $line, + 'block' => $block, + 'depth' => $depth, + 'in_class' => $classname, + 'in_function' => $functionname + ); + } + $expression .= ( in_array( $id, array( + /* Values */ + T_STRING, + T_CHARACTER, + T_CONSTANT_ENCAPSED_STRING, + T_ENCAPSED_AND_WHITESPACE, + T_WHITESPACE, + T_DNUMBER, + T_LNUMBER, + T_NUM_STRING, + T_VARIABLE, + //T_ARRAY, + T_STRING_VARNAME, + + /* Operators */ + T_BOOLEAN_AND, + T_BOOLEAN_OR, + T_DEC, + T_INC, + T_IS_EQUAL, + T_IS_GREATER_OR_EQUAL, + T_IS_IDENTICAL, + T_IS_NOT_EQUAL, + T_IS_NOT_IDENTICAL, + T_IS_SMALLER_OR_EQUAL, + T_LOGICAL_AND, + T_LOGICAL_OR, + T_LOGICAL_XOR, + T_OBJECT_OPERATOR, + T_DOUBLE_COLON, + T_SL, + T_SR, + + /* Casts */ + T_DOUBLE_CAST, + T_INT_CAST, + T_OBJECT_CAST, + T_STRING_CAST, + T_UNSET_CAST, + + /* Constructs */ + T_ECHO, + T_PRINT + ) ) ) ? $text : ' '; + $count = preg_match_all( '/\r?(\n|\r)/', $text, $m ); + $line += $count; + } + } + } +} +?> diff --git a/test.php b/test.php new file mode 100644 index 0000000..7531c61 --- /dev/null +++ b/test.php @@ -0,0 +1,59 @@ + PHPPARSER_EXPRESSION, + 'desc' => 'Echoing Sql', + 'pattern' => '/echo[\(\s].*?\$sql/i' + ), + array( + 'type' => PHPPARSER_LANGUAGE_CONSTRUCT, + 'desc' => 'Evil Eval', + 'pattern' => '/^eval$/i' + ), + array( + 'type' => PHPPARSER_FUNCTION_CALL, + 'desc' => 'PRINT_R or VAR_DUMP', + 'pattern' => '/^(print_r|var_dump)$/i' + ), + array( + 'type' => PHPPARSER_EXPRESSION, + 'desc' => 'Developer Email', + 'pattern' => '/(?parseFile( __FILE__ ); + +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"; + } + } + } +} + +$sql = "select * from failure"; +echo "Here's the $sql!\n"; +mail( 'correl@payquik.com', 'subject', 'stuffs' ); +eval( "echo \"here's eval!\n\";" ); +print_r( $sql ); +var_dump( $sql ); +echo "done\n"; + +/* OUTPUT: + +Triggered Filter 'Echoing Sql' at line 42 +Triggered Filter 'Developer Email' at line 43 +Triggered Filter 'Evil Eval' at line 44 +Triggered Filter 'PRINT_R or VAR_DUMP' at line 45 +Triggered Filter 'PRINT_R or VAR_DUMP' at line 46 + +*/ + +?>