Rob van der Woude's Scripting Pages
Powered by GeSHi

Source code for checkvarsphp.cs

(view source code of checkvarsphp.cs as plain text)

  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Text;
  5. using System.Text.RegularExpressions;
  6.  
  7.  
  8. namespace RobvanderWoude
  9. {
  10. 	internal class CheckVarsPHP
  11. 	{
  12. 		static readonly string progver = "1.01";
  13.  
  14.  
  15. 		static int Main( string[] args )
  16. 		{
  17. 			#region Initialize Variables
  18.  
  19. 			SortedList<string, int> functions = new SortedList<string, int>( );
  20. 			SortedList<string, int> variables = new SortedList<string, int>( );
  21. 			bool showfuncs = true;
  22. 			bool showvars = true;
  23. 			bool unusedonly = false;
  24. 			Regex regex;
  25. 			string pattern;
  26. 			string scriptcode = String.Empty;
  27. 			string scriptext = String.Empty;
  28. 			string scriptfile = String.Empty;
  29. 			int columnwidth = 12;
  30. 			int unusedfuncs = 0;
  31. 			int unusedvars = 0;
  32.  
  33. 			#endregion Initialize Variables
  34.  
  35.  
  36. 			#region Command Line Parsing
  37.  
  38. 			if ( args.Length == 0 )
  39. 			{
  40. 				return ShowHelp( );
  41. 			}
  42.  
  43. 			foreach ( string arg in args )
  44. 			{
  45. 				if ( arg[0] == '/' )
  46. 				{
  47. 					if ( arg.ToUpper( ) == "/?" )
  48. 					{
  49. 						return ShowHelp( );
  50. 					}
  51. 					else if ( arg.ToUpper( ) == "/F" )
  52. 					{
  53. 						if ( !showvars )
  54. 						{
  55. 							return ShowHelp( "Duplicate command line switch /F" );
  56. 						}
  57. 						if ( !showfuncs )
  58. 						{
  59. 							return ShowHelp( "Use /F or /V or neither, but not both" );
  60. 						}
  61. 						showvars = false;
  62. 					}
  63. 					else if ( arg.ToUpper( ) == "/U" )
  64. 					{
  65. 						if ( unusedonly )
  66. 						{
  67. 							return ShowHelp( "Duplicate command line switch /U" );
  68. 						}
  69. 						unusedonly = true;
  70. 					}
  71. 					else if ( arg.ToUpper( ) == "/V" )
  72. 					{
  73. 						if ( !showfuncs )
  74. 						{
  75. 							return ShowHelp( "Duplicate command line switch /V" );
  76. 						}
  77. 						if ( !showvars )
  78. 						{
  79. 							return ShowHelp( "Use /F or /V or neither, but not both" );
  80. 						}
  81. 						showfuncs = false;
  82. 					}
  83. 					else
  84. 					{
  85. 						return ShowHelp( "Invalid command line switch \"{0}\"", arg );
  86. 					}
  87. 				}
  88. 				else
  89. 				{
  90. 					if ( !String.IsNullOrWhiteSpace( scriptfile ) )
  91. 					{
  92. 						return ShowHelp( "Duplicate command line argument for PHP file" );
  93. 					}
  94. 					if ( !File.Exists( arg ) )
  95. 					{
  96. 						return ShowHelp( "Invalid file name or file not found: \"{0}\"", arg );
  97. 					}
  98. 					scriptext = Path.GetExtension( arg ).ToLower( );
  99. 					if ( scriptext != ".php" )
  100. 					{
  101. 						return ShowHelp( "Invalid file type \"{0}\"", arg );
  102. 					}
  103. 					scriptfile = Path.GetFullPath( arg );
  104. 				}
  105.  
  106. 			}
  107.  
  108. 			if ( String.IsNullOrWhiteSpace( scriptfile ) )
  109. 			{
  110. 				return ShowHelp( "Please specify a source file" );
  111. 			}
  112.  
  113. 			#endregion Command Line Parsing
  114.  
  115.  
  116. 			#region Read File
  117.  
  118. 			// Read the code from the file
  119. 			scriptcode = File.ReadAllText( scriptfile, Encoding.UTF8 );
  120. 			// Strip everything NOT PHP
  121. 			if ( scriptcode.Contains( "<?php" ) )
  122. 			{
  123. 				string purephp = string.Empty;
  124. 				pattern = @"<\?php[\w\W]*?\?>";
  125. 				regex = new Regex( pattern );
  126. 				if ( regex.IsMatch( scriptcode ) )
  127. 				{
  128. 					foreach ( Match match in regex.Matches( scriptcode ) )
  129. 					{
  130. 						purephp += match.Value + "\n\n";
  131. 					}
  132. 				}
  133. 				scriptcode = purephp;
  134. 			}
  135. 			// Remove comment lines from the code (does NOT strip comments starting halfway on a line)
  136. 			pattern = @"(^|\n|\r)[ \t]*//[^\n\r]+";
  137. 			regex = new Regex( pattern );
  138. 			scriptcode = regex.Replace( scriptcode, String.Empty );
  139. 			// Remove comment blocks from the code (does NOT strip comments starting halfway on a line)
  140. 			pattern = @"(^|\n|\r)[ \t]*/\*[\w\W]*\*/";
  141. 			regex = new Regex( pattern );
  142. 			scriptcode = regex.Replace( scriptcode, String.Empty );
  143.  
  144. 			#endregion Read File
  145.  
  146.  
  147. 			#region List Functions
  148.  
  149. 			// Create a list of subroutines found in the code
  150. 			// function names are NOT case sensitive
  151. 			if ( showfuncs )
  152. 			{
  153. 				pattern = @"(?:^|\n|\r)[ \t]*(?:function)[ \t]+([A-Z_][^\s\(]+)\(";
  154. 				regex = new Regex( pattern, RegexOptions.IgnoreCase );
  155. 				if ( regex.IsMatch( scriptcode ) )
  156. 				{
  157. 					MatchCollection matches = regex.Matches( scriptcode );
  158. 					if ( matches.Count > 0 )
  159. 					{
  160. 						foreach ( Match match in matches )
  161. 						{
  162. 							bool listed = false;
  163. 							string func = match.Groups[1].Value;
  164. 							foreach ( string key in functions.Keys )
  165. 							{
  166. 								if ( func.ToLower( ) == key.ToLower( ) )
  167. 								{
  168. 									listed = true;
  169. 								}
  170. 							}
  171. 							if ( !listed )
  172. 							{
  173. 								functions[func] = 0;
  174. 								columnwidth = Math.Max( func.Length, columnwidth );
  175. 							}
  176. 						}
  177. 					}
  178. 				}
  179. 			}
  180.  
  181. 			#endregion List Functions
  182.  
  183.  
  184. 			#region Check for Nested Functions
  185.  
  186. 			if ( showfuncs && functions.Count > 1 )
  187. 			{
  188. 				pattern = @"function[\t ]+([_A-Z]\w*)[\t ]*\([^\n\r\)]*\)([\w\W]*?)function[\t ]+[_A-Z]";
  189. 				regex = new Regex( pattern, RegexOptions.IgnoreCase );
  190. 				if ( regex.IsMatch( scriptcode ) )
  191. 				{
  192. 					foreach ( Match match in regex.Matches( scriptcode ) )
  193. 					{
  194. 						int errorcount = 0;
  195. 						string funcname = match.Groups[1].Value;
  196. 						string funccode = match.Groups[2].Value;
  197. 						int curlybraces = 0;
  198. 						foreach ( char chr in funccode.ToCharArray( ) )
  199. 						{
  200. 							if ( chr == '{' )
  201. 							{
  202. 								curlybraces++;
  203. 							}
  204. 							else if ( chr == '}' )
  205. 							{
  206. 								curlybraces--;
  207. 							}
  208. 							if ( curlybraces < 0 )
  209. 							{
  210. 								errorcount++;
  211. 							}
  212. 						}
  213. 						if ( curlybraces != 0 )
  214. 						{
  215. 							errorcount++;
  216. 						}
  217. 						if ( errorcount > 0 )
  218. 						{
  219. 							RedLine( "Nested function or unterminated curly braces detected in function \"{0}\"\n\n", funcname );
  220. 						}
  221. 					}
  222. 				}
  223. 			}
  224.  
  225. 			#endregion Check for Nested Functions
  226.  
  227.  
  228. 			#region List Variables
  229.  
  230. 			// Create a list of variables found in the code and count their occurrence
  231. 			// Variable names ARE case sensitive
  232. 			if ( showvars )
  233. 			{
  234. 				pattern = @"\$[A-Z_][A-Z0-9_]*(\[([\$'\""]?[A-Z_][A-Z0-9_]*['\""]?)\])?";
  235. 				regex = new Regex( pattern, RegexOptions.IgnoreCase );
  236. 				if ( regex.IsMatch( scriptcode ) )
  237. 				{
  238. 					MatchCollection matches = regex.Matches( scriptcode );
  239. 					if ( matches.Count > 0 )
  240. 					{
  241. 						foreach ( Match match in matches )
  242. 						{
  243. 							string varstring = match.ToString( );
  244. 							if ( !variables.ContainsKey( varstring ) )
  245. 							{
  246. 								variables[varstring] = 1;
  247. 								if ( varstring.Length > columnwidth )
  248. 								{
  249. 									columnwidth = varstring.Length;
  250. 								}
  251. 							}
  252. 							else
  253. 							{
  254. 								variables[varstring] += 1;
  255. 							}
  256. 						}
  257. 					}
  258. 				}
  259. 			}
  260.  
  261. 			#endregion List Variables
  262.  
  263.  
  264. 			#region Count and Display Functions Usage
  265.  
  266. 			// Iterate through the list of subroutines and count the occurrences of its name
  267. 			if ( showfuncs )
  268. 			{
  269. 				List<string> keys = new List<string>( functions.Keys );
  270. 				foreach ( string func in keys )
  271. 				{
  272. 					pattern = string.Format( @"\b{0}\(", func );
  273. 					regex = new Regex( pattern );
  274. 					if ( regex.IsMatch( scriptcode ) )
  275. 					{
  276. 						functions[func] = regex.Matches( scriptcode ).Count - 1;
  277. 					}
  278. 				}
  279. 				// Show the results
  280. 				if ( unusedonly )
  281. 				{
  282. 					Console.WriteLine( "{0} Unused Function{1}{2}", unusedfuncs, ( unusedfuncs == 1 ? String.Empty : "s" ), ( unusedfuncs == 0 ? String.Empty : ":" ) );
  283. 					Console.WriteLine( "{0}==============={1}{2}", new String( '=', unusedfuncs.ToString( ).Length ), ( unusedfuncs == 1 ? String.Empty : "=" ), ( unusedfuncs == 0 ? String.Empty : "=" ) );
  284. 				}
  285. 				else
  286. 				{
  287. 					Console.WriteLine( "{0,-" + columnwidth + "}    Occurrences:", "Function:" );
  288. 					Console.WriteLine( "{0,-" + columnwidth + "}    ============", "=========" );
  289. 				}
  290. 				foreach ( string key in functions.Keys )
  291. 				{
  292. 					if ( functions[key] == 0 )
  293. 					{
  294. 						if ( unusedonly )
  295. 						{
  296. 							Console.WriteLine( key );
  297. 						}
  298. 						else
  299. 						{
  300. 							RedLine( string.Format( "{0,-" + columnwidth + "}    {1}", key, functions[key] ) );
  301. 						}
  302. 						unusedfuncs += 1;
  303. 					}
  304. 					else if ( !unusedonly )
  305. 					{
  306. 						Console.WriteLine( "{0,-" + columnwidth + "}    {1}", key, functions[key] );
  307. 					}
  308. 				}
  309. 				Console.WriteLine( );
  310. 			}
  311.  
  312. 			#endregion Count and Display Functions Usage
  313.  
  314.  
  315. 			#region Count and Display Variables Usage
  316.  
  317. 			if ( showvars )
  318. 			{
  319. 				// Show the results
  320. 				if ( unusedonly )
  321. 				{
  322. 					Console.WriteLine( "{0} Unused Variable{1}{2}", unusedvars, ( unusedvars == 1 ? String.Empty : "s" ), ( unusedvars == 0 ? String.Empty : ":" ) );
  323. 					Console.WriteLine( "{0}================{1}{2}", new String( '=', unusedvars.ToString( ).Length ), ( unusedvars == 1 ? String.Empty : "=" ), ( unusedvars == 0 ? String.Empty : "=" ) );
  324. 				}
  325. 				else
  326. 				{
  327. 					Console.WriteLine( "{0,-" + columnwidth + "}    Occurrences:", "Variable:" );
  328. 					Console.WriteLine( "{0,-" + columnwidth + "}    ============", "=========" );
  329. 				}
  330. 				foreach ( string key in variables.Keys )
  331. 				{
  332. 					if ( variables[key] == 1 && !IsSuperGlobal( key ) )
  333. 					{
  334. 						if ( unusedonly )
  335. 						{
  336. 							Console.WriteLine( key );
  337. 						}
  338. 						else
  339. 						{
  340. 							RedLine( String.Format( "{0,-" + columnwidth + "}    {1}", key, variables[key] ) );
  341. 						}
  342. 						unusedvars += 1;
  343. 					}
  344. 					else if ( !unusedonly )
  345. 					{
  346. 						Console.WriteLine( "{0,-" + columnwidth + "}    {1}", key, variables[key] );
  347. 					}
  348. 				}
  349. 				Console.WriteLine( );
  350. 			}
  351.  
  352. 			#endregion Count and Display Variables Usage
  353.  
  354.  
  355. 			int rc = 0;
  356. 			if ( showfuncs )
  357. 			{
  358. 				rc += unusedfuncs;
  359. 			}
  360. 			if ( showvars )
  361. 			{
  362. 				rc += unusedvars;
  363. 			}
  364. 			return rc;
  365. 		}
  366.  
  367.  
  368. 		static bool IsSuperGlobal( string var )
  369. 		{
  370. 			string pattern = @"^\$(GLOBALS|_(SERVER|GET|POST|FILES|COOKIE|SESSION|REQUEST|ENV))(\[[^\]]+\])?$";
  371. 			Regex regex = new Regex( pattern );
  372. 			return regex.IsMatch( var.Trim( ) );
  373. 		}
  374.  
  375.  
  376. 		static void RedLine( string line, params object[] rlargs )
  377. 		{
  378. 			Console.ForegroundColor = ConsoleColor.Red;
  379. 			if ( rlargs.Length > 0 )
  380. 			{
  381. 				Console.WriteLine( line, rlargs );
  382. 			}
  383. 			else
  384. 			{
  385. 				Console.WriteLine( line );
  386. 			}
  387. 			Console.ResetColor( );
  388. 		}
  389.  
  390.  
  391. 		static int ShowHelp( params string[] errmsg )
  392. 		{
  393. 			#region Error Message
  394.  
  395. 			if ( errmsg.Length > 0 )
  396. 			{
  397. 				List<string> errargs = new List<string>( errmsg );
  398. 				errargs.RemoveAt( 0 );
  399. 				Console.Error.WriteLine( );
  400. 				Console.ForegroundColor = ConsoleColor.Red;
  401. 				Console.Error.Write( "ERROR:\t" );
  402. 				Console.ForegroundColor = ConsoleColor.White;
  403. 				Console.Error.WriteLine( errmsg[0], errargs.ToArray( ) );
  404. 				Console.ResetColor( );
  405. 			}
  406.  
  407. 			#endregion Error Message
  408.  
  409.  
  410. 			#region Help Text
  411.  
  412. 			/*
  413. 			CheckVarsPHP.exe,  Version 1.01
  414. 			Check PHP code for unused variables and functions
  415.  
  416. 			Usage:    CheckVarsPHP.exe  "phpfile"  [ /F | /V ]  [ /U ]
  417.  
  418. 			Where:    "phpfile"         is the PHP file to be examined
  419. 			          /F                tests Functions only
  420. 			                            (default: functions as well as variables)
  421. 			          /V                tests Variables only
  422. 			                            (default: functions as well as variables)
  423. 			          /U                list Unused functions and variables only
  424. 			                            (default: list all functions and variables)
  425.  
  426. 			Notes:    If functions are tested, a (limited) test for nested functions
  427. 			          is performed as well.
  428. 			          In PHP variables are not declared, so a single occurrence of a
  429. 			          variable means it is not used, unless it is a superglobal which
  430. 			          may be declared elsewhere. Keep in mind though, that this program
  431. 			          does not take into account the variables' scope, so a variable
  432. 			          name used once in two functions may in fact be unused but will
  433. 			          escape detection by this program.
  434. 			          The program's return code equals the number of unused functions
  435. 			          and/or variables, or -1 in case of (command line) errors.
  436.  
  437. 			Written by Rob van der Woude
  438. 			https://www.robvanderwoude.com
  439. 			*/
  440.  
  441. 			#endregion Help Text
  442.  
  443.  
  444. 			#region Display Help Text
  445.  
  446. 			Console.Error.WriteLine( );
  447.  
  448. 			Console.Error.WriteLine( "CheckVarsPHP.exe,  Version {0}", progver );
  449.  
  450. 			Console.Error.WriteLine( "Check PHP code for unused variables and functions" );
  451.  
  452. 			Console.Error.WriteLine( );
  453.  
  454. 			Console.Error.Write( "Usage:    " );
  455. 			Console.ForegroundColor = ConsoleColor.White;
  456. 			Console.Error.WriteLine( "CheckVarsPHP.exe  \"phpfile\"  [ /F | /V ]  [ /U ]" );
  457. 			Console.ResetColor( );
  458.  
  459. 			Console.Error.WriteLine( );
  460.  
  461. 			Console.Error.Write( "Where:    " );
  462. 			Console.ForegroundColor = ConsoleColor.White;
  463. 			Console.Error.Write( "\"phpfile\"" );
  464. 			Console.ResetColor( );
  465. 			Console.Error.WriteLine( "         is the PHP file to be examined" );
  466.  
  467. 			Console.ForegroundColor = ConsoleColor.White;
  468. 			Console.Error.Write( "          /F" );
  469. 			Console.ResetColor( );
  470. 			Console.Error.Write( "                tests " );
  471. 			Console.ForegroundColor = ConsoleColor.White;
  472. 			Console.Error.Write( "F" );
  473. 			Console.ResetColor( );
  474. 			Console.Error.WriteLine( "unctions only" );
  475.  
  476. 			Console.Error.WriteLine( "                            (default: functions as well as variables)" );
  477.  
  478. 			Console.ForegroundColor = ConsoleColor.White;
  479. 			Console.Error.Write( "          /V" );
  480. 			Console.ResetColor( );
  481. 			Console.Error.Write( "                tests " );
  482. 			Console.ForegroundColor = ConsoleColor.White;
  483. 			Console.Error.Write( "V" );
  484. 			Console.ResetColor( );
  485. 			Console.Error.WriteLine( "ariables only" );
  486.  
  487. 			Console.Error.WriteLine( "                            (default: functions as well as variables)" );
  488.  
  489. 			Console.ForegroundColor = ConsoleColor.White;
  490. 			Console.Error.Write( "          /U" );
  491. 			Console.ResetColor( );
  492. 			Console.Error.Write( "                list " );
  493. 			Console.ForegroundColor = ConsoleColor.White;
  494. 			Console.Error.Write( "U" );
  495. 			Console.ResetColor( );
  496. 			Console.Error.WriteLine( "nused functions and variables only" );
  497.  
  498. 			Console.Error.WriteLine( "                            (default: list all functions and variables)" );
  499.  
  500. 			Console.Error.WriteLine( );
  501.  
  502. 			Console.Error.WriteLine( "Notes:    If functions are tested, a (limited) test for nested functions" );
  503.  
  504. 			Console.Error.WriteLine( "          is performed as well." );
  505.  
  506. 			Console.Error.WriteLine( "          In PHP variables are not declared, so a single occurrence of a" );
  507.  
  508. 			Console.Error.WriteLine( "          variable means it is not used, unless it is a superglobal which" );
  509.  
  510. 			Console.Error.WriteLine( "          may be declared elsewhere. Keep in mind though, that this program" );
  511.  
  512. 			Console.Error.WriteLine( "          does not take into account the variables' scope, so a variable" );
  513.  
  514. 			Console.Error.WriteLine( "          name used once in two functions may in fact be unused but will" );
  515.  
  516. 			Console.Error.WriteLine( "          escape detection by this program." );
  517.  
  518. 			Console.Error.WriteLine( "          The program's return code equals the number of unused functions" );
  519.  
  520. 			Console.Error.WriteLine( "          and/or variables, or -1 in case of (command line) errors." );
  521.  
  522. 			Console.Error.WriteLine( );
  523.  
  524. 			Console.Error.WriteLine( "Written by Rob van der Woude" );
  525.  
  526. 			Console.Error.WriteLine( "https://www.robvanderwoude.com" );
  527.  
  528. 			#endregion Display Help Text
  529.  
  530.  
  531. 			return -1;
  532. 		}
  533.  
  534.  
  535. 		static void UnderLine( string text )
  536. 		{
  537. 			Console.WriteLine( text );
  538. 			Console.WriteLine( new string( '=', text.Replace( "\n", "" ).Replace( "\r", "" ).Length ) );
  539. 		}
  540. 	}
  541. }

page last uploaded: 2021-01-27