<?php


class RevPHPNodeVisitor extends PhpParser\NodeVisitorAbstract
{
	var $symtbl;  // Symbol table, instance of SymbolTable, so scopes.

	var $decode_func_names;  // single-string-arg functions to run on arguments in malware
	var $special_func_names; // function names to de-obusc arguments of

	// Replace any arguments that are calls to these functions
	// with the return value of the function call, if you can.
	var $replaced_func_names;

	// associative array of function names in
	// malware (keys), names to replace with (values)
	var $replacement_func_names;

	// Flag to control actual evaluation of functions
	// named in $decode_func_names
	var $evaluate_functions;

	var $fix_variable_names;

	var $anon_functions; // strings - bodies of create_function() calls
	var $anon_function_counter;  // For unique names for create_function() bodies.

	public function __construct(
		$extra_functions = null,
		$special_functions = null,
		$replacement_functions = null,
		$remove_comments = true,
		$replacement_function_names,
		$evaluate_functions = true,
		$fix_variable_names = false
	) {
		$this->evaluate_functions = $evaluate_functions;
		$this->fix_variable_names = $fix_variable_names;

		// Array of function names to apply to string arguments to do a
		// limited amount of decoding.
		$this->decode_func_names = array(
			'base64_decode', 'gzuncompress', 'strrev', 'gzinflate',
			'str_rot13', 'urldecode', 'chr', 'ord', 'strtolower',
			'strtoupper', 'base64_encode', 'range', 'implode',
			'bzdecompress',
		);
		foreach ($special_functions as $fname) {
			$this->decode_func_names[] = strtolower($fname);
		}

		// Function where knowing the value of string
		// argument(s) helps. Used to get obfuscated code to run.
		// Note that eval() is a "language construct".
		$this->special_func_names = array(
			'assert', 'preg_replace', 'eval', 'print',
			'file_put_contents', 'basename', 'function_exists',
			'mkdir', 'fopen', 'symlink', 'chdir', 'file', 'eregi',
			'chmod', 'die', 'strpos', 'curl_setopt', 'rmdir',
			'unlink', 'symlink', 'explode', 'file_exists',
			'filesize', 'ini_set', 'error_reporting', 'base64_decode'
		);
		// Superglobals - worth decoding the dimension.
		$this->superglobals = array(
			'_SERVER', '_REQUEST', '_GET', '_POST', '_COOKIE', 'GLOBALS'
		);

		foreach ($extra_functions as $fname)
			$this->special_func_names[] = strtolower($fname);

		$this->replaced_func_names = array('base64_decode');
		foreach ($replacement_functions  as $fname)
			$this->replaced_func_names[] = strtolower($fname);

		$this->replacement_func_names = array();
		if (is_array($replacement_function_names)) {
			foreach ($replacement_function_names as $key => $val) {
				$this->replacement_func_names[strtolower($key)] = $val;
			}
		}

		$this->symtbl = new SymbolTable();

		$this->remove_comments = $remove_comments;

		$this->anon_functions = Array();
		$this->anon_function_counter = 0;
	}

	public function afterTraverse(array $nodes) {
		$this->symtbl = null;
	}

	# Actually execute select functions if they have statically-
	# determined argument values.
	public function evaluateFunctionCall(PhpParser\Node\Expr\FuncCall $fcall, &$function_return) {
		#fwrite(STDERR, "enter evaluateFunctionCall, {$fcall->getType()}\n");
		$found_return = FALSE;
		if (!$this->evaluate_functions) {
			return FALSE;
		}
		if ($this->findValue($fcall->name, $fname)) {
			#fwrite(STDERR, "Found function name \"$fname\"\n");
			$fname = strtolower($fname);

			if (in_array($fname, $this->decode_func_names)) {
				$arg_count = count($fcall->args);
				$args_array = array();
				for ($i = 0; $i < $arg_count; ++$i) {
					if ($this->findValue($fcall->args[$i], $arg_value)) {
						$args_array[] = $arg_value;
					}
				}

				if (count($args_array) == $arg_count) {
					# XXX - Is suppressing errors really a good idea?
					$function_return = @call_user_func_array($fname, $args_array);
					if ($function_return !== null)
						$found_return = TRUE;
					else
						fwrite(STDERR, "Called function named \"$fname\", null return\n");
				}
			} else
			// Special case: create_function()
			if ($fname == 'create_function') {
				$found_return = $this->evaluateCreateFunction($fcall, $function_return);
			}
		}
		return $found_return;
	}

	// Turn a create_function() invocation into a named function,
	// which gets pretty-printed at the end of the pretty-printing.
	public function evaluateCreateFunction(PhpParser\Node\Expr\FuncCall $fcall, &$function_return) {
		#fwrite(STDERR, "enter evaluateCreateFunction, {$fcall->getType()}\n");
		// Figure out formal arguments. Assume correct format,
		//    since attackers will want their code to run...
		if ($this->findValue($fcall->args[0], $formal_arguments)) {
			if (!$this->findValue($fcall->args[1], $function_body)) {
				return FALSE;
			}
		}

		$function_name = 'anon_func_'.$this->anon_function_counter;
		$this->anon_function_counter++;

		$this->anon_functions[] = 'function '.$function_name.'('.$formal_arguments.') {'
			.$function_body . '}';

		$function_return = $function_name;

		return TRUE;
	}


	// return TRUE if you can statically find the value of
	// $node (a few functions get evaluated, see evaluateFunctionCall(),
	// and arrays might get created. Return FALSE if you can't
	// statically find the value of a PhpParser\Node.
	public function findValue(PhpParser\Node $node, &$value) {
		#fwrite(STDERR, "Enter findValue() node type {$node->getType()} at source line {$node->getLine()}\n");

		$found_value = FALSE;

		if ($node instanceof PhpParser\Node\Scalar\String_) {
			$value = $node->value;
			$found_value = TRUE;
		} else
		if ($node instanceof PhpParser\Node\Expr\BinaryOp\Concat) {
			if ($this->concatStrings($node, $value)) {
				$found_value = TRUE;
			} else {
			}
		} else
		if ($node instanceof PhpParser\Node\Expr\Variable) {
			$variable_value = null;
			if ($node->name instanceof PhpParser\Node)
				$this->findValue($node->name,$name_string);
			else
				$name_string = $node->name;

			if ($this->symtbl->symbolValue($name_string, $variable_value)) {
				$value = $variable_value;
				$found_value = TRUE;
			}
		} else
		if ($node instanceof PhpParser\Node\Name) {
			$value = $node->parts[0];
			$found_value = TRUE;
		} else
		if ($node instanceof PhpParser\Node\Expr\FuncCall) {
			if ($this->evaluateFunctionCall($node, $value))
				$found_value = TRUE;
		} else
		if ($node instanceof PhpParser\Node\Arg) {
			$arg_string = null;
			if ($this->findValue($node->value,$arg_string)) {
				$value = $arg_string;
				$found_value = TRUE;
			}
		} else
		if ($node instanceof PhpParser\Node\Name) {
			# This doesn't seem right.
			$value = $node->parts[0];
			$found_value = TRUE;
		} else
		if ($node instanceof PhpParser\Node\Scalar\LNumber) {
			$value = intval($node->value);
			$found_value = TRUE;
		} else
		if ($node instanceof PhpParser\Node\Scalar\DNumber) {
			$value = floatval($node->value);
			$found_value = TRUE;
		} else
		if ($node instanceof PhpParser\Node\Expr\ArrayDimFetch) {
			$found_value = $this->findArrayValue($node, $value);
		}
		#fwrite(STDERR, "Exit findValue() node type {$node->getType()} at source line {$node->getLine()}\n");
		return $found_value;
	}

	function findArrayValue(PhpParser\Node\Expr\ArrayDimFetch $node, &$value) {
		$found_value = false;
		$array_name = null;
		if ($this->findName($node->var, $array_name)) {
			if (is_string($array_name) && $this->symtbl->checkGLOBALSsynonym($array_name)) {
				if ($this->findValue($node->dim, $globals_name)) {
					if ($this->symtbl->globalSymbolValue($globals_name, $value))
						$found_value = TRUE;
				}
			} else
			if (is_array($array_name)) {
				// This only gets to 2D arrays. Not sure why 3D doesn't work.
				if ($this->findValue($node->dim, $dimvalue)) {
					if (isset($array_name[$dimvalue])) {
						$value = $array_name[$dimvalue];
						$found_value = TRUE;
					} else {
						fwrite(STDERR, "Problem accessing array at line {$node->getLine()}\n");
					}
				}
			} else {
				# The following is a hack around the way that $GLOBALS['varname']
				# has a value in the global symbol table, but I'm not passing the
				# "this variable is a global" back up from where the code finds 'varname'.
				# I can't think of what context this might misfire, however.
				if (!$this->symtbl->symbolValue($array_name, $the_array))
					$found_the_array = $this->symtbl->globalSymbolValue($array_name, $the_array);
				else
					$found_the_array = TRUE;

				if ($found_the_array) {

					# $the_array should actually hold something of type array() at this point.
					if ($this->findValue($node->dim, $dimvalue)) {
						# fwrite(STDERR, "ArrayDimFetch, dimension value $dimvalue\n");
						if (isset($the_array[$dimvalue])) {
							$value = $the_array[$dimvalue];
							$found_value = TRUE;
						} else {
							fwrite(STDERR, "Problem accessing array \${$array_name}[".$dimvalue."] at line {$node->getLine()}\n");
						}
					}
				}
			}
		} else {
			fwrite(STDERR, "Could not find array name, so no value, line {$node->var->getLine()}\n");
		}
		return $found_value;
	}

	// Find whatever string the Node evaluates to,
	// don't find the value of the string in the symbol table.
	public function findName(PhpParser\Node $node, &$varname) {
		$found_string = FALSE;
		if ($node instanceof PhpParser\Node\Expr\BinaryOp\Concat) {
			if ($this->concatStrings($node, $varname))
				$found_string = TRUE;
		} else
		if ($node instanceof PhpParser\Node\Scalar\String_) {
			$varname = $node->value; # XXX - ???
			$found_string = TRUE;
		} else
		if ($node instanceof PhpParser\Node\Expr\ArrayDimFetch) {
			if ($this->findName($node->var, $array_name)) {
				if ($this->symtbl->checkGLOBALSsynonym($array_name)) {
					# Then the variable name is the "dim" of the ArrayDimFetch, right?
					if ($this->findValue($node->dim, $globals_name)) {
						$varname = $globals_name;
						$found_string = TRUE;
						# XXX - for the lvalue of an assignment, how do we tell
						# the caller that this is a global variable?
					}
				} else {
					# Try to get the value of the array at the key.
					if ($this->symtbl->symbolValue($array_name, $the_array)) {
						if ($node->dim) {
							if ($this->findValue($node->dim, $dimvalue)) {
								$varname = $the_array[$dimvalue];
								$found_string = TRUE;
							}
						}
					}
				}
			} else {
				fwrite(STDERR, "Could not find array's name\n");
			}
		} else
		if ($node instanceof PhpParser\Node\Expr\Variable) {
			if ($node->name instanceof PhpParser\Node\Expr) {
				if ($this->findValue($node->name, $varname)) {
					$found_string = TRUE;
				} else {
					fwrite(STDERR, "Couldn't find variable name\n");
				}
			} else
			if (is_string($node->name)) {
				$varname = $node->name;
				$found_string = TRUE;
			} else {
				debug_print_backtrace();
			}
		} else
		if ($node instanceof PhpParser\Node\Expr\List_) {
			if ($node->getSubNodeNames()[0] instanceof PhpParser\Node) {
				if ($this->findName($node->name, $varname)) {
					$found_string = TRUE;
				}
			}
		} else {
			if (isset($node->name) && is_string($node->name)) {
				$varname = $node->name;
				$found_string = TRUE;
			} else {
				# Something dreadfully wrong. This is pretty much the
				# very last thing it could be.
				fwrite(STDERR, "Problem in findName(), no name, line {$node->getLine()}: ".print_r($node, TRUE)."\n");
				debug_print_backtrace();
			}
		}
		#fwrite(STDERR, "leave findName, {$node->getType()}, name: \"$varname\"\n");
		return $found_string;
	}

	public function calculateArrayElementValue(
		PhpParser\Node\Expr\ArrayDimFetch $node,
			&$elementvalue) {

		// ArrayDimFetch has two member, $var and $dim, which would look
		// something like this in PHP code:  $var[$dim]
		// $var can have type PhpParser\Node\Expr\ArrayDimFetch, too,
		// something like: $var[$dim1][$dim]
		// which means this must be recursive.

		if ($this->symtbl->checkGLOBALSsynonym($array_name)) {
			# Then the variable name is the "dim" of the ArrayDimFetch, right?
			if ($this->findValue($node->dim, $globals_name)) {
				$varname = $globals_name;
				$found_string = TRUE;
				# XXX - for the lvalue of an assignment, how do we tell
				# the caller that this is a global variable?
			}
		} else {
			# Try to get the value of the array at the key.
			if ($this->symtbl->symbolValue($array_name, $the_array)) {
				if ($node->dim) {
					if ($this->findValue($node->dim, $dimvalue)) {
						$varname = $the_array[$dimvalue];
						$found_string = TRUE;
					}
				}
			}
		}
		return $found_string;
	}

	public function concatStrings(PhpParser\Node\Expr\BinaryOp\Concat $concat, &$concatenated) {
		$found_string = FALSE;
		if ($this->findValue($concat->left, $leftstring)
			&& $this->findValue($concat->right, $rightstring)) {
			$concatenated = $leftstring.$rightstring;
			$found_string = TRUE;
		} else {
			# Replace left or right nodes with statically-determinable values, if possible
			if ($this->findValue($concat->left, $leftvalue)) {
				if (is_string($leftvalue))
					$concat->left = $this->newNodeAsType($leftvalue, false);
			}
			if ($this->findValue($concat->right, $rightvalue)) {
				if (is_string($rightvalue))
					$concat->right = $this->newNodeAsType($rightvalue, false);
			}
		}
		return $found_string;
	}

	public function enterNode(PhpParser\Node $node) {
		if ($node instanceof PhpParser\Node\Stmt\Function_) {
			$this->symtbl->pushScope();

			foreach ($node->params as $formal_argument)
				$this->symtbl->addSymbol($formal_argument->name, NULL, FALSE);
		} else
		if ($node instanceof PhpParser\Node\Stmt\Class_) {
			$this->symtbl->pushScope();
		} else
		if ($node instanceof PhpParser\Node\Stmt\ClassMethod) {
			$this->symtbl->pushScope();

			foreach ($node->params as $formal_argument)
				$this->symtbl->addSymbol($formal_argument->name, NULL, FALSE);
		}
	}

	public function newNodeAsType($something, $as_name = FALSE) {
		$noder = null;
		if (is_string($something)) {
			$noder = $as_name? new PhpParser\Node\Expr\Variable($something)
				:new PhpParser\Node\Scalar\String_($something);
		} else
		if (is_int($something)) {
			$noder = new PhpParser\Node\Scalar\LNumber($something);
		} else
		if (is_float($something)) {
			$noder = new PhpParser\Node\Scalar\DNumber($something);
		} else
		if (is_array($something)) {
			$intermediate = array();
			foreach ($something as $key => $value) {
				$intermediate[$key] = $this->newNodeAsType($value);
			}
			$noder = new PhpParser\Node\Expr\Array_($intermediate);
		} else
		if (is_bool($something)) {
			$noder = new PhpParser\Node\Expr\ConstFetch(
				new PhpParser\Node\Name($something?'true':'false')
			);
		} else { # Punt.  Treat it as a string.
			$noder = new PhpParser\Node\Scalar\String_($something);
		}
		return $noder;
	}

	// Find an lvalue and an rvalue, match them in $this->symtbl.
    public function performAssignment(PhpParser\Node\Expr\Assign $node) {
		#fwrite(STDERR, "Enter performAssignment() node type {$node->getType()} at source line {$node->getLine()}\n");
		#fwrite(STDERR, "\tlvalue of node type {$node->var->getType()}\n");
		#fwrite(STDERR, print_r($node->var, true)."\n");
		#fwrite(STDERR, "\trvalue of node type {$node->expr->getType()}\n");
		#fwrite(STDERR, print_r($node->expr, true)."\n");
		$namestring = null;

		// Put the rvalue into the parse tree.
		if ($this->findValue($node->expr, $valuestring)) {
			$node->expr = $this->newNodeAsType($valuestring);
		}

		if ($node->var instanceof PhpParser\Node\Expr\ArrayDimFetch) {
			if ($this->findName($node->var->var, $arrayname)) {
				if (!$this->symtbl->checkGLOBALSsynonym($arrayname)) {
					$this->performArrayAssignment($node);
					return;
				} else {
					// Assigning to $GLOBALS[] or a synonym of it
					if ($this->findValue($node->var->dim, $dimension)) {
						if ($this->findValue($node->expr, $value)) {
							$this->symtbl->addGlobalSymbol($dimension, $value, TRUE);
						}
					}
				}
			}
		}

		if ($this->findName($node->var, $namestring)) {

			$valuestring = null;

			if ($node->expr instanceof PhpParser\Node\Expr\Array_) {
				// Only a separate case because I can't figure out how to
				// set up a call to array() with all the different arguments.
				$array_node = $node->expr;
				$new_array = array();
				foreach ($array_node->items as $idx => $item) {
					// This will handle array-of-array, but nothing deeper
					if ($item->value instanceof PhpParser\Node\Expr\Array_) {
						$sub_array = array();
						foreach ($item->value->items as $subidx => $subitem) {
							if ($this->findValue($subitem->value, $subitem_value)) {
								$sub_array[] = $subitem_value;
							}
						}
						$new_array[] = $sub_array;
					} else {
						if (is_object($item->value) && $this->findValue($item->value, $item_value)) {
							$array_node->items[$idx] = $this->newNodeAsType($item_value);
							$new_array[] = $item_value;
						} else {
							$array_node->items[$idx] = $this->newNodeAsType($item->value);
							$new_array[] = $item->value;
						}
					}
				}
				$this->symtbl->addSymbol($namestring, $new_array);

			} else
			if ($node->expr instanceof PhpParser\Node\Expr\Closure) {
				#fwrite(STDERR, "Assigning a Closure to a variable\n");
				# Just note that $namestring denotes a symbol, not a value.
				$this->symtbl->addSymbol($namestring, 'Anonymous Function Closure');
			} else
			if ($this->findValue($node->expr, $valuestring)) {
				$node->expr = $this->newNodeAsType($valuestring);
				# Need to add name of variable and value into
				# the symbol table.
				$this->symtbl->addSymbol($namestring, $valuestring);
			}
			else {
				// The RHS of "=" has no value that we know about.
				// Special case: $something = $GLOBALS;
				if ($node->expr instanceof PhpParser\Node\Expr\Variable && $this->findName($node->expr, $rname) &&
					strcmp($rname, 'GLOBALS') == 0) {  // I'm not sure if assigning a synonym of $GLOBALS works here too
					// Assignment of $GLOBALS to something makes
					// that something into a synonym, not a copy,
					// of $GLOBALS
					$this->symtbl->addGLOBALSsynonym($namestring);
					$this->symtbl->addGlobalSymbol($namestring, null, FALSE);
				} else {
					# Just note that $namestring denotes a symbol, not a value.
					$this->symtbl->addSymbol($namestring, null, FALSE);
				}
			}
		} else {
			if ($node->expr instanceof PhpParser\Node\Expr\Array_) {
				$array_node = $node->expr;
				foreach ($array_node->items as $idx => $item) {
					if ($this->findValue($item->value, $item_value))
						$array_node->items[$idx] = $this->newNodeAsType($item_value);
				}
			}
		}
	}

	# Special case: something like $a[$x] = $something; or
	# $x[] = (some expression);
	# Apparently the boolean return isn't too useful.
    public function performArrayAssignment(PhpParser\Node\Expr\Assign $node) {
		$array_assigned = FALSE;
		// lvalue:
		// Find array name.
		if ($this->findName($node->var->var, $arrayname)) {
			// Find array itself in symtbl.
			if ($this->symtbl->symbolValue($arrayname, $array)) {
				// find rvalue.
				if ($this->findValue($node->expr, $rvalue)) {
					// Find dimension.
					if (!isset($node->var->dim)) {
						// If dimesion is null, this is an array push in disgues,
						array_push($array, $rvalue);
						$array_assigned = TRUE;
					} else {
						if ($this->findValue($node->var->dim, $dimvalue)) {
							// otherwise insert rvalue into array with dimension as key.
							$array[$dimvalue] = $rvalue;
							$array_assigned = TRUE;
						}
					}
					$node->expr = $this->newNodeAsType($rvalue);
				}
			}
		}
		if ($array_assigned) {
			$this->symtbl->updateValue($arrayname, $array);
		}
		return $array_assigned;
	}

	// Replace obfuscated/unclear function names at the call site
	// with user-specified function names
	public function replaceFunctionCallNames(PhpParser\Node\Expr\FuncCall $node) {
		if ($this->findValue($node->name, $lookup)) {
			$lookup = strtolower($lookup);
			if (array_key_exists($lookup, $this->replacement_func_names)) {
				$node->name = new PhpParser\Node\Name($this->replacement_func_names[$lookup]);
			}
		}
	}

	// Put in statically-determined strings for the arguments
	// of select functions: preg_replace(), eval(), assert().
	public function replaceArguments(PhpParser\Node\Expr\FuncCall $node) {
		if ($this->findValue($node->name, $function_name)) {
			if (in_array(strtolower($function_name), $this->special_func_names)) {
				foreach ($node->args as $idx => $arg) {
					if ($this->findValue($arg->value, $valuestring)) {
						$node->args[$idx]->value = $this->newNodeAsType($valuestring);
					}
				}
			}
		} else {
			fwrite(STDERR, "Could not find function name to see if arguments get replaced, line {$node->name->getLine()}.\n");
		}
	}

	// Replace arguments of FuncCalls, if any argument is a FunCall
	// in $this->replaced_func_names array. Add element to $replaced_func_names
	// with "-r fname" command line flag.
	public function replaceSelectArguments(PhpParser\Node\Expr\FuncCall $node) {
		#fwrite(STDERR, "enter replaceSelectArguments\n");
		if (count($node->args) < 1) return;
		foreach ($node->args as $idx => $arg) {
			if ($arg->value instanceof PhpParser\Node\Expr\FuncCall) {
				$fc = $arg->value;
				if ($this->findValue($fc->name, $function_name)) {
					if (in_array(strtolower($function_name), $this->replaced_func_names)) {
						if ($this->evaluateFunctionCall($fc, $valuestring)) {
							$node->args[$idx]->value = $this->newNodeAsType($valuestring);
						}
					}
				}
			}
		}
	}

	public function fixIndirectVarName(PhpParser\Node\Expr\Variable $node) {
		if (!$this->fix_variable_names) return;
		if (is_string($node->name)) return;

		if ($this->findName($node, $some_name)) {
			$node->name = $some_name;
		}
	}

	# Replace an indirected function call ($func($args), for example) with
	# the actual statically-determinable function name. Oddly, language
	# constructs like "eval" can be indirected exactly as functions.
	public function fixIndirectFuncName(PhpParser\Node\Expr\FuncCall $node) {
		#fwrite(STDERR, "Enter fixIndirectFuncName() node type {$node->getType()} at source line {$node->getLine()}\n");

		$found_function_name = FALSE;

		if ($node->name instanceof PhpParser\Node\Name) {
			#fwrite(STDERR, "Exit fixIndirectFuncName() node type {$node->getType()} at source line {$node->getLine()}\n");
			return FALSE;  // This is a common, directly-named function call.
		} else
		if ($node->name instanceof PhpParser\Node\Expr\ArrayDimFetch) {
			if ($this->findValue($node->name, $function_name)) {
				$found_function_name = TRUE;
			}
		} else
		if ($node->name instanceof PhpParser\Node\Expr\FuncCall) {
			if ($this->findValue($node->name, $function_name)) {
				$found_function_name = TRUE;
			}
		} else
		if ($node->name instanceof PhpParser\Node\Expr\Variable) {
			if ($this->findValue($node->name, $function_name)) {
				$found_function_name = TRUE;
			}
		}

		if ($found_function_name) {
			$comment = array(
				new PhpParser\Comment(
					"# Replaced function indirection with static value \"$function_name\"",
				 	$node->name->getLine()
				)
			);
			$attributes = array(
				'startLine' => $node->name->getAttribute('startLine'),
				'endLine' => $node->name->getAttribute('endLine')
			);
			$node->name = new PhpParser\Node\Name($function_name,
				$attributes); # Value of $attributes is required.
			$node->setAttribute('comments', $comment);
		}
		#fwrite(STDERR, "Exit fixIndirectFuncName() node type {$node->getType()} at source line {$node->getLine()}\n");
	}

	// Substitute user-specified function name for the real function name
	// at the function declaration site.
	public function substituteFunctionName(PhpParser\Node\Stmt\Function_ $node) {
		if (isset($node->name)) {
			$lookup = strtolower($node->name);
			if (array_key_exists($lookup, $this->replacement_func_names)) {
				$node->name = $this->replacement_func_names[$lookup];
			}
		}
	}

	public function leaveNode(PhpParser\Node $node) {

		if ($this->remove_comments) {
			# Remove all comments - they're mostly deliberately misleading anyway.
			if ($node->hasAttribute('comments')) {
				$node->setAttribute('comments', array());
			}
		}

        if ($node instanceof PhpParser\Node\Expr\Assign) {
    		$this->performAssignment($node);
		} else
        if ($node instanceof PhpParser\Node\Expr\FuncCall) {
			# Here's where we replace indirect function calls
			# of the form $variable($a1, $a2..) with the value of
			# $variable, if we can.
			$this->fixIndirectFuncName($node);

			# Replace any decodeable arguments of select functions
			# with the statically-determinate, decoded values.
			# Happens more often than fixing an indirect function call.
			$this->replaceArguments($node);

			if (count($this->replaced_func_names) > 0)
				$this->replaceSelectArguments($node);

			// Replace select function names with user-defined
			// names.
			if (count($this->replacement_func_names) > 0)
				$this->replaceFunctionCallNames($node);

		} else
        if ($node instanceof PhpParser\Node\Expr\ArrayDimFetch) {
			#fwrite(STDERR, "Standalone manipulation of ArrayDimFetch\n");
			# Fix up $GLOBALS[] references
			if ($this->findName($node->var, $array_name)) {
				if ($array_name == 'GLOBALS') {
					if ($this->symtbl->globalSymbolValue($array_name, $real_name)) {
						# Replace $GLOBALS['something'] with value of $something
						if ($this->findValue($real_name, $real_value)) {
							$node->var = $this->newNodeAsType($real_value, TRUE);
						}
					}
				} 

				# See if we can't put a static value on the *dimension*
				# of $_REQUEST, $_SERVER, etc.
				# XXX - why is $node->dim null sometimes?
				if ($node->dim && $this->findValue($node->dim, $dim_value)) {
					$node->dim = $this->newNodeAsType($dim_value);
				}
			}
		} else
		# Handle a few "language constructs" - these end up in subclasses
		# of PhpParser\Node, and have enough pecularities to deal with
		# on a case-by-case basis.
		if ($node instanceof PhpParser\Node\Expr\Eval_) {
			# Put in statically-determined strings for the argument
			# of any eval() constructs.
			if ($this->findValue($node->expr, $eval_body)) {
				# XXX - this should actually look through the code that
				# makes up the eval's body, and substitute it for $node.
				$node->expr = new PhpParser\Node\Scalar\String_($eval_body);
			}
		} else
		if ($node instanceof PhpParser\Node\Stmt\Global_) {
			foreach ($node->vars as $variable) {
				$value = null;
				if ($this->symtbl->globalSymbolValue($variable->name, $value))
					$this->symtbl->addSymbol($variable->name, $value);
			}
		} else
		if ($node instanceof PhpParser\Node\Stmt\Echo_) {
			foreach ($node->exprs as $idx => $subnode) {
				$subnode_as_string = null;
				if ($this->findValue($subnode, $subnode_as_string)) {
					$as_name = $this->symtbl->isSymbol($subnode_as_string);
					$node->exprs[$idx] = $this->newNodeAsType($subnode_as_string, $as_name);
				}
			}
		} else
		if ($node instanceof PhpParser\Node\Expr\Print_) {
			if ($this->findValue($node->expr, $subnode_as_string)) {
				$as_name = $this->symtbl->isSymbol($subnode_as_string);
				$node->expr = $this->newNodeAsType($subnode_as_string, $as_name);
			} else
			if ($this->findName($node->expr, $subnode_as_string)) {
				$as_name = $this->symtbl->isSymbol($subnode_as_string);
				$node->expr = $this->newNodeAsType($subnode_as_string, $as_name);
			}
		} else
		if ($node instanceof PhpParser\Node\Expr\AssignOp\Concat) {
			if ($this->findName($node->var, $var_name)) {
				if ($this->findValue($node->expr, $exprvalue)) {
					if ($this->findValue($node->var, $varvalue)) {
						$concatenated_string = $varvalue . $exprvalue;
						$this->symtbl->addSymbol($var_name, $concatenated_string);
					}
					// Substitute RHS of ".=" into parse tree for better readability.
					$node->expr = $this->newNodeAsType($exprvalue);
				}
			}
		} else
		if ($node instanceof PhpParser\Node\Expr\BinaryOp\Equal
			|| $node instanceof PhpParser\Node\Expr\BinaryOp\NotEqual
		) {
			if ($this->findValue($node->left, $leftvalue)) {
				if (is_string($leftvalue))
					$node->left = $this->newNodeAsType($leftvalue, false);
			}
			if ($this->findValue($node->right, $rightvalue)) {
				if (is_string($rightvalue))
					$node->right = $this->newNodeAsType($rightvalue, false);
			}
		} else
		if ($node instanceof PhpParser\Node\Expr\BinaryOp\Concat) {
			# Roll up any substrings. This can be computationally inefficient.
			$left_is_string = false;
			if ($this->findValue($node->left, $leftvalue)) {
				if (is_string($leftvalue))
					$left_is_string = true;
			}
			if ($this->findValue($node->right, $rightvalue)) {
				if (is_string($rightvalue)) {
					if ($node->left instanceof PhpParser\Node\Expr\BinaryOp\Concat) {
						if ($this->findValue($node->left->right, $othervalue)) {
							if (is_string($othervalue)) {
								$node->right = $this->newNodeAsType($othervalue.$rightvalue);
								$node->left  = $node->left->left;
							}
						}
					} else {
						$node->right = $this->newNodeAsType($rightvalue, false);
					}
				}
			} else {
				if ($left_is_string)
					$node->left = $this->newNodeAsType($leftvalue, false);
			}
		} else
		if ($node instanceof PhpParser\Node\Stmt\Function_) {
			$this->substituteFunctionName($node);
			$this->symtbl->popScope();
		} else
		if ($node instanceof PhpParser\Node\Stmt\Class_) {
			$this->symtbl->popScope();
		} else
		if ($node instanceof PhpParser\Node\Stmt\ClassMethod) {
			$this->symtbl->popScope();
		} else
		if ($node instanceof PhpParser\Node\Expr\Variable) {
			$this->fixIndirectVarName($node);
		}
	}

	public function generateCallTrace() {
		$e = new Exception();
		$trace = explode("\n", $e->getTraceAsString());
		// reverse array to make steps line up chronologically
		$trace = array_reverse($trace);
		array_shift($trace); // remove {main}
		array_pop($trace); // remove call to this method
		$length = count($trace);
		$result = array();

		for ($i = 0; $i < $length; $i++) {
			$result[] = ($i + 1)  . ')'
				. substr($trace[$i], strpos($trace[$i], ' ')); // replace '#someNum' with '$i)', set the right ordering
		}

		fwrite(STDERR, "\t" . implode("\n\t", $result));
	}
}


class FuncPHPNodeVisitor extends PhpParser\NodeVisitorAbstract
{
	public $function_names;
	public function __construct() {
		$this->function_names = Array();
	}

	public function enterNode(PhpParser\Node $node) {
		if ($node instanceof PhpParser\Node\Stmt\Function_) {
			$this->function_names[] = $node->name;
		}
	}
}
