// Debugging support for Bro policy files.

#include "config.h"

#include <stdio.h>
#include <stdarg.h>
#include <signal.h>
#include <ctype.h>

#include <string>
using namespace std;

#ifdef HAVE_READLINE
#include <readline/readline.h>
#endif

#include "util.h"
#include "Debug.h"
#include "DebugCmds.h"
#include "DbgBreakpoint.h"
#include "Stmt.h"
#include "Func.h"
#include "Scope.h"
#include "PolicyFile.h"

bool g_policy_debug = false;
DebuggerState g_debugger_state;
TraceState g_trace_state;
PDict(Filemap) g_dbgfilemaps;

// These variables are used only to decide whether or not to print the
// current context; you don't want to do it after a step or next
// command unless you've exited a function.
static bool step_or_next_pending = false;
static Frame* last_frame;

DebuggerState::DebuggerState()
	{
	next_bp_id = next_watch_id = next_display_id = 1;
	BreakBeforeNextStmt(false);
	curr_frame_idx = 0;
	BreakFromSignal(false);

	// ### Don't choose this arbitrary size! Extend Frame.
	dbg_locals = new Frame(1024, /* func = */ 0, /* fn_args = */ 0);
	}

DebuggerState::~DebuggerState()
	{
	Unref(dbg_locals);
	}

bool StmtLocMapping::StartsAfter(const StmtLocMapping* m2)
	{
	if ( ! m2  )
		internal_error("Assertion failed: %s", "m2 != 0");

	return loc.first_line > m2->loc.first_line ||
		(loc.first_line == m2->loc.first_line &&
		 loc.first_column > m2->loc.first_column);
	}


// Generic debug message output.
int debug_msg(const char* fmt, ...)
	{
	va_list args;
	int retval;

	va_start(args, fmt);
	retval = vfprintf(stderr, fmt, args);
	va_end(args);

	return retval;
	}


// Trace message output

FILE* TraceState::SetTraceFile(const char* filename)
	{
	FILE* newfile;

	if ( streq(filename, "-") )
		newfile = stderr;
	else
		newfile = fopen(filename, "w");

	FILE* oldfile = trace_file;
	if ( newfile )
		{
		trace_file = newfile;
		}
	else
		{
		fprintf(stderr, "Unable to open trace file %s\n", filename);
		trace_file = 0;
		}

	return oldfile;
	}

void TraceState::TraceOn()
	{
	fprintf(stderr, "Execution tracing ON.\n");
	dbgtrace = true;
	}

void TraceState::TraceOff()
	{
	fprintf(stderr, "Execution tracing OFF.\n");
	dbgtrace = false;
	}

int TraceState::LogTrace(const char* fmt, ...)
	{
	va_list args;
	int retval;

	va_start(args, fmt);

	// Prefix includes timestamp and file/line info.
	fprintf(trace_file, "%.6f ", network_time);

	const Stmt* stmt;
	Location loc;
	loc.filename = 0;

	if ( g_frame_stack.back() )
		{
		stmt = g_frame_stack.back()->GetNextStmt();
		if ( stmt )
			loc = *stmt->GetLocationInfo();
		else
			{
			const BroFunc* f = g_frame_stack.back()->GetFunction();
			if ( f )
				loc = *f->GetLocationInfo();
			}
		}

	if ( ! loc.filename )
		{
		loc.filename = "<no filename>";
		loc.last_line = 0;
		}

	fprintf(trace_file, "%s:%d", loc.filename, loc.last_line);

	// Each stack frame is indented.
	for ( int i = 0; i < int(g_frame_stack.size()); ++i )
		fprintf(trace_file, "\t");

	retval = vfprintf(trace_file, fmt, args);

	fflush(trace_file);
	va_end(args);

	return retval;
	}


// Helper functions.
void get_first_statement(Stmt* list, Stmt*& first, Location& loc)
	{
	if ( ! list )
		{
		first = 0;
		return;
		}

	first = list;
	while ( first->Tag() == STMT_LIST )
		{
		if ( first->AsStmtList()->Stmts()[0] )
			first = first->AsStmtList()->Stmts()[0];
		else
			break;
		}

	loc = *first->GetLocationInfo();
	}

static void parse_function_name(vector<ParseLocationRec>& result,
				ParseLocationRec& plr, const string& s)
	{ // function name
	ID* id = lookup_ID(s.c_str(), current_module.c_str());
	if ( ! id )
		{
		string fullname = make_full_var_name(current_module.c_str(), s.c_str());
		debug_msg("Function %s not defined.\n", fullname.c_str());
		plr.type = plrUnknown;
		return;
		}

	FuncType* ftype;
	if ( ! (ftype = id->Type()->AsFuncType()) )
		{
		debug_msg("Function %s not declared.\n", id->Name());
		plr.type = plrUnknown;
		return;
		}

	if ( ! id->HasVal() )
		{
		debug_msg("Function %s declared but not defined.\n", id->Name());
		plr.type = plrUnknown;
		return;
		}

	Func* func = id->ID_Val()->AsFunc();
	vector<Stmt*>& bodies = func->GetBodies();

	if ( bodies.size() == 0 )
		{
		debug_msg("Function %s is a built-in function\n", id->Name());
		plr.type = plrUnknown;
		return;
		}

	Stmt* body = 0;	// the particular body we care about; 0 = all

	if ( bodies.size() == 1 )
		body = bodies[0];
	else
		{
		while ( 1 )
			{
			debug_msg("There are multiple definitions of that event handler.\n"
				 "Please choose one of the following options:\n");
			for ( unsigned int i = 0; i < bodies.size(); ++i )
				{
				Stmt* first;
				Location stmt_loc;
				get_first_statement(bodies[i], first, stmt_loc);
				debug_msg("[%d] %s:%d\n", i+1, stmt_loc.filename, stmt_loc.first_line);
				}

			debug_msg("[a] All of the above\n");
			debug_msg("[n] None of the above\n");
			debug_msg("Enter your choice: ");

			char charinput[256];
			if ( ! fgets(charinput, sizeof(charinput) - 1, stdin) )
				{
				plr.type = plrUnknown;
				return;
				}

			if ( charinput[strlen(charinput) - 1] == '\n' )
				charinput[strlen(charinput) - 1] = 0;

			string input = charinput;

			if ( input == "a" )
				break;

			if ( input == "n" )
				{
				plr.type = plrUnknown;
				return;
				}

			int option = atoi(input.c_str());
			if ( option > 0 && option <= (int) bodies.size() )
				{
				body = bodies[option - 1];
				break;
				}
			}
		}

	plr.type = plrFunction;

	// Find first atomic (non-STMT_LIST) statement
	Stmt* first;
	Location stmt_loc;

	if ( body )
		{
		get_first_statement(body, first, stmt_loc);
		if ( first )
			{
			plr.stmt = first;
			plr.filename = stmt_loc.filename;
			plr.line = stmt_loc.last_line;
			}
		}

	else
		{
		result.pop_back();
		ParseLocationRec plr;

		for ( unsigned int i = 0; i < bodies.size(); ++i )
			{
			get_first_statement(bodies[i], first, stmt_loc);
			if ( ! first )
				continue;

			plr.type = plrFunction;
			plr.stmt = first;
			plr.filename = stmt_loc.filename;
			plr.line = stmt_loc.last_line;
			result.push_back(plr);
			}
		}
	}

vector<ParseLocationRec> parse_location_string(const string& s)
	{
	vector<ParseLocationRec> result;
	result.push_back(ParseLocationRec());
	ParseLocationRec& plr = result[0];
	const char* full_filename = 0;

	// If plrFileAndLine, set this to the filename you want; for
	// memory management reasons, the real filename is set when looking
	// up the line number to find the corresponding statement.
	const char* loc_filename = 0;

	if ( sscanf(s.c_str(), "%d", &plr.line) )
		{ // just a line number (implicitly referring to the current file)
		loc_filename = g_debugger_state.last_loc.filename;
		plr.type = plrFileAndLine;
		}

	else
		{
		string::size_type pos_colon = s.find(':');
		string::size_type pos_dblcolon = s.find("::");

		if ( pos_colon == string::npos || pos_dblcolon != string::npos )
			parse_function_name(result, plr, s);
		else
			{ // file:line
			string filename = s.substr(0, pos_colon);
			string line_string = s.substr(pos_colon + 1, s.length() - pos_colon);

			if ( ! sscanf(line_string.c_str(), "%d", &plr.line) )
				plr.type = plrUnknown;

			FILE* throwaway = search_for_file(filename.c_str(), "bro",
								&full_filename);
			if ( ! throwaway )
				{
				debug_msg("No such policy file: %s.\n", filename.c_str());
				plr.type = plrUnknown;
				return result;
				}

			fclose(throwaway);

			loc_filename = full_filename;
			plr.type = plrFileAndLine;
			}
		}

	if ( plr.type == plrFileAndLine )
		{
		Filemap* map = g_dbgfilemaps.Lookup(loc_filename);
		if ( ! map )
			internal_error("Policy file %s should have been loaded\n",
					loc_filename);

		if ( plr.line > how_many_lines_in(loc_filename) )
			{
			debug_msg("No line %d in %s.\n", plr.line, loc_filename);
			delete [] full_filename;
			plr.type = plrUnknown;
			return result;
			}

		StmtLocMapping* hit = 0;
		loop_over_queue(*map, i)
			{
			StmtLocMapping* entry = (*map)[i];
			plr.filename = (*map)[i]->Loc().filename;

			if ( entry->Loc().first_line > plr.line )
				break;

			if ( plr.line >= entry->Loc().first_line &&
			     plr.line <= entry->Loc().last_line )
				{
				hit = (*map)[i];
				break;
				}
			}

		if ( hit )
			plr.stmt = hit->Statement();
		else
			plr.stmt = 0;
		}

	delete [] full_filename;
	return result;
	}


// Interactive debugging console.

static int dbg_dispatch_cmd(DebugCmd cmd_code, const vector<string>& args);

#ifdef HAVE_READLINE

void using_history(void);

static bool init_readline()
	{
	// ### Set up custom completion.

	rl_outstream = stderr;
	using_history();

	return false;
	}

#endif

void break_signal(int)
	{
	g_debugger_state.BreakBeforeNextStmt(true);
	g_debugger_state.BreakFromSignal(true);
	}

int dbg_init_debugger(const char* cmdfile)
	{
	if ( ! g_policy_debug )
		return 0;	// probably shouldn't have been called

	init_global_dbg_constants();

	// Hit the debugger before running anything.
	g_debugger_state.BreakBeforeNextStmt(true);

	if ( cmdfile )
		// ### Implement this
		debug_msg("Command files not supported. Using interactive mode.\n");

	// ### if ( interactive ) (i.e., not reading cmds from a file)
#ifdef HAVE_READLINE
	init_readline();
#endif

	signal(SIGINT, &break_signal);
	signal(SIGTERM, break_signal);

	return 1;
	}

int dbg_shutdown_debugger()
	{
	// ### TODO: Remove signal handlers
	return 1;
	}


/*
 * Umesh: I stole this code from libedit; I modified it here to use
 * <string>s to avoid memory management problems. The main command is returned
 * by the operation argument; the additional arguments are put in the
 * supplied vector.
 *
 * Parse the string into individual tokens, similarily to how shell
 * would do it.
 */

void tokenize(const char* cstr, string& operation, vector<string>& arguments)
	{
	int num_tokens = 0;
	char delim = '\0';
	const string str(cstr);

	for ( int i = 0; i < int(str.length()); ++i )
		{
		while ( isspace((unsigned char) str[i]) )
			++i;

		int start = i;

		for ( ; str[i]; ++i )
			{
			if ( str[i] == '\\' )
				{
				if ( i < int(str.length()) )
					++i;
				}

			else if ( ! delim && (str[i] == '\'' || str[i] == '"') )
				delim = str[i];

			else if ( delim && str[i] == delim )
				{
				delim = '\0';
				++i;
				break;
				}

			else if ( ! delim && isspace(str[i]) )
				break;
			}

		size_t len = i - start;

		if ( ! num_tokens )
			operation = string(str, start, len);
		else
			arguments.push_back(string(str, start, len));

		++num_tokens;
		}
	}


// Given a command string, parse it and send the command to be dispatched.
int dbg_execute_command(const char* cmd)
	{
	bool matched_history = false;

	if ( ! cmd )
		return 0;

	if ( streq(cmd, "") ) // do the GDB command completion
		{
#ifdef HAVE_READLINE
		int i;
		for ( i = history_length; i >= 1; --i )
			{
			HIST_ENTRY* entry = history_get(i);
			if ( ! entry )
				return 0;

			const DebugCmdInfo* info =
				(const DebugCmdInfo*) entry->data;

			if ( info && info->Repeatable() )
				{
				cmd = entry->line;
				matched_history = true;
				break;
				}
			}
#endif

		if ( ! matched_history )
			return 0;
		}

	char* localcmd = new char [strlen(cmd) + sizeof(char)];
	strcpy(localcmd, cmd);

	string opstring;
	vector<string> arguments;
	tokenize(localcmd, opstring, arguments);

	delete [] localcmd;

	// Make sure we know this op name.
	const char* matching_cmds[num_debug_cmds()];
	int num_matches = find_all_matching_cmds(opstring, matching_cmds);

	if ( ! num_matches )
		{
		debug_msg("No Matching command for '%s'.\n", opstring.c_str());
		return 0;
		}

	if ( num_matches > 1 )
		{
		debug_msg("Ambiguous command; could be\n");

		for ( int i = 0; i < num_debug_cmds(); ++i )
			if ( matching_cmds[i] )
				debug_msg("\t%s\n", matching_cmds[i]);

		return 0;
		}

	// Matched exactly one command: find out which one.
	DebugCmd cmd_code = dcInvalid;
	for ( int i = 0; i < num_debug_cmds(); ++i )
		if ( matching_cmds[i] )
			{
			cmd_code = (DebugCmd) i;
			break;
			}

#ifdef HAVE_READLINE
	// Insert command into history.
	if ( ! matched_history && cmd && *cmd )
		add_history(cmd, (void *) get_debug_cmd_info(cmd_code));
#endif

	if ( int(cmd_code) >= num_debug_cmds() )
		internal_error("Assertion failed: %s", "int(cmd_code) < num_debug_cmds()");

	// Dispatch to the op-specific handler (with args).
	int retcode = dbg_dispatch_cmd(cmd_code, arguments);
	if ( retcode < 0 )
		return retcode;

	const DebugCmdInfo* info = get_debug_cmd_info(cmd_code);
	if ( ! info  )
		internal_error("Assertion failed: %s", "info");

	if ( ! info )
		return -2;	// ### yuck, why -2?

	return info->ResumeExecution();
	}

// Call the appropriate function for the command.
static int dbg_dispatch_cmd(DebugCmd cmd_code, const vector<string>& args)
	{
	switch ( cmd_code ) {
	case dcHelp:
		dbg_cmd_help(cmd_code, args);
		break;

	case dcQuit:
		debug_msg("Program Terminating\n");
		exit(0);

	case dcNext:
		g_frame_stack.back()->BreakBeforeNextStmt(true);
		step_or_next_pending = true;
		last_frame = g_frame_stack.back();
		break;

	case dcStep:
		g_debugger_state.BreakBeforeNextStmt(true);
		step_or_next_pending = true;
		last_frame = g_frame_stack.back();
		break;

	case dcContinue:
		g_debugger_state.BreakBeforeNextStmt(false);
		debug_msg("Continuing.\n");
		break;

	case dcFinish:
		g_frame_stack.back()->BreakOnReturn(true);
		g_debugger_state.BreakBeforeNextStmt(false);
		break;

	case dcBreak:
		dbg_cmd_break(cmd_code, args);
		break;

	case dcBreakCondition:
		dbg_cmd_break_condition(cmd_code, args);
		break;

	case dcDeleteBreak:
	case dcClearBreak:
	case dcDisableBreak:
	case dcEnableBreak:
	case dcIgnoreBreak:
		dbg_cmd_break_set_state(cmd_code, args);
		break;

	case dcPrint:
		dbg_cmd_print(cmd_code, args);
		break;

	case dcBacktrace:
		return dbg_cmd_backtrace(cmd_code, args);

	case dcFrame:
	case dcUp:
	case dcDown:
		return dbg_cmd_frame(cmd_code, args);

	case dcInfo:
		return dbg_cmd_info(cmd_code, args);

	case dcList:
		return dbg_cmd_list(cmd_code, args);

	case dcDisplay:
	case dcUndisplay:
		debug_msg("Command not yet implemented.\n");
		break;

	case dcTrace:
		return dbg_cmd_trace(cmd_code, args);

	default:
		debug_msg("INTERNAL ERROR: "
		"Got an unknown debugger command in DbgDispatchCmd: %d\n",
		cmd_code);
		return 0;
	}

	return 0;
	}

static char* get_prompt(bool reset_counter = false)
	{
	static char prompt[512] = "";
	static int counter = 0;

	prompt[255] = '\0';

	if ( reset_counter )
		counter = 0;

	snprintf(prompt, sizeof(prompt) - 1, "(Bro [%d]) ", counter++);

	return prompt;
	}

string get_context_description(const Stmt* stmt, const Frame* frame)
	{
	char buf[1024];
	ODesc d;
	const BroFunc* func = frame->GetFunction();

	if ( func )
		func->DescribeDebug(&d, frame->GetFuncArgs());
	else
		d.Add("<unknown function>", 0);

	Location loc;
	if ( stmt )
		loc = *stmt->GetLocationInfo();
	else
		{
		loc.filename = "<no filename>";
		loc.last_line = 0;
		}

	snprintf(buf, sizeof(buf) - 1, "In %s at %s:%d",
		 d.Description(), loc.filename, loc.last_line);

	return string(buf);
	}

int dbg_handle_debug_input()
	{
	static char* input_line = 0;
	int status = 0;

	if ( g_debugger_state.BreakFromSignal() )
		{
		debug_msg("Program received signal SIGINT: entering debugger\n");

		g_debugger_state.BreakFromSignal(false);
		}

	Frame* curr_frame = g_frame_stack.back();
	const BroFunc* func = curr_frame->GetFunction();
	if ( func )
		current_module = func->GetID()->ModuleName();
	else
		current_module = GLOBAL_MODULE_NAME;

	const Stmt* stmt = curr_frame->GetNextStmt();
	if ( ! stmt )
		internal_error("Assertion failed: %s", "stmt != 0");

	const Location loc = *stmt->GetLocationInfo();

	if ( ! step_or_next_pending || g_frame_stack.back() != last_frame )
		{
		string context =
			get_context_description(stmt, g_frame_stack.back());
		debug_msg("%s\n", context.c_str());
		}

	step_or_next_pending = false;

	PrintLines(loc.filename, loc.first_line,
			loc.last_line - loc.first_line + 1, true);
	g_debugger_state.last_loc = loc;

	do
		{
		// readline returns a pointer to a buffer it allocates; it's
		// freed at the bottom.
#ifdef HAVE_READLINE
		input_line = readline(get_prompt());
#else
		printf ("%s", get_prompt());
		input_line = (char *) safe_malloc(1024);
		input_line[1023] = 0;
		// ### Maybe it's not always stdin.
		fgets(input_line, sizeof(input_line) - 1, stdin);
#endif

		// ### Maybe not stdin; maybe do better cleanup.
		if ( feof(stdin) )
			exit(0);

		status = dbg_execute_command(input_line);

		if ( input_line )
			{
			free(input_line);
			input_line = 0;
			}
		else
			exit(0);
		}
	while ( status == 0 );

	// Clear out some state. ### Is there a better place?
	g_debugger_state.curr_frame_idx = 0;
	g_debugger_state.already_did_list = false;

	signal(SIGINT, &break_signal);
	signal(SIGTERM, &break_signal);

	return 0;
	}


// Return true to continue execution, false to abort.
bool pre_execute_stmt(Stmt* stmt, Frame* f)
	{
	if ( ! g_policy_debug ||
	     stmt->Tag() == STMT_LIST || stmt->Tag() == STMT_NULL )
		return true;

	if ( g_trace_state.DoTrace() )
		{
		ODesc d;
		stmt->Describe(&d);

		const char* desc = d.Description();
		const char* s = strchr(desc, '\n');

		int len;
		if ( s )
			len = s - desc;
		else
			len = strlen(desc);

		g_trace_state.LogTrace("%*s\n", len, desc);
		}

	bool should_break = false;

	if ( g_debugger_state.BreakBeforeNextStmt() ||
	     f->BreakBeforeNextStmt() )
		{
		if ( g_debugger_state.BreakBeforeNextStmt() )
			g_debugger_state.BreakBeforeNextStmt(false);

		if ( f->BreakBeforeNextStmt() )
			f->BreakBeforeNextStmt(false);

		should_break = true;
		}

	if ( stmt->BPCount() )
		{
		pair<BPMapType::iterator, BPMapType::iterator> p;

		p = g_debugger_state.breakpoint_map.equal_range(stmt);

		if ( p.first == p.second )
			internal_error("Breakpoint count nonzero, but no matching breakpoints");

		for ( BPMapType::iterator i = p.first; i != p.second; ++i )
			{
			int break_code = i->second->ShouldBreak(stmt);
			if ( break_code == 2 )	// ### 2?
				{
				i->second->SetEnable(false);
				delete i->second;
				}

			should_break = should_break || break_code;
			}
		}

	if ( should_break )
		dbg_handle_debug_input();

	return true;
	}

bool post_execute_stmt(Stmt* stmt, Frame* f, Val* result, stmt_flow_type* flow)
	{
	// Handle the case where someone issues a "next" debugger command,
	// but we're at a return statement, so the next statement is in
	// some other function.
	if ( *flow == FLOW_RETURN && f->BreakBeforeNextStmt() )
		g_debugger_state.BreakBeforeNextStmt(true);

	// Handle "finish" commands.
	if ( *flow == FLOW_RETURN && f->BreakOnReturn() )
		{
		if ( result )
			{
			ODesc d;
			result->Describe(&d);
			debug_msg("Return Value: '%s'\n", d.Description());
			}
		else
			debug_msg("Return Value: <none>\n");

		g_debugger_state.BreakBeforeNextStmt(true);
		f->BreakOnReturn(false);
		}

	return true;
	}


// Evaluates the given expression in the context of the currently selected
// frame.  Returns the resulting value, or nil if none (or there was an error).
Expr* g_curr_debug_expr = 0;

// ### fix this hardwired access to external variables etc.
struct yy_buffer_state;
typedef struct yy_buffer_state* YY_BUFFER_STATE;
YY_BUFFER_STATE bro_scan_string(const char*);

extern YYLTYPE yylloc;	// holds start line and column of token
extern int line_number;
extern const char* filename;

Val* dbg_eval_expr(const char* expr)
	{
	// Push the current frame's associated scope.
	// Note: g_debugger_state.curr_frame_idx is the user-visible number,
	//       while the array index goes in the opposite direction
	int frame_idx =
		(g_frame_stack.size() - 1) - g_debugger_state.curr_frame_idx;

	if ( ! (frame_idx >= 0 && (unsigned) frame_idx < g_frame_stack.size())  )
		internal_error("Assertion failed: %s", "frame_idx >= 0 && (unsigned) frame_idx < g_frame_stack.size()");

	Frame* frame = g_frame_stack[frame_idx];
	if ( ! (frame)  )
		internal_error("Assertion failed: %s", "frame");

	const BroFunc* func = frame->GetFunction();
	if ( func )
		push_existing_scope(func->GetScope());

	// ### Possibly push a debugger-local scope?

	// Set up the lexer to read from the string.
	string parse_string = string("@DEBUG ") + expr;
	bro_scan_string(parse_string.c_str());

	// Fix filename and line number for the lexer/parser, which record it.
	filename = "<interactive>";
	line_number = 1;
	yylloc.filename = filename;
	yylloc.first_line = yylloc.last_line = line_number = 1;

	// Parse the thing into an expr.
	Val* result = 0;
	if ( yyparse() )
		{
		if ( g_curr_debug_expr )
			{
			delete g_curr_debug_expr;
			g_curr_debug_expr = 0;
			}
		}
	else
		result = g_curr_debug_expr->Eval(frame);

	if ( func )
		pop_scope();

	delete g_curr_debug_expr;
	g_curr_debug_expr = 0;

	return result;
	}
