├── sampleclass.php ├── test.php ├── Readme.md └── codespy.php /sampleclass.php: -------------------------------------------------------------------------------- 1 | 10) 7 | return "Result too large"; 8 | elseif($x>1) 9 | return $x*$this->factorial($x-1); 10 | elseif($x==1) 11 | return 1; 12 | else 13 | throw new Exception("test"); 14 | } 15 | 16 | function function_with_branches($x) 17 | { 18 | if($x==1) 19 | echo "one"; 20 | elseif($x==2) 21 | switch(1){ 22 | case 1: 23 | switch($x) { 24 | case 1: 25 | echo 1; 26 | break; 27 | case 2: 28 | echo 2; 29 | foreach(array(1,2,3) as $v) echo 56; 30 | while($v < 10) $v++; 31 | echo $v; 32 | if(4) {break 1; 33 | if(5) 34 | echo 45; 35 | if(5) 36 | echo 45; 37 | } 38 | while(0) { 39 | while(0) 40 | if(5) { 41 | continue; 42 | if(7){ 43 | echo 7; 44 | } 45 | else echo 9; 46 | } 47 | } 48 | echo $v+2; 49 | do echo 45,$v++; while(0); 50 | if(2) echo 12; 51 | break 2; 52 | default: 53 | echo 8; 54 | break; 55 | case 0?3:2?5:3: 56 | echo 3; 57 | break; 58 | } 59 | } 60 | else echo 64; 61 | echo 344; 62 | } 63 | function function_with_branches_2($x) 64 | { 65 | if($x==1) 66 | echo "one"; 67 | elseif($x==2) 68 | switch($x) { 69 | case 1: 70 | echo 1; 71 | break; 72 | case 2: 73 | echo 2; 74 | foreach(array(1,2,3) as $v) echo 56; 75 | while($v < 10) $v++; 76 | echo $v; 77 | if(4) break; 78 | echo $v+2; 79 | if(2) echo 12; 80 | default: 81 | echo 8; 82 | break; 83 | 84 | } 85 | else echo 64; 86 | } 87 | } 88 | 89 | -------------------------------------------------------------------------------- /test.php: -------------------------------------------------------------------------------- 1 | function_with_branches(1); 36 | echo $o->function_with_branches(2); 37 | echo $o->function_with_branches(3); 38 | echo $o->factorial(3); 39 | 40 | //a function not analayzed. 41 | echo $o->function_with_branches_2(2); 42 | 43 | //Codespys destructor will write to stdout or files after the script terminates. 44 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | ## Codespy 2.5: A code /path coverage analyzing tool in pure php. 2 | 3 | New in this version 2.5 4 | 5 | * More accurate statement coverage. Now tracks the execution of multiple statements in a single line. 6 | 7 | ![Screenshot](http://i40.tinypic.com/16rsl4.jpg) 8 | 9 | New in this version 2.0 10 | 11 | * Rewrote the patcher from scratch to organize its operation as a series of passes. This makes fixing any bugs and adding / modifying the functionality a lot easier and faster. 12 | * Added Executable Statement Coverage for more accurate code coverage measurement. 13 | * Added an index.html for the html output wih summary of code coverage report. 14 | * Added experimental path coverage analysis functionality. 15 | 16 | **Code coverage analysis** means finding out which lines in a source code are actually getting executed. 17 | **Statement coverage analysis** which means how many of the executable statements are actually getting executed. 18 | **Path coverage analysis** is sometimes required because, even if the execution has covered all code, it can do so by following different paths, executing different sections of code in numerous combinations. It is possible that some combination can cause unexpected behaviour. So it is sometimes necessary to check for all combinations that execution can follow from entering a function till returning from it. 19 | 20 | Existing code coverage analyzing tools depends on xdebug functions to do the code coverage analysis. 21 | This is an attempt to accomplish the same without using xdebug. 22 | 23 | **How to do code coverage analysis:** 24 | 25 | Codespy can work only with included files. So you will have to include the target source file in the test-cases file. 26 | 27 | **Codespy can only measure code coverage in included files** 28 | 29 | **You have to include codespy.php BEFORE you include any other file for which code covarage is to be measured.** 30 | 31 | 32 | Codecoverage output produces two metrics. 33 | 34 | * 1.Line coverage, which measures the number of executed lines/ total no of lines in that file. 35 | * 2.Statement Coverage, which measures the number of statements covered/ total no of statements. This is supposed to be a more accurate measure of code coverage. 36 | 37 | 38 | Supported output formats, html, vim, php and text 39 | 40 | * **html** : writes html files, one for each analyzed file.**IMPORTANT** : Output directory must be set using **\codespy\Analyzer::$outputdir**. 41 | 42 | An index.html file will be generated with a summary of code coverage and statement coverage of all files and links 43 | to the coverage view of each files. 44 | 45 | ![Screenshot](http://i42.tinypic.com/bhkbc1.jpg) 46 | The view coverage link takes you to an html page where you can see the coverage report for each file. 47 | 48 | This html file will contain the source code with executed lines highlighted. See the screenshot below. 49 | 50 | ![Screenshot](http://i40.tinypic.com/16rsl4.jpg) 51 | 52 | File extension .cc.html will be appended to filenames when writing into output directory. 53 | 54 | 55 | * **vim** : outputs vim command for each of the files that can be used to highlight the executed lines in a vim window displaying the respective source file. 56 | 57 | * **php** : outputs a 2 dimensional array in php format with lines executed for each of analyzed file. 58 | 59 | * **text** : outputs the executed lines for each analyzed file 60 | 61 | 62 | For text and html output, the net code coverage in percentage shown at end of output for each file. 63 | 64 | 65 | For trying codespy, you will need minimum of three files, which are 66 | 67 | **codespy.php** 68 | 69 | **simpleclass.php** -> The source file to analyze. 70 | 71 | **test_cases.php** -> The file with testcases. 72 | 73 | test_cases.php should look like, 74 | 75 | factorial(5); 95 | //Codespys destructor will write output to stdout or files after the script terminates. 96 | 97 | 98 | After the script terminates, the codespy destructor will write the output to the standard output or files. If output format is html, just open the generated html files in a browser to see code coverage. 99 | 100 | ### The addFileToSpy Function. 101 | This is a useful function that can limit the code analysis to a set of specified files. This can be useful if a project contains a large number of php files, but at time we are only interested in analyzing coverage of a particular file. 102 | 103 | A file can be added for analysis as 104 | 105 | include 'codespy.php'; 106 | \codespy\Analyzer::addFileToSpy("/path/to/target/file"); 107 | 108 | This function should be called right after you include codespy.php. 109 | 110 | By restricting analysis to a single file or a small number of files, the test can be run without much reduction in speed. 111 | 112 | For testing, you can just download all the three files in to a directory, change the output dir in the test.php and just run it to see its working. The html files should be written to the set output directory. Just open the file in browser to see code coverage. 113 | 114 | 115 | **How to do path coverage analysis:[ Experimental ]** 116 | 117 | Warning : Path coverage still is in developmen. 118 | 119 | * Download codespy.php. 120 | * Include it in your project and add the functions to be analyzed for path coverage by calling function '_addFunctionToAnalyze_' **BEFORE** any of your files gets included. Please view the included test.php file to see how to use the function '_addFunctionToAnalyze_'. 121 | * Specify the output directory for codespy's html output files. Directory must be script writable. 122 | * Execute your tests/ code via command line or from a browser. 123 | * Codespy's destructor method will write the collected data to the output directory at the end of execution. 124 | 125 | The path coverage analysis is done by splitting the body of functions into a number of nodes. Each node is a block of code that may or may not gets executed depending on the path taken by the execution. 126 | 127 | When codespy outputs the result of analysis, it marks the nodes in the generated html file as shown below. 128 | 129 | ![node-marker-image](http://i40.tinypic.com/rbxvdx.png) 130 | 131 | Each node is numbered using big red numerals. 132 | 133 | Codespy can calculate the possible execution paths through these nodes by analyzing the structure of conditional statements and statements like return, break, continue etc. (goto statements are not currently taken into account.) 134 | 135 | This is done by constructing a node tree from the source using this rule. A node 'B' is a child of another node 'A' only if execution of 'B' can follow the execution of 'A'. 136 | 137 | This node tree is also displayed in the html output as shown below. 138 | 139 | ![node-tree](http://i40.tinypic.com/sq47de.png) 140 | 141 | The path coverage information is displayed in the generated html as shown below. 142 | 143 | ![Path coverage output](http://i44.tinypic.com/1ghkxd.png) 144 | 145 | **Possible execution paths:** 146 | This is the number of ways a function can execute. in the above image it says 61. This means there are 61 unique execution paths through the function. Below this, codespy displays all the calculated paths. 147 | 148 | **Paths Covered:** 149 | This is the actual number paths that was covered through the function during the execution. Below this codespy lists the actual executed paths. 150 | 151 | To try this out, 152 | 153 | * Download the files **codespy.php, sampleclass.php, test.php** to the same directory. 154 | * Edit the file test.php to change the directory in line 155 | 156 | \codespy\Analyzer::$outputdir = 'g:\work\easyphp-5.3.6.1\www\crxml\output'; 157 | 158 | to some script writable directory in your system. 159 | 160 | * Execute test.php using command line or from a browser. 161 | * Open the file sampleclass.php.cc.html which will be written to your output directory in your browser to see the output as shown in the images above. 162 | 163 | * Edit the file sampleclass.php to change the code and see the change reflected in the generated output. 164 | 165 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /codespy.php: -------------------------------------------------------------------------------- 1 | 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining 9 | a copy of this software and associated documentation files (the 10 | "Software"), to deal in the Software without restriction, including 11 | without limitation the rights to use, copy, modify, merge, publish, 12 | distribute, sublicense, and/or sell copies of the Software, and to 13 | permit persons to whom the Software is furnished to do so, subject to 14 | the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be 17 | included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | */ 28 | 29 | class str_wrp 30 | { 31 | public $resource; 32 | public $patched_content; 33 | public $path; 34 | function wr() 35 | { 36 | stream_wrapper_unregister("file"); 37 | stream_wrapper_register("file", __CLASS__); 38 | } 39 | function unwr() 40 | { 41 | stream_wrapper_restore('file'); 42 | } 43 | function stream_open($path, $mode, $options, &$openedPath) 44 | { 45 | $this->path = $path; 46 | $this->unwr(); 47 | $this->resource = fopen($path, $mode, $options); 48 | $this->wr(); 49 | return $this->resource !== false ; 50 | } 51 | 52 | function stream_close() 53 | { 54 | $this->unwr(); 55 | $return = fclose($this->resource); 56 | $this->wr(); 57 | return $return; 58 | } 59 | 60 | function stream_eof() 61 | { 62 | return ($this->offset < $this->length); 63 | } 64 | 65 | function stream_flush() 66 | { 67 | $this->unwr(); 68 | $return = fflush($this->resource); 69 | $this->wr(); 70 | return $return; 71 | } 72 | 73 | function stream_read($count) 74 | { 75 | $this->unwr(); 76 | if(!$this->patched_content) { 77 | $this->offset = 0; 78 | $lines = 0; 79 | while (!feof($this->resource)) { 80 | if ($line = fgets($this->resource)) { 81 | $this->content .= $line; 82 | $lines++; 83 | } 84 | } 85 | if(strpos($this->content,chr(10)) === false) { 86 | $lines = substr_count($this->content,chr(13)); 87 | $this->content = str_replace(chr(13),PHP_EOL,$this->content); 88 | } 89 | $this->patched_content = analyzer::load($this->content,$this->path,$lines); 90 | $this->length = strlen($this->patched_content); 91 | } 92 | $this->wr(); 93 | $return = substr($this->patched_content,$this->offset,$count); 94 | $this->offset += $count; 95 | return $return; 96 | } 97 | 98 | function stream_seek($offset, $whence = SEEK_SET) 99 | { 100 | $this->unwr(); 101 | $return = fseek($this->resource, $offset, $whence) === 0; 102 | $this->offset = $offset; 103 | $this->wr(); 104 | return $return; 105 | } 106 | 107 | function stream_stat() 108 | { 109 | $this->unwr(); 110 | $return = fstat($this->resource); 111 | $this->wr(); 112 | return $return; 113 | 114 | } 115 | 116 | function stream_tell() 117 | { 118 | $this->unwr(); 119 | $return = ftell($this->resource); 120 | $this->wr(); 121 | return $return; 122 | } 123 | 124 | function url_stat($path, $flags) 125 | { 126 | stream_wrapper_restore('file'); 127 | $result = @stat($path); 128 | stream_wrapper_unregister("file"); 129 | stream_wrapper_register("file", '\codespy\str_wrp'); 130 | return $result; 131 | } 132 | 133 | function dir_closedir() 134 | { 135 | $this->unwr(); 136 | $return = closedir($this->resource); 137 | $this->wr(); 138 | return $return; 139 | } 140 | 141 | function dir_opendir($path, $options) 142 | { 143 | $this->unwr(); 144 | if (isset($this->context)) { 145 | $this->resource = opendir($path, $this->context); 146 | } else { 147 | $this->resource = opendir($path); 148 | } 149 | $return = $this->resource !== false; 150 | $this->wr(); 151 | return $return; 152 | } 153 | 154 | function dir_readdir() 155 | { 156 | $this->unwr(); 157 | $return = readdir($this->resource); 158 | $this->wr(); 159 | return $return; 160 | } 161 | 162 | function dir_rewinddir() 163 | { 164 | $this->unwr(); 165 | $return = rewinddir($this->resource); 166 | $this->wr(); 167 | return $return; 168 | } 169 | 170 | function mkdir($path, $mode, $options) 171 | { 172 | $this->unwr(); 173 | if (isset($this->context)) { 174 | $result = mkdir($path, $mode, $options, $this->context); 175 | } else { 176 | $result = mkdir($path, $mode, $options); 177 | } 178 | $this->wr(); 179 | return $result; 180 | } 181 | 182 | function rename($path_from, $path_to) 183 | { 184 | $this->unwr(); 185 | if (isset($this->context)) { 186 | $result = rename($path_from, $path_to, $this->context); 187 | } else { 188 | $result = rename($path_from, $path_to); 189 | } 190 | $this->wr(); 191 | return $result; 192 | } 193 | 194 | function rmdir($path, $options) 195 | { 196 | $this->unwr(); 197 | $return = rmdir($path, $options); 198 | $this->wr(); 199 | return $return; 200 | } 201 | 202 | function stream_cast($cast_as) 203 | { 204 | $this->unwr(); 205 | $return = $this->resource; 206 | $this->wr(); 207 | return $return; 208 | } 209 | 210 | function stream_lock($operation) 211 | { 212 | $this->unwr(); 213 | $return = flock($this->resource, $operation); 214 | $this->wr(); 215 | return $return; 216 | } 217 | 218 | function stream_set_option($option, $arg1, $arg2) 219 | { 220 | throw new Exceptions\NotImplemented(__METHOD__); 221 | } 222 | 223 | function stream_write($data) 224 | { 225 | $this->unwr(); 226 | $return = fwrite($this->resource, $data); 227 | $this->wr(); 228 | return $return; 229 | } 230 | 231 | function unlink($path) 232 | { 233 | $this->unwr(); 234 | if (isset($this->context)) { 235 | $result = unlink($path, $this->context); 236 | } else { 237 | $result = unlink($path); 238 | } 239 | $this->wr(); 240 | return $result; 241 | } 242 | 243 | } 244 | class analyzer 245 | { 246 | public static $coveredlines = array(); 247 | public static $executable_statements = array(); 248 | public static $executionbranches = array(); 249 | public static $instancenumberfor = array(); 250 | public static $possiblebranches = array(); 251 | public static $nodetrees = array(); 252 | public static $outputformat = 'txt'; 253 | public static $outputdir = ''; 254 | public static $file_to_cover=array(); 255 | public static $functions_to_analayze = array(); 256 | public static $coveredcolor = '#ebd9b4'; 257 | public static $currenttest = ''; 258 | private function token_content($token) 259 | { 260 | if(is_array($token)) { 261 | if(isset($token[0])) { 262 | return $token[1]; 263 | } 264 | else 265 | return false; 266 | } else { 267 | return $token; 268 | } 269 | } 270 | private function token_name($token) 271 | { 272 | if(is_array($token)) { 273 | if(isset($token[0])) { 274 | return token_name($token[0]); 275 | } 276 | else 277 | return false; 278 | } else { 279 | return $token; 280 | } 281 | } 282 | public function wrap_section($line,$from) 283 | { 284 | $tokens = token_get_all(""; 292 | } 293 | else $return = "
";
 294 | 		$token_length = count($tokens) - 2;
 295 | 		foreach($tokens as $k=>$t) {
 296 | 			if($k==0) continue;
 297 | 			$foundsc = false;
 298 | 			if($semicolon_ended) {
 299 | 				for($lh=$k;$lh<=$token_length;$lh++) {
 300 | 					if($tokens[$lh] == ';'  ) $foundsc = true;
 301 | 					}
 302 | 				if(isset($from[$sf+1]) ) {
 303 | 					$title = join(', ',array_unique($from[$sf+1]));
 304 | 					if($foundsc) {
 305 | 					$return .= "
";
 306 | 					$start = true;
 307 | 					}
 308 | 				} elseif($foundsc) {
 309 | 				$return.="
";
 310 | 				$start = true;
 311 | 				}
 312 | 				$semicolon_ended = false;
 313 | 			}
 314 | 			$return .= htmlentities($this->token_content($t));
 315 | 			if($t == ';' ) {
 316 | 				$sf++;
 317 | 				$return .= "
"; 318 | $semicolon_ended = true; 319 | $start = false; 320 | 321 | } elseif($t == ':' ) { 322 | for($temp = $k;$temp>0;$temp--) if($this->token_name($tokens[$temp]) == 'T_CASE') { 323 | $sf++; 324 | $return .= "
"; 325 | $semicolon_ended = true; 326 | $start = false; 327 | break; 328 | } 329 | } 330 | } 331 | if($start) $return = $return."
"; 332 | return $return; 333 | } 334 | public function __destruct() 335 | { 336 | if(self::$outputformat == 'vim') { 337 | foreach(self::$coveredlines as $file=>$lines) { 338 | //echo PHP_EOL.$file,' : ',join(',',array_keys($lines)); 339 | $lines = array_filter($lines,function($x) { return $x>0;}); 340 | echo PHP_EOL.$file,PHP_EOL,'match cursorline /\%',join('l\|\%',array_keys($lines))."l/"; 341 | } 342 | } elseif(self::$outputformat == 'php') { 343 | echo var_export(self::$coveredlines,true); 344 | echo "Code Coverage percentage=".($covered_lines/count($file_lines))*100; 345 | } elseif(self::$outputformat =='html') { 346 | $self_covered_color = self::$coveredcolor; 347 | $style =<< 349 | 393 | EOB; 394 | ob_start(); 395 | ?> 396 | 420 | $lines) { 424 | if(self::shouldpatchfile($file)) { 425 | $patcher = new patcher(file_get_contents($file)); 426 | $patched = $patcher->patch(array(1,2,3,6)); 427 | file_put_contents(self::$outputdir."/".basename($file).".cc.temp",$patched); 428 | $file_lines = file(self::$outputdir."/".basename($file).".cc.temp"); 429 | unlink(self::$outputdir."/".basename($file).".cc.temp"); 430 | $output = ""; 431 | $maxlen = strlen(count($file_lines).max(self::$coveredlines[$file])) +1; 432 | $covered_lines=0; 433 | if(isset(Analyzer::$possiblebranches[$file])) foreach(Analyzer::$possiblebranches[$file] as $class =>$filebranches) { 434 | $output .= "\nFor Class

$class:

\n
"; 435 | foreach($filebranches as $file_1=>$branches) { $output .= "
Function: 

$file_1"."


Execution node tree constructed from source. Paths in red are the ones that actually executed.
"; 436 | $highlightpaths = array(); 437 | foreach(self::$executionbranches[$file][$class][$file_1] as $path) { ($highlightpaths[] = join(',',array_keys($path)));} 438 | ob_start(); 439 | Analyzer::$nodetrees[$file][$class][$file_1]->dumpNode(0,array(),$highlightpaths); 440 | $output .= ob_get_clean(); 441 | $output .= "
Possible execution paths:".($pp = count($branches))."\n
"; 442 | $output .= "
"; 443 | foreach($branches as $b) $output .= join(',',$b)."\n
"; 444 | $output .= "
"; 445 | $output .= "\n
Paths covered:".($cp = count($highlightpaths = array_unique($highlightpaths)))."\n\n

"; 446 | $output .= "Covered Paths:\n
"; 447 | foreach($highlightpaths as $path) { $output .= ($path."\n
");} 448 | $output .= "\n
"; 449 | $output .= "Path covereage :".(($cp*100)/$pp)."%\n\n

"; 450 | } 451 | } 452 | } else { 453 | $file_lines = file($file); 454 | $file_that_was_pathced_content = $file_lines[0]; 455 | if(strpos($file_that_was_pathced_content,chr(10)) === false) { 456 | $flines = substr_count($file_that_was_pathced_content,chr(13)); 457 | $file_that_was_pathced_content = str_replace(chr(13),PHP_EOL,$file_that_was_pathced_content); 458 | $file_lines = $flines; 459 | $file_lines = explode(chr(13),$file_that_was_pathced_content); 460 | } 461 | $maxlen = strlen(count($file_lines)) +1; 462 | $covered_statements = $covered_lines=0; 463 | $output = ''; 464 | } 465 | //foreach(self::$executionbranches as $functionname=>$paths) {$output .=$functionname."
"; foreach($paths as $path) $output .= join(',',array_keys($path))."\n
";} 466 | $contained_functions = array(); 467 | foreach($file_lines as $k=>$line) 468 | if(isset($lines[$k+1])) { 469 | foreach($lines[$k+1] as $called_functions) $contained_functions = array_merge($contained_functions,$called_functions); 470 | 471 | // $output .= "".str_pad(($k+1).':'.join(',',array_keys($lines[$k+1])),$maxlen,'0',STR_PAD_LEFT)."
".rtrim(preg_replace('/\(codespy-execution-node:([0-9.]+)\)/','\1',$this->wrap_section($line,array_keys[$k+1])))."

"; 472 | $output .= "".str_pad(($k+1)/*.':'.join(',',array_keys($lines[$k+1]))*/,$maxlen,'0',STR_PAD_LEFT)."".rtrim($temp = $this->wrap_section($line,$lines[$k+1]))."
"; 473 | $covered_statements+=count($lines[$k+1]); 474 | $covered_lines+=count($lines[$k+1]); 475 | } else 476 | // $output .= "".str_pad($k+1,$maxlen,'0',STR_PAD_LEFT)."
".rtrim(preg_replace('/\(codespy-execution-node:([0-9.]+)\)/','\1',htmlentities($line)))."

"; 477 | $output .= "".str_pad($k+1,$maxlen,'0',STR_PAD_LEFT)."
".rtrim(htmlentities($line))."

"; 478 | $called_functions = array_unique($contained_functions); 479 | $coverage = ($covered_lines/count($file_lines))*100; 480 | $actual_coverage = ($covered_statements*100/Analyzer::$executable_statements[$file]); 481 | $coverages[$file] = $coverage; 482 | $actual_coverages[$file] = $actual_coverage; 483 | $report = ''; 484 | $report .= ""; 485 | $report .= ""; 486 | $report .= "
'.$file.'
Statement Coverage =  ".number_format($actual_coverage,2)."%  ($covered_statements/".Analyzer::$executable_statements[$file].")Line Coverage =  ".number_format($coverage)."%  ($covered_lines/".count($file_lines).")Highlight statements covered by test    Exclusive
"; 489 | $output = "
$report
$output
"; 490 | if(self::$outputdir) { 491 | file_put_contents(self::$outputdir."/".($visual_report_file[$file] = preg_replace("/[:\\/\\\]/",'-',$file).".cc.html"),$style.$output); 492 | } 493 | } 494 | ob_start(); 495 | echo "$style"; 496 | $rc=0; 497 | foreach($coverages as $file=>$coverage) { 498 | $coverage = number_format($coverage,2); 499 | $actual_coverage = number_format($actual_coverages[$file],2); 500 | echo ""; 501 | } 502 | echo "
File NameLine Coverage~Statement CoverageView Report
$file$coverage %$actual_coverage %View Coverage
"; 503 | $index_content = ob_get_clean(); 504 | file_put_contents(self::$outputdir."/index.html",$index_content); 505 | 506 | } else { 507 | foreach(self::$coveredlines as $file=>$lines) { 508 | $total_lines = count($lines); 509 | $lines = array_filter($lines,function($x) { return $x>0;}); 510 | echo PHP_EOL.$file,' : ',PHP_EOL,join(',',array_keys($lines)).PHP_EOL; 511 | echo PHP_EOL."Code Coverage percentage=".((count($lines)/$total_lines)*100).PHP_EOL; 512 | } 513 | } 514 | } 515 | public static function shouldpatchfile($file) 516 | { 517 | foreach(self::$functions_to_analayze as $item) if($item[0] == $file ) return true; 518 | } 519 | public static function shouldpatch($function,$class ='') 520 | { 521 | $function = strtolower($function); 522 | $class = strtolower($class); 523 | if($class) { 524 | foreach(self::$functions_to_analayze as $item) { 525 | if($item[1] == $function && $item[2] == $class) return true; 526 | } 527 | } else { 528 | foreach(self::$functions_to_analayze as $item) { 529 | if($item[1] == $function && $item[2]=='' ) return true; 530 | } 531 | } 532 | } 533 | public static function addFunctionToAnalyze($file,$function_name,$class='') 534 | { 535 | if($file = realpath($file)) 536 | array_push(self::$functions_to_analayze,array($file,strtolower($function_name),strtolower($class))); 537 | } 538 | public static function addFileToSpy($path) 539 | { 540 | array_push(self::$file_to_cover,$path); 541 | } 542 | 543 | static function trace($lines,$file) 544 | { 545 | if(isset(self::$coveredlines[$file][$lines])) self::$coveredlines[$file][$lines]+=1;else self::$coveredlines[$file][$lines]=1; 546 | } 547 | 548 | static function match($f1,$f2) 549 | { 550 | $f1_array = explode('::',$f1); 551 | $f2_array = explode('::',$f2); 552 | if(!class_exists($f2_array[0])) return false; 553 | if(isset($f1_array[1])) return( (is_subclass_of($f2_array[0],$f1_array[0]) || $f2_array[0]==$f1_array[0]) && ($f1_array[1]==$f2_array[1])) ; 554 | else return (is_subclass_of($f2_array[0],$f1_array[0]) || $f2_array[0]==$f1_array[0]) ; 555 | } 556 | 557 | static function load($source,$path,$lc) 558 | { 559 | if((in_array($path,self::$file_to_cover) === false) && count(self::$file_to_cover)>0) return $source; 560 | $patcher = new patcher($source); 561 | if(count(self::$functions_to_analayze)>0) 562 | return $patcher->patch(array(1,2,3,4,5),$path); 563 | else 564 | return $patcher->patch(array(1,2,3,4),$path); 565 | } 566 | static function add_to_patch($token,$offset) 567 | { 568 | 569 | } 570 | 571 | public function exception_handler($e) 572 | { 573 | $this->__destruct(); 574 | throw $e; 575 | } 576 | 577 | } 578 | 579 | class patcher 580 | { 581 | function __construct($source) 582 | { 583 | $this->source = array($source); 584 | $this->current_pass = 0; 585 | $this->break_levels = array(); 586 | $this->break_nodes = array(); 587 | $this->continue_nodes = array(); 588 | $this->return_nodes = array(); 589 | $this->jumps = array(); 590 | $this->tokens_to_be_inserted_after = array(); 591 | $this->tokens_to_be_replaced = array(); 592 | } 593 | 594 | private function get_till($tokens,$search_token,$start) 595 | { 596 | $return = array(); 597 | while(isset($tokens[$start])) { 598 | if(is_array($tokens[$start])) { 599 | $token_name = token_name($tokens[$start][0]); 600 | $token_content = $tokens[$start++][1]; 601 | $return[] = $token_content; 602 | if($token_name == $search_token ) return $return; 603 | 604 | } else { 605 | $return[] =$tokens[$start]; 606 | if($search_token == $tokens[$start++]) return $return; 607 | } 608 | } 609 | return false; 610 | } 611 | private function get_previous_non_comment($tokens,$start,$return_content = false) 612 | { 613 | while(isset($tokens[$start])) { 614 | if(is_array($tokens[$start])) { 615 | $token_name = token_name($tokens[$start][0]); 616 | $token_content = $tokens[$start][1]; 617 | if($token_name == 'T_WHITESPACE' || $token_name=='T_COMMENT' || $token_name== 'T_MLCOMMENT') { 618 | $start--; 619 | continue; 620 | } 621 | if($return_content) return $token_content;else return $start; 622 | } else return $start; 623 | $start--; 624 | } 625 | return false; 626 | } 627 | private function get_next_non_comment($tokens,$start,$return_content = false) 628 | { 629 | while(isset($tokens[$start])) { 630 | if(is_array($tokens[$start])) { 631 | $token_name = token_name($tokens[$start][0]); 632 | $token_content = $tokens[$start][1]; 633 | if($token_name == 'T_WHITESPACE' || $token_name=='T_COMMENT' || $token_name== 'T_MLCOMMENT') { 634 | $start++; 635 | continue; 636 | } 637 | if($return_content) return $token_content;else return $start; 638 | } else return $start; 639 | $start++; 640 | } 641 | return false; 642 | } 643 | public function patch($passes=array(),$path='') 644 | { 645 | $pass = 0; 646 | $this->last_pass = 0; 647 | foreach($passes as $pass) { 648 | if(method_exists($this,$method_name = 'pass_'.$pass)) { 649 | $this->current_pass = $pass; 650 | $this->tokens_to_be_inserted_after = array(); 651 | $this->tokens_to_be_replaced = array(); 652 | $this->$method_name($path); 653 | $this->last_pass = $pass; 654 | } 655 | } 656 | //echo $this->source[$this->current_pass]; 657 | return $this->source[$this->current_pass]; 658 | } 659 | private function add_tokens_to_be_inserted_after($tp,$token,$node=false) 660 | { 661 | if($node === false) { 662 | if(isset($this->tokens_to_be_inserted_after[$tp][$token])) 663 | $this->tokens_to_be_inserted_after[$tp][$token] += 1; 664 | else 665 | $this->tokens_to_be_inserted_after[$tp][$token] = 1; 666 | } else { 667 | $token = "(codespy-execution-node:$node)"; 668 | if(isset($this->tokens_to_be_inserted_after[$tp][$token])) 669 | $this->tokens_to_be_inserted_after[$tp][$token] += 1; 670 | else 671 | $this->tokens_to_be_inserted_after[$tp][$token] = 1; 672 | } 673 | } 674 | private function add_tokens_to_be_replaced($tp,$token) 675 | { 676 | if(isset($this->tokens_to_be_replaced[$tp][$token])) 677 | $this->tokens_to_be_replaced[$tp][$token] += 1; 678 | else 679 | $this->tokens_to_be_replaced[$tp][$token] = 1; 680 | } 681 | private function change_stuff() 682 | { 683 | $tp=0; 684 | $out = ''; 685 | $tokens = token_get_all($this->source[$this->last_pass]); 686 | $token_count = count($tokens); 687 | while($tp < $token_count) { 688 | if(isset($this->tokens_to_be_inserted_after[$tp])) { 689 | foreach($this->tokens_to_be_inserted_after[$tp] as $k=>$v) { 690 | $out .= str_repeat($k,$v); 691 | } 692 | } 693 | if(isset($this->tokens_to_be_replaced[$tp])) { 694 | foreach($this->tokens_to_be_replaced[$tp] as $k=>$v) { 695 | $out .= str_repeat($k,$v); 696 | } 697 | } else{ 698 | $out .= $this->token_content($tokens[$tp]); 699 | } 700 | $tp++; 701 | } 702 | return $out; 703 | } 704 | public function get_line_pos($tokens,$tp) 705 | { 706 | $charecter_pos = 0; 707 | do{ 708 | $token_name = $this->token_name($tokens[$tp]); 709 | if($token_name == 'T_WHITESPACE' && (strpos($tokens[$tp][1],13)!==false || strpos($tokens[$tp][1],10)!==false) ) { 710 | return $charecter_pos; 711 | } elseif($token_name == ';') { 712 | $charecter_pos++; 713 | $tp--; 714 | } else { 715 | $tp--; 716 | } 717 | 718 | }while($tp>=0); 719 | } 720 | //replace else if with elseif 721 | public function pass_1() 722 | { 723 | $tokens = token_get_all($this->source[$this->last_pass]); 724 | $token_count = count($tokens); 725 | $tp = 0; 726 | $tokens_of_interest = array('T_ELSE'); 727 | while($tp < $token_count) { 728 | if($this->token_name($tokens[$tp]) == 'T_ELSE' ){ 729 | $temp = $this->get_next_non_comment($tokens,$tp+1); 730 | if($this->token_name($tokens[$temp]) == 'T_IF') { 731 | $this->add_tokens_to_be_replaced($tp ,'elseif'); 732 | $this->add_tokens_to_be_replaced($temp ,''); 733 | } 734 | } 735 | $tp++; 736 | } 737 | $this->source[$this->current_pass] = $this->change_stuff() ; 738 | 739 | } 740 | 741 | //convert alternate syntax to conventional syntax, convert colons after case to ';'. 742 | public function pass_2() 743 | { 744 | $tokens = token_get_all($this->source[$this->last_pass]); 745 | $token_count = count($tokens); 746 | $tp = 0; 747 | $tokens_of_interest = array('T_IF','T_FOR','T_FOREACH','T_ELSE','T_ELSEIF','T_WHILE','T_SWITCH'); 748 | $tokens_of_interest_1 = array('T_ENDIF','T_ENDWHILE','T_ENDFOR','T_ENDSWITCH'); 749 | while($tp < $token_count) { 750 | if(in_array($token_name = $this->token_name($tokens[$tp]), $tokens_of_interest )){ 751 | if($token_name == 'T_DO') { 752 | 753 | } else { 754 | $temp = $this->get_next_non_comment($tokens,$tp+1); 755 | if($this->token_name($tokens[$temp]) == '(') { 756 | $temp = $this->get_pair($tokens,$temp); 757 | $temp = $this->get_next_non_comment($tokens,$temp+1); 758 | } 759 | if($this->token_name($tokens[$temp]) == ':') { 760 | $this->add_tokens_to_be_replaced($temp,'{'); 761 | if($token_name == 'T_ELSEIF' || $token_name == 'T_ELSE') { 762 | $this->add_tokens_to_be_inserted_after($tp,'}'); 763 | } 764 | } 765 | } 766 | } elseif(in_array($token_name,$tokens_of_interest_1)) { 767 | $this->add_tokens_to_be_replaced($tp,'}'); 768 | $this->add_tokens_to_be_replaced($this->get_next_non_comment($tokens,$tp+1),' '); 769 | } elseif($token_name == 'T_CASE' || $token_name == 'T_DEFAULT') { 770 | //jump over the ternary operators to get the last colon 771 | if($this->search_token($tokens,$tp,'?',array(';',':'))) { 772 | while($tmp = $this->search_token($tokens,$tp,'?',array(';',':'))) $tp = $tmp+1; 773 | while($tmp = $this->search_token($tokens,$tp,':',array(';'))) $tp = $tmp+1; 774 | } 775 | if($temp = $this->search_token($tokens,$tp,':',array(';'))) { 776 | $this->add_tokens_to_be_replaced($temp ,';'); 777 | } 778 | } 779 | $tp++; 780 | } 781 | 782 | $this->source[$this->current_pass] = $this->change_stuff() ; 783 | } 784 | 785 | // enclode inline if,elseif,for,while statements to blocks. 786 | public function pass_3() 787 | { 788 | $tokens = token_get_all($this->source[$this->last_pass]); 789 | $token_count = count($tokens); 790 | $tp = 0; 791 | $tokens_of_interest = array('T_IF','T_FOR','T_FOREACH','T_ELSE','T_ELSEIF','T_WHILE','T_DO'); 792 | while($tp < $token_count) { 793 | $ct = $tokens[$tp]; 794 | if(in_array($this->token_name($ct), $tokens_of_interest )){ 795 | while(in_array($token_name = $this->token_name($ct), $tokens_of_interest )) { 796 | $temp = $this->get_next_non_comment($tokens,$tp+1); 797 | if($this->token_name($tokens[$temp]) == '(') { 798 | $temp = $this->get_pair($tokens,$temp); 799 | $next = $this->get_next_non_comment($tokens,$temp+1); 800 | } else 801 | $next = $temp; 802 | $ct = $tokens[$next]; 803 | if(!in_array($this->token_name($tokens[$next]), array('{',':',';'))) { 804 | $this->add_tokens_to_be_inserted_after($next,'{'); 805 | if($token_name == 'T_IF') 806 | $this->add_tokens_to_be_inserted_after($this->get_if_end($tokens,$tp)+1,'}'); 807 | else 808 | $this->add_tokens_to_be_inserted_after($this->get_statement_end($tokens,$next)+1,'}'); 809 | 810 | } 811 | $tp = $next; 812 | } 813 | } 814 | $tp++; 815 | } 816 | $this->source[$this->current_pass] = $this->change_stuff() ; 817 | } 818 | // inject trace code in statements 819 | public function pass_4($path = '') 820 | { 821 | $tokens = token_get_all($this->source[$this->last_pass]); 822 | $token_count = count($tokens); 823 | $tp = 0; 824 | $tokens_to_jump_parenthesis = array('T_IF','T_FOR','T_FOREACH','T_ELSEIF','T_WHILE'); 825 | $tokens_to_patch = array('T_FUNCTION'); 826 | $tokens_to_stop_patching = array('T_CLASS','T_TRAIT','T_INTERFACE'); 827 | $offset = 0; 828 | $suspend = true; 829 | $inclass = false; 830 | $executable_statements = 0; 831 | $charecter_pos = 0; 832 | while($tp < $token_count) { 833 | $token_name = $this->token_name($tokens[$tp]); 834 | if(in_array($token_name, $tokens_to_patch )) { 835 | if($blockstart = $this->search_token($tokens,$tp,'{',array(';'))) { 836 | if(!isset($blockend)) $blockend = $this->get_pair($tokens,$blockstart); 837 | $suspend = false; 838 | } 839 | } elseif(in_array($token_name, $tokens_to_stop_patching )) { 840 | if($blockstart = $this->search_token($tokens,$tp,'{',array(';'))) { 841 | $inclass = true; 842 | $classend = $this->get_pair($tokens,$blockstart); 843 | $suspend = true; 844 | } 845 | } 846 | if($inclass && $tp >= $classend) { 847 | $inclass = false; 848 | }; 849 | if($suspend && $inclass) { 850 | $tp++; 851 | continue; 852 | } else { 853 | if(isset($blockend) && $tp >= $blockend) { 854 | $suspend = true; 855 | $tp++; 856 | unset($blockend); 857 | continue; 858 | } 859 | } 860 | if(in_array($token_name, $tokens_to_jump_parenthesis )) { 861 | $temp = $this->get_next_non_comment($tokens,$tp+1); 862 | if($this->token_name($tokens[$temp]) == '(') { 863 | $temp = $this->get_pair($tokens,$temp); 864 | $tp = $this->get_next_non_comment($tokens,$temp+1); 865 | } 866 | } elseif($token_name =='T_END_HEREDOC') { 867 | if($this->token_name($tokens[$temp = $this->get_next_non_comment($tokens,$tp+1)]) == ';') { 868 | $offset++; 869 | $charecter_pos = $this->get_line_pos($tokens,$tp); 870 | $this->add_tokens_to_be_inserted_after($temp+1,PHP_EOL.'\codespy\Analyzer::$coveredlines[__FILE__][__LINE__-'.$offset.']['.$charecter_pos.'][]=\codespy\Analyzer\::$currenttest;'); 871 | $tp = $temp+1; 872 | } 873 | 874 | } elseif($token_name == ';') { 875 | $charecter_pos = $this->get_line_pos($tokens,$tp); 876 | $executable_statements++; 877 | $this->add_tokens_to_be_inserted_after($tp+1,'\codespy\Analyzer::$coveredlines[__FILE__][__LINE__-'.$offset.']['.$charecter_pos.'][]=\codespy\Analyzer::$currenttest;'); 878 | } elseif($token_name == 'T_GOTO' || $token_name == 'T_RETURN' || $token_name == 'T_THROW' || $token_name == 'T_BREAK' || $token_name == 'T_CONTINUE') { 879 | $charecter_pos = $this->get_line_pos($tokens,$tp); 880 | $this->add_tokens_to_be_inserted_after($tp,'\codespy\Analyzer::$coveredlines[__FILE__][__LINE__-'.$offset.']['.($charecter_pos+1).'][]=\codespy\Analyzer::$currenttest;'); 881 | 882 | } elseif($token_name == 'T_WHITESPACE') { 883 | //var_dump(ord($tokens[$tp][1])); 884 | //var_dump(ord("\r")); 885 | //echo ord($tokens[$tp][1]),"\n"; 886 | //var_dump($tokens[$tp][1]); 887 | if((strpos($tokens[$tp][1],13) !==false) || (strpos($tokens[$tp][1],10) !==false)) { 888 | $charecter_pos = 0; 889 | } 890 | 891 | } 892 | $tp++; 893 | } 894 | Analyzer::$executable_statements[$path] = $executable_statements; 895 | 896 | $this->source[$this->current_pass] = $this->change_stuff() ; 897 | 898 | } 899 | //Add branch analysis code 900 | public function pass_5($path) 901 | { 902 | $tokens = token_get_all($this->source[$this->last_pass]); 903 | $token_count = count($tokens); 904 | $tp = 0; 905 | $tokens_to_patch = array('T_FUNCTION'); 906 | $last_class = ''; 907 | while($tp < $token_count) { 908 | $token_name = $this->token_name($tokens[$tp]); 909 | if($token_name == 'T_NAMESPACE') $current_namespace = $this->token_content($tokens[$this->get_next_non_comment($tokens,$tp+1)]); 910 | if($token_name == 'T_CLASS') { 911 | $last_class = $this->token_content($tokens[$this->get_next_non_comment($tokens,$tp+1)]); 912 | if(isset($current_namespace)) $last_class = $current_namespace."\\".$last_class; 913 | } 914 | if(in_array($token_name, $tokens_to_patch )) { 915 | if($blockstart = $this->search_token($tokens,$tp,'{',array(';'))) { 916 | $function_name_tp = $this->get_next_non_comment($tokens,$tp+1); 917 | if(!is_array($tokens[$function_name_tp])) 918 | $function_name_tp = $this->get_next_non_comment($tokens,$function_name_tp+1); 919 | $function_name = $tokens[$function_name_tp][1]; 920 | if(!Analyzer::shouldpatch($function_name,$last_class)) { 921 | $tp++; 922 | continue; 923 | } 924 | Analyzer::$instancenumberfor[$function_name] = 0; 925 | $this->add_tokens_to_be_inserted_after($blockstart+1,"/*0*/"."if(isset(\\codespy\\Analyzer::\$instancenumberfor[__FILE__][__CLASS__][__FUNCTION__])) \\codespy\\Analyzer::\$instancenumberfor[__FILE__][__CLASS__][__FUNCTION__]++; else \\codespy\\Analyzer::\$instancenumberfor[__FILE__][__CLASS__][__FUNCTION__]=0;\\codespy\\Analyzer::\$executionbranches[__FILE__][__CLASS__][__FUNCTION__][\\codespy\\Analyzer::\$instancenumberfor[__FILE__][__CLASS__][__FUNCTION__]][0] = 1;"); 926 | $blockend = $this->get_pair($tokens,$blockstart); 927 | $children = $this->get_children_from($tokens,$blockstart,$blockend,0); 928 | Analyzer::$possiblebranches[$path][$last_class][$function_name] = $children->getPaths(); 929 | Analyzer::$nodetrees[$path][$last_class][$function_name] = $children; 930 | } 931 | } 932 | 933 | $tp++; 934 | } 935 | $this->source[$this->current_pass] = $this->change_stuff(); 936 | } 937 | 938 | //for-output-only copy of pass 5 939 | public function pass_6() 940 | { 941 | $tokens = token_get_all($this->source[$this->last_pass]); 942 | $token_count = count($tokens); 943 | $tp = 0; 944 | $tokens_to_patch = array('T_FUNCTION'); 945 | $last_class = ''; 946 | while($tp < $token_count) { 947 | $token_name = $this->token_name($tokens[$tp]); 948 | if($token_name == 'T_NAMESPACE') $current_namespace = $this->token_content($tokens[$this->get_next_non_comment($tokens,$tp+1)]); 949 | if($token_name == 'T_CLASS') { 950 | $last_class = $this->token_content($tokens[$this->get_next_non_comment($tokens,$tp+1)]); 951 | if(isset($current_namespace)) $last_class = $current_namespace."\\".$last_class; 952 | } 953 | if(in_array($token_name, $tokens_to_patch )) { 954 | if($blockstart = $this->search_token($tokens,$tp,'{',array(';'))) { 955 | $function_name_tp = $this->get_next_non_comment($tokens,$tp+1); 956 | if(!is_array($tokens[$function_name_tp])) 957 | $function_name_tp = $this->get_next_non_comment($tokens,$function_name_tp+1); 958 | $function_name = $tokens[$function_name_tp][1]; 959 | if(!Analyzer::shouldpatch($function_name,$last_class)) { 960 | $tp++; 961 | continue; 962 | } 963 | //Analyzer::$instancenumberfor[$function_name] = 0; 964 | $this->add_tokens_to_be_inserted_after($blockstart+1,"",0); 965 | $blockend = $this->get_pair($tokens,$blockstart); 966 | $children = $this->get_children_from($tokens,$blockstart,$blockend,0,$temp,true); 967 | //Analyzer::$possiblebranches[$function_name] = $children->getPaths(); 968 | } 969 | } 970 | 971 | $tp++; 972 | } 973 | $this->source[$this->current_pass] = $this->change_stuff(); 974 | } 975 | private function get_children_from_switch($tokens,&$tp,$parent_node,&$last_node = null,$foroutput = false) 976 | { 977 | $start = $this->search_token($tokens,$tp,'(',array(';')); 978 | $start = $this->get_pair($tokens,$start); 979 | $start = $this->search_token($tokens,$start,'{',array(';')); 980 | $end = $this->get_pair($tokens,$start); 981 | $this->break_levels[] = $end; 982 | $current_node_id = $parent_node; 983 | $tree = new tree; 984 | while($tp = $this->search_token($tokens,$tp,array('T_CASE','T_DEFAULT'),array('}')) ) { 985 | if($this->token_name($tokens[$tp]) == 'T_DEFAULT' ) $founddefault = true; 986 | 987 | //jump over the ternary operators to get the last colon 988 | $case_end = $this->get_end_of_case($tokens,$tp+1); 989 | 990 | if($temp = $this->search_token($tokens,$tp,array(';','T_CLOSE_TAG'))) { 991 | $case_start = $temp; 992 | $new_node = $current_node_id = $current_node_id + 1; 993 | if(!in_array($parent_node,$this->break_nodes)) $tree->addChild($parent_node,$new_node); 994 | $this->add_tokens_to_be_inserted_after($case_start+1,"/*$new_node*/"."\\codespy\\Analyzer::\$executionbranches[__FILE__][__CLASS__][__FUNCTION__][\\codespy\\Analyzer::\$instancenumberfor[__FILE__][__CLASS__][__FUNCTION__]][$new_node] = 1;",$foroutput?$new_node:false); 995 | if($children = $this->get_children_from($tokens,$case_start,$case_end,$current_node_id,$current_node_id,$foroutput)) { 996 | $tree->addChildren($children); 997 | 998 | } 999 | } 1000 | $tp = $case_end; 1001 | } 1002 | $new_node = $current_node_id+1; 1003 | if(!isset($founddefault)) 1004 | if(!in_array($parent_node,array_merge($this->break_nodes , $this->continue_nodes,$this->return_nodes))) $tree->addChild($parent_node,$new_node); 1005 | $tree->addChildToAllLeavesOfParent($parent_node,$new_node,array_merge($this->break_nodes , $this->continue_nodes,$this->return_nodes)); 1006 | if(isset($this->jumps[$end])) { 1007 | foreach($this->jumps[$end] as $jn) { 1008 | $tree->addChild($jn,$new_node); 1009 | } 1010 | } 1011 | $this->add_tokens_to_be_inserted_after($end+1,"/*$new_node*/"."\\codespy\\Analyzer::\$executionbranches[__FILE__][__CLASS__][__FUNCTION__][\\codespy\\Analyzer::\$instancenumberfor[__FILE__][__CLASS__][__FUNCTION__]][$new_node] = 1;",$foroutput?$new_node:false); 1012 | $last_node = $new_node; 1013 | $tp = $end; 1014 | array_pop($this->break_levels); 1015 | return $tree; 1016 | 1017 | } 1018 | private function get_children_from_boolean($tokens,&$start,$end,$parent_node,$group_node,&$last_node = null,$foroutput=false) 1019 | { 1020 | $new_node = $parent_node; 1021 | $tree = new tree; 1022 | while($start<=$end) { 1023 | $token_name = $this->token_name($tokens[$start]); 1024 | if(in_array($token_name,array('T_BOOLEAN_AND','T_BOOLEAN_OR'))) { 1025 | $new_node++; 1026 | $tree->addChild($parent_node,$new_node); 1027 | $parent_node = $new_node; 1028 | $this->add_tokens_to_be_inserted_after($start," && (\\codespy\\Analyzer::\$executionbranches[__FILE__][__CLASS__][__FUNCTION__][\\codespy\\Analyzer::\$instancenumberfor[__FILE__][__CLASS__][__FUNCTION__]][$new_node] = 1)",$foroutput?"$new_node":$foroutput); 1029 | $src_node = $new_node; 1030 | if($this->token_name($tokens[$start = $this->get_next_non_comment($tokens,$start+1)]) == '('){ 1031 | $group_node = $new_node++; 1032 | $tree->addChild($src_node,$new_node); 1033 | $this->add_tokens_to_be_inserted_after($start+1,"(\\codespy\\Analyzer::\$executionbranches[__FILE__][__CLASS__][__FUNCTION__][\\codespy\\Analyzer::\$instancenumberfor[__FILE__][__CLASS__][__FUNCTION__]][$new_node] = 1)&&",$foroutput?($group_node+1):$foroutput); 1034 | $thisend = $this->get_pair($tokens,$start); 1035 | $last_node = $new_node; 1036 | if($node_children = $this->get_children_from_boolean($tokens,$start,$thisend,$new_node,$group_node+1,$last_node,$foroutput)) { 1037 | $tree->addChildren($node_children); 1038 | } 1039 | $parent_node = $new_node = $last_node; 1040 | $tree->addChildToAllDecendants($src_node,$new_node+1); 1041 | continue; 1042 | } 1043 | } 1044 | $start++; 1045 | } 1046 | $last_node = $new_node; 1047 | return $tree; 1048 | 1049 | } 1050 | 1051 | 1052 | 1053 | 1054 | 1055 | private function get_children_from_if($tokens,&$start,$parent_node,&$last_node,$foroutput = false) 1056 | { 1057 | $tree = new tree; 1058 | $current_node_id = $parent_node; 1059 | $tp = $start; 1060 | $tree = new tree; 1061 | while(1) { 1062 | $token_name = $this->token_name($tokens[$this->get_next_non_comment($tokens,$tp)]); 1063 | if(in_array($token_name,array('T_IF','T_ELSE','T_ELSEIF'))) { 1064 | if($token_name == 'T_ELSE') { $elsefound = true; 1065 | $start = $this->search_token($tokens,$tp,'{',array(';')); 1066 | } else { 1067 | if($token_name == 'T_IF') { 1068 | if(isset($found_if)) break; 1069 | $found_if = true; 1070 | } 1071 | $boolleft = $start = $this->search_token($tokens,$tp,'(',array(';')); 1072 | $boolright = $start = $this->get_pair($tokens,$start); 1073 | $temp_current_node = $current_node_id; 1074 | $parent_node_old = $parent_node; 1075 | if( $node_children =$this->get_children_from_boolean($tokens,$boolleft,$boolright,$current_node_id,$current_node_id,$current_node_id,$foroutput) ) { 1076 | if($temp_current_node != $current_node_id) { 1077 | $parent_node = $current_node_id; 1078 | $tree->addChildToAllDecendants($temp_current_node+1,$current_node_id+1); 1079 | } 1080 | $tree->addChildren($node_children); 1081 | } 1082 | $start = $this->search_token($tokens,$start,'{',array(';')); 1083 | } 1084 | $end = $this->get_pair($tokens,$start); 1085 | 1086 | $new_node = $current_node_id = $current_node_id + 1; 1087 | if(!in_array($parent_node,array_merge($this->break_nodes , $this->continue_nodes,$this->return_nodes))) { 1088 | $tree->addChild($parent_node,$new_node); 1089 | } 1090 | $this->add_tokens_to_be_inserted_after($start+1,"/*$new_node*/"."\\codespy\\Analyzer::\$executionbranches[__FILE__][__CLASS__][__FUNCTION__][\\codespy\\Analyzer::\$instancenumberfor[__FILE__][__CLASS__][__FUNCTION__]][$new_node] = 1;",$foroutput?$new_node:false); 1091 | if($children = $this->get_children_from($tokens,$start,$end,$current_node_id,$current_node_id,$foroutput)) { 1092 | $tree->addChildren($children); 1093 | } 1094 | $tp = $end+1; 1095 | } else break; 1096 | 1097 | 1098 | } 1099 | $new_node = $current_node_id+1; 1100 | if(!isset($elsefound)) { 1101 | if(!in_array($parent_node_old,array_merge($this->break_nodes , $this->continue_nodes,$this->return_nodes))) $tree->addChild($parent_node_old,$new_node); 1102 | } 1103 | echo "Pn = $parent_node_old"; 1104 | $tree->addChildToAllLeavesOfParent($parent_node,$new_node,array_merge($this->break_nodes , $this->continue_nodes,$this->return_nodes)); 1105 | $this->add_tokens_to_be_inserted_after($end+1,"/*$new_node*/"."\\codespy\\Analyzer::\$executionbranches[__FILE__][__CLASS__][__FUNCTION__][\\codespy\\Analyzer::\$instancenumberfor[__FILE__][__CLASS__][__FUNCTION__]][$new_node] = 1;",$foroutput?$new_node:false); 1106 | $last_node = $new_node; 1107 | $start = $end; 1108 | array_pop($this->break_levels); 1109 | return $tree; 1110 | } 1111 | private function get_children_from_loop($tokens,&$start,$parent_node,&$last_node,$foroutput = false) 1112 | { 1113 | $tree = new tree; 1114 | $current_node_id = $parent_node; 1115 | $tp = $start; 1116 | $tree = new tree; 1117 | $start = $this->search_token($tokens,$tp,'(',array(';')); 1118 | $start = $this->get_pair($tokens,$start); 1119 | if(!($temp= $this->search_token($tokens,$start,'{',array(';')))) return false; 1120 | $start = $temp; 1121 | $end = $this->get_pair($tokens,$start); 1122 | $this->break_levels[] = $end; 1123 | 1124 | $new_node = $current_node_id = $current_node_id + 1; 1125 | if(!in_array($parent_node,array_merge($this->break_nodes , $this->continue_nodes,$this->return_nodes))) $tree->addChild($parent_node,$new_node); 1126 | $this->add_tokens_to_be_inserted_after($start+1,"/*$new_node*/"."\\codespy\\Analyzer::\$executionbranches[__FILE__][__CLASS__][__FUNCTION__][\\codespy\\Analyzer::\$instancenumberfor[__FILE__][__CLASS__][__FUNCTION__]][$new_node] = 1;",$foroutput?$new_node:false); 1127 | if($children = $this->get_children_from($tokens,$start,$end,$current_node_id,$current_node_id,$foroutput)) { 1128 | $tree->addChildren($children); 1129 | } 1130 | $tp = $end+1; 1131 | $new_node = $current_node_id+1; 1132 | if(!in_array($parent_node,$this->break_nodes)) $tree->addChild($parent_node,$new_node); 1133 | $tree->addChildToAllLeavesOfParent($parent_node,$new_node,array_merge($this->break_nodes , $this->continue_nodes,$this->return_nodes)); 1134 | if(isset($this->jumps[$end])) { 1135 | foreach($this->jumps[$end] as $jn) { 1136 | $tree->addChild($jn,$new_node); 1137 | } 1138 | } 1139 | $this->add_tokens_to_be_inserted_after($end+1,"/*$new_node*/"."\\codespy\\Analyzer::\$executionbranches[__FILE__][__CLASS__][__FUNCTION__][\\codespy\\Analyzer::\$instancenumberfor[__FILE__][__CLASS__][__FUNCTION__]][$new_node] = 1;",$foroutput?$new_node:false); 1140 | $last_node = $new_node; 1141 | $start = $end; 1142 | array_pop($this->break_levels); 1143 | return $tree; 1144 | } 1145 | private function get_children_from_do($tokens,&$start,$parent_node,&$last_node,$foroutput = false) 1146 | { 1147 | $tree = new tree; 1148 | $current_node_id = $parent_node; 1149 | $tp = $start; 1150 | $tree = new tree; 1151 | if(!($temp= $this->search_token($tokens,$start,'{',array(';')))) return false; 1152 | $start = $temp; 1153 | $end = $this->get_pair($tokens,$start); 1154 | $this->break_levels[] = $end; 1155 | 1156 | $new_node = $current_node_id = $current_node_id + 1; 1157 | if(!in_array($parent_node,array_merge($this->break_nodes , $this->continue_nodes,$this->return_nodes))) $tree->addChild($parent_node,$new_node); 1158 | $this->add_tokens_to_be_inserted_after($start+1,"/*$new_node*/"."\\codespy\\Analyzer::\$executionbranches[__FILE__][__CLASS__][__FUNCTION__][\\codespy\\Analyzer::\$instancenumberfor[__FILE__][__CLASS__][__FUNCTION__]][$new_node] = 1;",$foroutput?$new_node:false); 1159 | if($children = $this->get_children_from($tokens,$start,$end,$current_node_id,$current_node_id,$foroutput)) { 1160 | $tree->addChildren($children); 1161 | } 1162 | $tp = $end+1; 1163 | $tp = $this->search_token($tokens,$tp,'('); 1164 | $end = $this->get_pair($tokens,$tp)+1; 1165 | $new_node = $current_node_id+1; 1166 | if(!in_array($parent_node,$this->break_nodes)) $tree->addChild($parent_node,$new_node); 1167 | $tree->addChildToAllLeavesOfParent($parent_node,$new_node,array_merge($this->break_nodes , $this->continue_nodes,$this->return_nodes)); 1168 | if(isset($this->jumps[$end])) { 1169 | foreach($this->jumps[$end] as $jn) { 1170 | $tree->addChild($jn,$new_node); 1171 | } 1172 | } 1173 | $this->add_tokens_to_be_inserted_after($end+1,"/*$new_node*/"."\\codespy\\Analyzer::\$executionbranches[__FILE__][__CLASS__][__FUNCTION__][\\codespy\\Analyzer::\$instancenumberfor[__FILE__][__CLASS__][__FUNCTION__]][$new_node] = 1;",$foroutput?$new_node:false); 1174 | $last_node = $new_node; 1175 | $start = $end; 1176 | array_pop($this->break_levels); 1177 | return $tree; 1178 | } 1179 | 1180 | 1181 | private function get_children_from($tokens,$start,$end,$parent_node,&$last_node = null,$foroutput=false) 1182 | { 1183 | $branch_points = array('T_IF','T_FOR','T_FOREACH','T_WHILE','T_SWITCH','T_DO','T_BOOLEAN_OR','T_BOOLEAN_AND'); 1184 | $tp = $start; 1185 | $tree= new tree; 1186 | $current_node_id = $parent_node; 1187 | $this->break_nodes = $this->return_nodes = $this->continue_nodes = array(); 1188 | while($tp<$end) { 1189 | $token_name = $this->token_name($tokens[$tp]); 1190 | if($token_name == 'T_FUNCTION') { 1191 | if($blockstart = $this->search_token($tokens,$tp,'{',array(';'))) { 1192 | if($tp = $this->get_pair($tokens,$blockstart)) { $tp++;continue;} 1193 | } 1194 | } elseif($token_name == 'T_BREAK') { 1195 | $level = $this->get_break_level($tokens,$tp); 1196 | $break_target = $this->break_levels[count($this->break_levels)-$level]; 1197 | $this->jumps[$break_target][] = $current_node_id; 1198 | $this->break_nodes[] = $current_node_id; 1199 | } elseif($token_name == 'T_CONTINUE') { 1200 | $this->continue_nodes[] = $current_node_id; 1201 | } elseif($token_name == 'T_RETURN' || $token_name == 'T_THROW') { 1202 | $this->return_nodes[] = $current_node_id; 1203 | } 1204 | 1205 | elseif(in_array($token_name, $branch_points)) { 1206 | if($token_name == 'T_SWITCH') { 1207 | /* 1208 | $trace = debug_backtrace(false); 1209 | foreach ($trace as $t) var_dump($t['function']); 1210 | */ 1211 | $node_children = $this->get_children_from_switch($tokens,$tp,$current_node_id,$current_node_id,$foroutput); 1212 | $tree->addChildren($node_children); 1213 | }elseif(in_array($token_name,array('T_IF'))) { 1214 | $temp = $current_node_id; 1215 | $node_children = $this->get_children_from_if($tokens,$tp,$current_node_id,$current_node_id,$foroutput); 1216 | $tree->addChildren($node_children); 1217 | } elseif($token_name == 'T_DO') { 1218 | if( $node_children = $this->get_children_from_do($tokens,$tp,$current_node_id,$current_node_id,$foroutput)) 1219 | $tree->addChildren($node_children); 1220 | } 1221 | elseif(in_array($token_name,array('T_BOOLEAN_AND','T_BOOLEAN_OR')) ) { 1222 | $boolright = $this->get_right_of_boolean($tokens,$tp); 1223 | if( $node_children =$this->get_children_from_boolean($tokens,$tp,$boolright,$current_node_id,$current_node_id,$current_node_id,$foroutput)) { 1224 | $tree->addChildren($node_children); 1225 | } 1226 | } 1227 | else { 1228 | if( $node_children = $this->get_children_from_loop($tokens,$tp,$current_node_id,$current_node_id,$foroutput)) 1229 | $tree->addChildren($node_children); 1230 | } 1231 | } 1232 | $tp++; 1233 | } 1234 | $last_node = $current_node_id; 1235 | return $tree; 1236 | } 1237 | public function get_right_of_boolean($tokens,$tp) 1238 | { 1239 | if($temp = $this->search_token($tokens,$tp+1,array(':' ,'?', '.' , ';' , ',' ,')','T_CLOSE_TAG','T_BOOLEAN_OR','T_BOOLEAN_AND'),array(),true)) return $this->get_previous_non_comment($tokens,$temp-1); 1240 | } 1241 | public function get_left_of_boolean($tokens,$tp) 1242 | { 1243 | if($temp = $this->search_back($tokens,$tp-1,array('{','(',';','T_OPEN_TAG',',','?',':','T_BOOLEAN_OR','T_BOOLEAN_AND'),array(),true)) return $this->get_next_non_comment($tokens,$temp+1); 1244 | } 1245 | 1246 | public function get_break_level($tokens,$tp) 1247 | { 1248 | if($this->token_name($token = $tokens[$this->get_next_non_comment($tokens,$tp+1)]) == 'T_LNUMBER') return (($temp = $this->token_content($token))>0)?$temp:1; else return 1; 1249 | } 1250 | 1251 | public function get_ternary_statement_end($tokens,$start) 1252 | { 1253 | $token_count = count($tokens); 1254 | 1255 | $tp = $start; 1256 | $qm_count = 0; 1257 | while($tp<$token_count) { 1258 | $token_name = $this->token_name($tokens[$tp]); 1259 | if($token_name ==';' || ($token_name ==':' && $qm_count==0) || $token_name == 'T_CLOSE_TAG') break; 1260 | elseif($token_name == '?') $qm_count++; 1261 | elseif($token_name == ':') $qm_count--; 1262 | elseif($token_name == '(') $tp = $this->get_pair($tokens,$tp); 1263 | $tp++; 1264 | } 1265 | return $tp; 1266 | } 1267 | public function get_context($tokens,$tp,$offset= 3) 1268 | { 1269 | $start = ($tp>$offset) ? $tp-$offset: 0; 1270 | $end = ($tp+$offset < count($tokens))?$tp+$offset: count($tokens)-1; 1271 | return $this->get_contents($tokens,$start,$end); 1272 | 1273 | } 1274 | public function get_contents($tokens,$start,$end) 1275 | { 1276 | $return = ''; 1277 | for(;$start<=$end;$start++) { 1278 | if(is_array($tokens[$start])) $return .= $tokens[$start][1];else $return .= $tokens[$start]; 1279 | } 1280 | return $return; 1281 | } 1282 | public function get_ternary_sub_statements($tokens,$start) 1283 | { 1284 | $tp =$real_start = $this->get_next_non_comment($tokens,$start+1); 1285 | $token_count = count($tokens); 1286 | $qm_count = 0; 1287 | while(($tp<$token_count) && ( ($token_name = $this->token_name($tokens[$tp])) !=':' || ($qm_count > 0) ) ) { 1288 | if($token_name == '?') $qm_count++; 1289 | elseif($token_name == ':') $qm_count--; 1290 | $tp++; 1291 | } 1292 | $mid_segment_end = $tp; 1293 | $end = $this->get_ternary_statement_end($tokens,$tp+1); 1294 | return array($start,$mid_segment_end,$end); 1295 | } 1296 | private function get_end_of_case($tokens,$start) 1297 | { 1298 | $token_count = count($tokens); 1299 | while($start< $token_count) { 1300 | $token_name = $this->token_name($tokens[$start]); 1301 | if($token_name == '}' || $token_name == 'T_BREAK'|| $token_name == 'T_CONTINUE') return $start; 1302 | elseif($token_name == '{') $start = $this->get_pair($tokens,$start)+1; 1303 | else $start++; 1304 | } 1305 | } 1306 | private function search_back($tokens,$start,$search,$breakon = array(),$jump_para = false) 1307 | { 1308 | $token_count = count($tokens); 1309 | while($start> 0) { 1310 | $token_name = $this->token_name($tokens[$start]); 1311 | if($token_name == ')' && $jump_para) { 1312 | $start = $this->get_pair($tokens,$start,-1)-1; 1313 | continue; 1314 | } 1315 | if($breakon && in_array($token_name,$breakon)) return false; 1316 | if(is_array($search)) { 1317 | if(in_array($token_name,$search)) return $start; 1318 | 1319 | } else { 1320 | if($token_name == $search) return $start; 1321 | } 1322 | $start--; 1323 | } 1324 | } 1325 | private function search_token($tokens,$start,$search,$breakon = array(),$jump_para = false) 1326 | { 1327 | $token_count = count($tokens); 1328 | while($start< $token_count) { 1329 | $token_name = $this->token_name($tokens[$start]); 1330 | if($token_name == '(' && $jump_para) { 1331 | $start = $this->get_pair($tokens,$start,1); 1332 | $start++; 1333 | continue; 1334 | } 1335 | if($breakon && in_array($token_name,$breakon)) return false; 1336 | if(is_array($search)) { 1337 | if(in_array($token_name,$search)) return $start; 1338 | 1339 | } else { 1340 | if($token_name == $search) return $start; 1341 | } 1342 | $start++; 1343 | } 1344 | } 1345 | private function get_statement_start($tokens,$end) 1346 | { 1347 | while($end>0 && in_array($this->token_name($tokens[$end]), array(';','}','T_WHITESPACE'))) $end--; 1348 | while($end>0 && !in_array($token_name = $this->token_name($tokens[$end]), array('{',';','T_COMMENT','T_ML_COMMENT','T_DOC_COMMENT'))) { 1349 | if($token_name != 'T_WHITESPACE') $last_non_whitespace = $end; 1350 | if($token_name == ')') $end = $this->get_pair($tokens,$end,-1);else $end--; 1351 | } 1352 | return $last_non_whitespace; 1353 | } 1354 | private function get_statement_end($tokens,$start) 1355 | { 1356 | while(isset($tokens[$start])) { 1357 | $token_name = $this->token_name($tokens[$start]); 1358 | if($token_name == 'T_IF') return $this->get_if_end($tokens,$start); 1359 | if($token_name == ';') 1360 | return $start; 1361 | elseif(in_array($token_name,array('(','['))) 1362 | $start = $this->get_pair($tokens,$start); 1363 | elseif($token_name == '{') 1364 | return $this->get_pair($tokens,$start); 1365 | else 1366 | $start++; 1367 | } 1368 | } 1369 | private function get_if_end($tokens,$start,$full=false) 1370 | { 1371 | $tp = $start; 1372 | $next = $this->get_statement_after_if($tokens,$tp); 1373 | $token_name = $this->token_name($tokens[$next]); 1374 | if($token_name == 'T_IF' ) 1375 | $end = $this->get_if_end($tokens,$next,true); 1376 | elseif($token_name == '{' ) 1377 | $end = $this->get_pair($tokens,$next); 1378 | else 1379 | $end = $this->get_statement_end($tokens,$next); 1380 | if(!$full) return $end; 1381 | while($this->token_name($tokens[$next = $this->get_next_non_comment($tokens,$end+1)]) == 'T_ELSEIF') { 1382 | $end = $this->get_if_end($tokens,$next); 1383 | } 1384 | if($this->token_name($tokens[$next]) == 'T_ELSE') { 1385 | return $this->get_if_end($tokens,$next); 1386 | } else return $end; 1387 | 1388 | } 1389 | private function get_statement_after_if($tokens,$start) 1390 | { 1391 | if($this->token_name( $tokens[$next =$this->get_next_non_comment($tokens,$start+1)]) == '(') 1392 | return $this->get_next_non_comment($tokens,$this->get_pair($tokens,$next)+1); 1393 | else 1394 | return $this->get_next_non_comment($tokens,$start+1); 1395 | } 1396 | 1397 | private function get_pair($tokens,$start,$dir = 1) 1398 | { 1399 | $pairs = array('{'=>'}','['=>']','('=>')','}'=>'{',']'=>'[',')'=>'('); 1400 | $src = $this->token_name($tokens[$start]); 1401 | if(isset($pairs[$src ])) { 1402 | $pair = $pairs[$src ]; 1403 | $count = 1; 1404 | while($count>0) { 1405 | if($dir == 1) 1406 | $start = $next = $this->get_next_non_comment($tokens,++$start); 1407 | else 1408 | $start = $next = $this->get_previous_non_comment($tokens,--$start); 1409 | 1410 | if($next!==false) 1411 | $next = $this->token_name($tokens[$next]); 1412 | else 1413 | return false; 1414 | if($next == 'T_CURLY_OPEN' || $next == 'T_DOLLAR_OPEN_CURLY_BRACES' ) $next = '{'; 1415 | if($next == $src) 1416 | $count++; 1417 | elseif($next == $pair) 1418 | $count--; 1419 | } 1420 | return $start; 1421 | } 1422 | } 1423 | 1424 | private function token_content($token) 1425 | { 1426 | if(is_array($token)) { 1427 | if(isset($token[0])) { 1428 | return $token[1]; 1429 | } 1430 | else 1431 | return false; 1432 | } else { 1433 | return $token; 1434 | } 1435 | } 1436 | private function token_name($token) 1437 | { 1438 | if(is_array($token)) { 1439 | if(isset($token[0])) { 1440 | return token_name($token[0]); 1441 | } 1442 | else 1443 | return false; 1444 | } else { 1445 | return $token; 1446 | } 1447 | } 1448 | 1449 | 1450 | } 1451 | class tree{ 1452 | private $nodes = array(); 1453 | private $paths= array(); 1454 | public function __construct($nodes=array()) 1455 | { 1456 | if($nodes) $this->nodes = $nodes; 1457 | } 1458 | public function nodes() 1459 | { 1460 | return $this->nodes; 1461 | } 1462 | public function dumpNode($node,$path=array(),$highlightpaths=array()) 1463 | { 1464 | if(isset($this->nodes[$node])) { 1465 | $path[] = $node; 1466 | $path_str = join(',',$path); 1467 | $highlight = false; 1468 | foreach($highlightpaths as $v) { 1469 | if(strpos($v,$path_str) === 0) $highlight = true; 1470 | } 1471 | if(count($this->nodes[$node]) == 0) { 1472 | if($highlight) echo "$node";else echo $node; 1473 | } else { 1474 | if($highlight) $cont = "$node";else $cont = $node; 1475 | echo "
$cont
"; 1476 | foreach($this->nodes[$node] as $n) { 1477 | echo ""; 1480 | } 1481 | echo "
"; 1478 | $this->dumpNode($n,$path,$highlightpaths); 1479 | echo "
"; 1482 | } 1483 | } 1484 | } 1485 | 1486 | public function addChildren($tree) 1487 | { 1488 | foreach($tree->getNodes() as $k=>$nodes) { 1489 | foreach($nodes as $v ) { 1490 | $this->addChild($k,$v); 1491 | } 1492 | } 1493 | } 1494 | public function addChildToAllDecendants($parent,$child,$exclude = array()) 1495 | { 1496 | while(1) { 1497 | $this->addChild($parent,$child); 1498 | if(isset($this->nodes[$parent][0])) $parent = $this->nodes[$parent][0]; else break; 1499 | if($parent == $child) break; 1500 | } 1501 | } 1502 | 1503 | public function addChildToAllLeavesOfParent($parent,$child,$exclude = array()) 1504 | { 1505 | foreach($this->getLeavesForTreeAt($parent,$exclude) as $leaf) { 1506 | if(!in_array($leaf,$exclude) && $leaf!=$child) { 1507 | $this->addChild($leaf,$child); 1508 | } 1509 | } 1510 | 1511 | } 1512 | public function getLeavesForTreeAt($parent,$exclude = array()) 1513 | { 1514 | $leaves = array(); 1515 | $paths = $this->getPaths($parent); 1516 | foreach($paths as $path) { 1517 | foreach($exclude as $en) if(in_array($en,$path)) continue 2; 1518 | $leaves[] = end($path); 1519 | } 1520 | return $leaves; 1521 | } 1522 | public function getSiblings($node) 1523 | { 1524 | $siblings = array(); 1525 | foreach($this->getParents($node) as $parent) { 1526 | $siblings = array_merge($siblings,$this->nodes[$parent]); 1527 | } 1528 | return $siblings; 1529 | 1530 | } 1531 | public function getParents($node) 1532 | { 1533 | $return = array(); 1534 | foreach($this->nodes as $k=>$v) { 1535 | if(($found = array_search($node,$v)) !== false ) { 1536 | $return[] = $k; 1537 | } 1538 | } 1539 | return $return; 1540 | } 1541 | public function getNodes() 1542 | { 1543 | return $this->nodes; 1544 | } 1545 | public function addChild($parent,$child) 1546 | { 1547 | if(isset($this->nodes[$parent])) { 1548 | if(in_array($child,$this->nodes[$parent]) === false ) { 1549 | array_push($this->nodes[$parent],$child); 1550 | if(!isset($this->nodes[$child])) $this->nodes[$child] = array(); 1551 | } 1552 | } 1553 | else { 1554 | $this->nodes[$parent] = array($child); 1555 | if(!isset($this->nodes[$child])) $this->nodes[$child] = array(); 1556 | } 1557 | } 1558 | public function getPaths($root=0) 1559 | { 1560 | $this->paths = array(); 1561 | $this->dfs($root); 1562 | return ($this->paths); 1563 | } 1564 | public function dfs($parent,$path = array()) 1565 | { 1566 | if(!$path) $path = array($parent); 1567 | $visited = array(); 1568 | if(isset($this->nodes[$parent]) && count($this->nodes[$parent]) >0) { 1569 | while(($child = $this->getUnvisitedChild($parent,$visited)) !== false ) { 1570 | $visited[$child]=1; 1571 | $this->dfs($child,$temp = array_merge($path,array($child))); 1572 | } 1573 | } else { 1574 | $this->paths[] = $path; 1575 | } 1576 | 1577 | } 1578 | public function getUnvisitedChild($parent,$visited) 1579 | { 1580 | if(isset($this->nodes[$parent])) foreach($this->nodes[$parent] as $v) { 1581 | if(!isset($visited[$v])) return $v; 1582 | } 1583 | return false; 1584 | } 1585 | } 1586 | stream_wrapper_unregister("file"); 1587 | stream_wrapper_register("file", "codespy\str_wrp"); 1588 | $_codecoverage_object_to_call_upon_exit = new analyzer; 1589 | set_exception_handler(array($_codecoverage_object_to_call_upon_exit,'exception_handler')); 1590 | --------------------------------------------------------------------------------