// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to 
/* based on example code: https://github.com/sheredom/llvm_bc_parsing_example
 * which came under the above (un-)license.  does not depend on any FRR
 * pieces, so no reason to change the license.
 *
 * please note that while included in the FRR sources, this tool is in no way
 * supported or maintained by the FRR community.  it is provided as a
 * "convenience";  while it worked at some point (using LLVM 8 / 9), it may
 * easily break with a future LLVM version or any other factors.
 *
 * 2020-05-04, David Lamparter
 */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "frr-llvm-debuginfo.h"
/* if you want to use this without the special FRRouting defines,
 * remove the following #define
 */
#define FRR_SPECIFIC
static struct dbginfo *dbginfo;
static void dbgloc_add(struct json_object *jsobj, LLVMValueRef obj)
{
	unsigned file_len = 0;
	const char *file = LLVMGetDebugLocFilename(obj, &file_len);
	unsigned line = LLVMGetDebugLocLine(obj);
	if (!file)
		file = "???", file_len = 3;
	else if (file[0] == '.' && file[1] == '/')
		file += 2, file_len -= 2;
	json_object_object_add(jsobj, "filename",
			       json_object_new_string_len(file, file_len));
	json_object_object_add(jsobj, "line", json_object_new_int64(line));
}
static struct json_object *js_get_or_make(struct json_object *parent,
					  const char *key,
					  struct json_object *(*maker)(void))
{
	struct json_object *ret;
	ret = json_object_object_get(parent, key);
	if (ret)
		return ret;
	ret = maker();
	json_object_object_add(parent, key, ret);
	return ret;
}
static bool try_struct_fptr(struct json_object *js_call, LLVMValueRef gep,
			    const char *prefix)
{
	unsigned long long val = 0;
	bool ret = false;
	LLVMTypeRef ptrtype = LLVMTypeOf(LLVMGetOperand(gep, 0));
	LLVMValueRef idx;
	/* middle steps like struct a -> struct b a_member; -> fptr */
	for (int i = 1; ptrtype && i < LLVMGetNumOperands(gep) - 1; i++) {
		if (LLVMGetTypeKind(ptrtype) == LLVMPointerTypeKind
		    || LLVMGetTypeKind(ptrtype) == LLVMArrayTypeKind
		    || LLVMGetTypeKind(ptrtype) == LLVMVectorTypeKind) {
			ptrtype = LLVMGetElementType(ptrtype);
			continue;
		}
		if (LLVMGetTypeKind(ptrtype) != LLVMStructTypeKind)
			return false;
		idx = LLVMGetOperand(gep, i);
		if (!LLVMIsConstant(idx))
			return false;
		val = LLVMConstIntGetZExtValue(idx);
		unsigned n = LLVMGetNumContainedTypes(ptrtype);
		LLVMTypeRef arr[n];
		if (val > n)
			return false;
		LLVMGetSubtypes(ptrtype, arr);
		ptrtype = arr[val];
	}
	if (!ptrtype)
		return false;
	idx = LLVMGetOperand(gep, LLVMGetNumOperands(gep) - 1);
	if (!LLVMIsConstant(idx))
		return false;
	val = LLVMConstIntGetZExtValue(idx);
	char *sname = NULL, *mname = NULL;
	if (dbginfo_struct_member(dbginfo, ptrtype, val, &sname, &mname)) {
		fprintf(stderr, "%s: call to struct %s->%s\n", prefix, sname,
			mname);
		json_object_object_add(js_call, "type",
				       json_object_new_string("struct_memb"));
		json_object_object_add(js_call, "struct",
				       json_object_new_string(sname));
		json_object_object_add(js_call, "member",
				       json_object_new_string(mname));
		ret = true;
	}
	free(sname);
	free(mname);
	return ret;
}
static bool details_fptr_vars = false;
static bool details_fptr_consts = true;
enum called_fn {
	FN_GENERIC = 0,
	FN_NONAME,
	FN_INSTALL_ELEMENT,
	FN_THREAD_ADD,
};
static void walk_const_fptrs(struct json_object *js_call, LLVMValueRef value,
			     const char *prefix, bool *hdr_written)
{
	LLVMTypeRef type;
	LLVMValueKind kind;
	if (LLVMIsAGlobalVariable(value)) {
		type = LLVMGlobalGetValueType(value);
		value = LLVMGetInitializer(value);
	} else {
		type = LLVMTypeOf(value);
	}
	if (LLVMIsAFunction(value)) {
		struct json_object *js_fptrs;
		js_fptrs = js_get_or_make(js_call, "funcptrs",
					  json_object_new_array);
		size_t fn_len;
		const char *fn_name = LLVMGetValueName2(value, &fn_len);
		size_t curlen = json_object_array_length(js_fptrs);
		struct json_object *jsobj;
		const char *s;
		for (size_t i = 0; i < curlen; i++) {
			jsobj = json_object_array_get_idx(js_fptrs, i);
			s = json_object_get_string(jsobj);
			if (s && !strcmp(s, fn_name))
				return;
		}
		if (details_fptr_consts && !*hdr_written) {
			fprintf(stderr,
				"%s: calls function pointer from constant or global data\n",
				prefix);
			*hdr_written = true;
		}
		if (details_fptr_consts)
			fprintf(stderr, "%s-   constant: %.*s()\n",
				prefix, (int)fn_len, fn_name);
		json_object_array_add(js_fptrs,
				       json_object_new_string_len(fn_name,
								  fn_len));
		return;
	}
	kind = LLVMGetValueKind(value);
	unsigned len;
	char *dump;
	switch (kind) {
	case LLVMUndefValueValueKind:
	case LLVMConstantAggregateZeroValueKind:
	case LLVMConstantPointerNullValueKind:
		/* null pointer / array - ignore */
		break;
	case LLVMConstantIntValueKind:
		/* integer - ignore */
		break;
	case LLVMConstantStructValueKind:
		len = LLVMCountStructElementTypes(type);
		for (unsigned i = 0; i < len; i++)
			walk_const_fptrs(js_call, LLVMGetOperand(value, i),
					 prefix, hdr_written);
		break;
	case LLVMConstantArrayValueKind:
		len = LLVMGetArrayLength(type);
		for (unsigned i = 0; i < len; i++)
			walk_const_fptrs(js_call, LLVMGetOperand(value, i),
					 prefix, hdr_written);
		return;
	case LLVMConstantExprValueKind:
		switch (LLVMGetConstOpcode(value)) {
		case LLVMGetElementPtr:
			if (try_struct_fptr(js_call, value, prefix)) {
				*hdr_written = true;
				return;
			}
			fprintf(stderr,
				"%s: calls function pointer from unhandled const GEP\n",
				prefix);
			*hdr_written = true;
			/* fallthru */
		default:
			/* to help the user / development */
			if (!*hdr_written) {
				fprintf(stderr,
					"%s: calls function pointer from constexpr\n",
					prefix);
				*hdr_written = true;
			}
			dump = LLVMPrintValueToString(value);
			fprintf(stderr, "%s-   [opcode=%d] %s\n", prefix,
				LLVMGetConstOpcode(value), dump);
			LLVMDisposeMessage(dump);
		}
		return;
	default:
		/* to help the user / development */
		if (!*hdr_written) {
			fprintf(stderr,
				"%s: calls function pointer from constant or global data\n",
				prefix);
			*hdr_written = true;
		}
		dump = LLVMPrintValueToString(value);
		fprintf(stderr,
			"%s-   value could not be processed:\n"
			"%s-   [kind=%d] %s\n",
			prefix, prefix, kind, dump);
		LLVMDisposeMessage(dump);
		return;
	}
	return;
}
#ifdef FRR_SPECIFIC
static bool is_thread_sched(const char *name, size_t len)
{
#define thread_prefix "_"
	static const char *const names[] = {
		thread_prefix "thread_add_read_write",
		thread_prefix "thread_add_timer",
		thread_prefix "thread_add_timer_msec",
		thread_prefix "thread_add_timer_tv",
		thread_prefix "thread_add_event",
		thread_prefix "thread_execute",
	};
	size_t i;
	for (i = 0; i < sizeof(names) / sizeof(names[0]); i++) {
		if (strlen(names[i]) != len)
			continue;
		if (!memcmp(names[i], name, len))
			return true;
	}
	return false;
}
#endif
static bool _check_val(bool cond, const char *text, LLVMValueRef dumpval)
{
	if (cond)
		return true;
	char *dump = LLVMPrintValueToString(dumpval);
	fprintf(stderr, "check failed: %s\ndump:\n\t%s\n", text, dump);
	LLVMDisposeMessage(dump);
	return false;
}
#define check_val(cond, dump)                                                  \
	if (!_check_val(cond, #cond, dump))                                    \
		return;
static char *get_string(LLVMValueRef value)
{
	if (!LLVMIsAConstant(value))
		return strdup("!NOT-A-CONST");
	if (LLVMGetValueKind(value) == LLVMConstantExprValueKind
	    && LLVMGetConstOpcode(value) == LLVMGetElementPtr) {
		value = LLVMGetOperand(value, 0);
		if (!LLVMIsAConstant(value))
			return strdup("!NOT-A-CONST-2");
	}
	if (LLVMIsAGlobalVariable(value))
		value = LLVMGetInitializer(value);
	size_t len = 0;
	const char *sval = LLVMGetAsString(value, &len);
	return strndup(sval, len);
}
static void handle_yang_module(struct json_object *js_special,
			       LLVMValueRef yang_mod)
{
	check_val(LLVMIsAGlobalVariable(yang_mod), yang_mod);
	LLVMValueRef value;
	value = LLVMGetInitializer(yang_mod);
	LLVMValueKind kind = LLVMGetValueKind(value);
	check_val(kind == LLVMConstantStructValueKind, value);
	size_t var_len = 0;
	const char *var_name = LLVMGetValueName2(yang_mod, &var_len);
	char buf_name[var_len + 1];
	memcpy(buf_name, var_name, var_len);
	buf_name[var_len] = '\0';
	struct json_object *js_yang, *js_yangmod, *js_items;
	js_yang = js_get_or_make(js_special, "yang", json_object_new_object);
	js_yangmod = js_get_or_make(js_yang, buf_name, json_object_new_object);
	js_items = js_get_or_make(js_yangmod, "items", json_object_new_array);
	char *mod_name = get_string(LLVMGetOperand(value, 0));
	json_object_object_add(js_yangmod, "name",
			       json_object_new_string(mod_name));
	free(mod_name);
	value = LLVMGetOperand(value, 1);
	kind = LLVMGetValueKind(value);
	check_val(kind == LLVMConstantArrayValueKind, value);
	unsigned len = LLVMGetArrayLength(LLVMTypeOf(value));
	for (unsigned i = 0; i < len - 1; i++) {
		struct json_object *js_item, *js_cbs;
		LLVMValueRef item = LLVMGetOperand(value, i);
		char *xpath = get_string(LLVMGetOperand(item, 0));
		js_item = json_object_new_object();
		json_object_array_add(js_items, js_item);
		json_object_object_add(js_item, "xpath",
				       json_object_new_string(xpath));
		js_cbs = js_get_or_make(js_item, "cbs", json_object_new_object);
		free(xpath);
		LLVMValueRef cbs = LLVMGetOperand(item, 1);
		check_val(LLVMGetValueKind(cbs) == LLVMConstantStructValueKind,
			  value);
		LLVMTypeRef cbs_type = LLVMTypeOf(cbs);
		unsigned cblen = LLVMCountStructElementTypes(cbs_type);
		for (unsigned i = 0; i < cblen; i++) {
			LLVMValueRef cb = LLVMGetOperand(cbs, i);
			char *sname = NULL;
			char *mname = NULL;
			if (dbginfo_struct_member(dbginfo, cbs_type, i, &sname,
						  &mname)) {
				(void)0;
			}
			if (LLVMIsAFunction(cb)) {
				size_t fn_len;
				const char *fn_name;
				fn_name = LLVMGetValueName2(cb, &fn_len);
				json_object_object_add(
					js_cbs, mname,
					json_object_new_string_len(fn_name,
								   fn_len));
			}
			free(sname);
			free(mname);
		}
	}
}
static void handle_daemoninfo(struct json_object *js_special,
			      LLVMValueRef daemoninfo)
{
	check_val(LLVMIsAGlobalVariable(daemoninfo), daemoninfo);
	LLVMTypeRef type;
	LLVMValueRef value;
	unsigned len;
	type = LLVMGlobalGetValueType(daemoninfo);
	value = LLVMGetInitializer(daemoninfo);
	LLVMValueKind kind = LLVMGetValueKind(value);
	check_val(kind == LLVMConstantStructValueKind, value);
	int yang_idx = -1;
	len = LLVMCountStructElementTypes(type);
	LLVMTypeRef fieldtypes[len];
	LLVMGetSubtypes(type, fieldtypes);
	for (unsigned i = 0; i < len; i++) {
		LLVMTypeRef t = fieldtypes[i];
		if (LLVMGetTypeKind(t) != LLVMPointerTypeKind)
			continue;
		t = LLVMGetElementType(t);
		if (LLVMGetTypeKind(t) != LLVMPointerTypeKind)
			continue;
		t = LLVMGetElementType(t);
		if (LLVMGetTypeKind(t) != LLVMStructTypeKind)
			continue;
		const char *name = LLVMGetStructName(t);
		if (!strcmp(name, "struct.frr_yang_module_info"))
			yang_idx = i;
	}
	if (yang_idx == -1)
		return;
	LLVMValueRef yang_mods = LLVMGetOperand(value, yang_idx);
	LLVMValueRef yang_size = LLVMGetOperand(value, yang_idx + 1);
	check_val(LLVMIsConstant(yang_size), yang_size);
	unsigned long long ival = LLVMConstIntGetZExtValue(yang_size);
	check_val(LLVMGetValueKind(yang_mods) == LLVMConstantExprValueKind
			  && LLVMGetConstOpcode(yang_mods) == LLVMGetElementPtr,
		  yang_mods);
	yang_mods = LLVMGetOperand(yang_mods, 0);
	check_val(LLVMIsAGlobalVariable(yang_mods), yang_mods);
	yang_mods = LLVMGetInitializer(yang_mods);
	check_val(LLVMGetValueKind(yang_mods) == LLVMConstantArrayValueKind,
		  yang_mods);
	len = LLVMGetArrayLength(LLVMTypeOf(yang_mods));
	if (len != ival)
		fprintf(stderr, "length mismatch - %llu vs. %u\n", ival, len);
	for (unsigned i = 0; i < len; i++) {
		char *dump;
		LLVMValueRef item = LLVMGetOperand(yang_mods, i);
		LLVMValueKind kind = LLVMGetValueKind(item);
		check_val(kind == LLVMGlobalVariableValueKind
				  || kind == LLVMConstantExprValueKind,
			  item);
		if (kind == LLVMGlobalVariableValueKind)
			continue;
		LLVMOpcode opcode = LLVMGetConstOpcode(item);
		switch (opcode) {
		case LLVMBitCast:
			item = LLVMGetOperand(item, 0);
			handle_yang_module(js_special, item);
			break;
		default:
			dump = LLVMPrintValueToString(item);
			printf("[%u] = [opcode=%u] %s\n", i, opcode, dump);
			LLVMDisposeMessage(dump);
		}
	}
}
static void process_call(struct json_object *js_calls,
			 struct json_object *js_special,
			 LLVMValueRef instr,
			 LLVMValueRef function)
{
	struct json_object *js_call, *js_fptrs = NULL;
	LLVMValueRef called = LLVMGetCalledValue(instr);
	if (LLVMIsAInlineAsm(called))
		return;
	if (LLVMIsAConstantExpr(called)) {
		LLVMOpcode opcode = LLVMGetConstOpcode(called);
		if (opcode == LLVMBitCast) {
			LLVMValueRef op0 = LLVMGetOperand(called, 0);
			if (LLVMIsAFunction(op0))
				called = op0;
		}
	}
	size_t called_len = 0;
	const char *called_name = LLVMGetValueName2(called, &called_len);
	unsigned n_args = LLVMGetNumArgOperands(instr);
	bool is_external = LLVMIsDeclaration(called);
	js_call = json_object_new_object();
	json_object_array_add(js_calls, js_call);
	dbgloc_add(js_call, instr);
	json_object_object_add(js_call, "is_external",
			       json_object_new_boolean(is_external));
	if (!called_name || called_len == 0) {
		json_object_object_add(js_call, "type",
				       json_object_new_string("indirect"));
		LLVMValueRef last = called;
		size_t name_len = 0;
		const char *name_c = LLVMGetValueName2(function, &name_len);
#ifdef FRR_SPECIFIC
		/* information for FRR hooks is dumped for the registration
		 * in _hook_typecheck; we can safely ignore the funcptr here
		 */
		if (strncmp(name_c, "hook_call_", 10) == 0)
			return;
#endif
		unsigned file_len = 0;
		const char *file = LLVMGetDebugLocFilename(instr, &file_len);
		unsigned line = LLVMGetDebugLocLine(instr);
		char prefix[256];
		snprintf(prefix, sizeof(prefix), "%.*s:%d:%.*s()",
			 (int)file_len, file, line, (int)name_len, name_c);
		if (LLVMIsALoadInst(called)
		    && LLVMIsAGetElementPtrInst(LLVMGetOperand(called, 0))
		    && try_struct_fptr(js_call, LLVMGetOperand(called, 0),
				       prefix))
			goto out_struct_fptr;
		while (LLVMIsALoadInst(last) || LLVMIsAGetElementPtrInst(last))
			/* skipping over details for GEP here, but meh. */
			last = LLVMGetOperand(last, 0);
		if (LLVMIsAAllocaInst(last)) {
			/* "alloca" is just generically all variables on the
			 * stack, this does not refer to C alloca() calls
			 *
			 * looking at the control flow in the function can
			 * give better results here, it's just not implemented
			 * (yet?)
			 */
			fprintf(stderr,
				"%s: call to a function pointer variable\n",
				prefix);
			if (details_fptr_vars) {
				char *dump = LLVMPrintValueToString(called);
				printf("%s-   %s\n", prefix, dump);
				LLVMDisposeMessage(dump);
			}
			json_object_object_add(
				js_call, "type",
				json_object_new_string("stack_fptr"));
		} else if (LLVMIsACallInst(last)) {
			/* calling the a function pointer returned from
			 * another function.
			 */
			struct json_object *js_indirect;
			js_indirect = js_get_or_make(js_call, "return_of",
						     json_object_new_array);
			process_call(js_indirect, js_special, last, function);
		} else if (LLVMIsAConstant(last)) {
			/* function pointer is a constant (includes loading
			 * from complicated constants like structs or arrays.)
			 */
			bool hdr_written = false;
			walk_const_fptrs(js_call, last, prefix, &hdr_written);
			if (details_fptr_consts && !hdr_written)
				fprintf(stderr,
					"%s: calls function pointer from constant or global data, but no non-NULL function pointers found\n",
					prefix);
		} else {
			char *dump = LLVMPrintValueToString(called);
			fprintf(stderr, "%s: ??? %s\n", prefix, dump);
			LLVMDisposeMessage(dump);
		}
#ifdef FRR_SPECIFIC
	} else if (!strcmp(called_name, "_install_element")) {
		LLVMValueRef param0 = LLVMGetOperand(instr, 0);
		if (!LLVMIsAConstantInt(param0))
			goto out_nonconst;
		long long vty_node = LLVMConstIntGetSExtValue(param0);
		json_object_object_add(js_call, "vty_node",
				       json_object_new_int64(vty_node));
		LLVMValueRef param1 = LLVMGetOperand(instr, 1);
		if (!LLVMIsAGlobalVariable(param1))
			goto out_nonconst;
		LLVMValueRef intlz = LLVMGetInitializer(param1);
		assert(intlz && LLVMIsConstant(intlz));
		LLVMValueKind intlzkind = LLVMGetValueKind(intlz);
		assert(intlzkind == LLVMConstantStructValueKind);
		LLVMValueRef funcptr = LLVMGetOperand(intlz, 4);
		assert(LLVMIsAFunction(funcptr));
		size_t target_len = 0;
		const char *target;
		target = LLVMGetValueName2(funcptr, &target_len);
		json_object_object_add(
			js_call, "type",
			json_object_new_string("install_element"));
		json_object_object_add(
			js_call, "target",
			json_object_new_string_len(target, target_len));
		return;
	out_nonconst:
		json_object_object_add(
			js_call, "target",
			json_object_new_string("install_element"));
		return;
	} else if (is_thread_sched(called_name, called_len)) {
		json_object_object_add(js_call, "type",
				       json_object_new_string("thread_sched"));
		json_object_object_add(
			js_call, "subtype",
			json_object_new_string_len(called_name, called_len));
		LLVMValueRef fparam;
		fparam = LLVMGetOperand(instr, 2);
		assert(fparam);
		size_t target_len = 0;
		const char *target;
		target = LLVMGetValueName2(fparam, &target_len);
		json_object_object_add(js_call, "target",
				       !target_len ? NULL :
			       json_object_new_string_len(target, target_len));
		if (!LLVMIsAFunction(fparam))
			json_object_object_add(js_call, "target_unresolved",
					       json_object_new_boolean(true));
		return;
	} else if (!strncmp(called_name, "_hook_typecheck_",
			    strlen("_hook_typecheck_"))) {
		struct json_object *js_hook, *js_this;
		const char *hook_name;
		hook_name = called_name + strlen("_hook_typecheck_");
		json_object_object_add(js_call, "type",
				       json_object_new_string("hook"));
		LLVMValueRef param0 = LLVMGetOperand(instr, 0);
		if (!LLVMIsAFunction(param0))
			return;
		size_t target_len = 0;
		const char *target;
		target = LLVMGetValueName2(param0, &target_len);
		js_hook = js_get_or_make(js_special, "hooks",
					 json_object_new_object);
		js_hook = js_get_or_make(js_hook, hook_name,
					 json_object_new_array);
		js_this = json_object_new_object();
		json_object_array_add(js_hook, js_this);
		dbgloc_add(js_this, instr);
		json_object_object_add(
			js_this, "target",
			json_object_new_string_len(target, target_len));
		return;
		/* TODO (FRR specifics):
		 * - workqueues - not sure we can do much there
		 * - zclient->* ?
		 */
#endif /* FRR_SPECIFIC */
	} else if (!strcmp(called_name, "frr_preinit")) {
		LLVMValueRef daemoninfo = LLVMGetOperand(instr, 0);
		handle_daemoninfo(js_special, daemoninfo);
		json_object_object_add(
			js_call, "target",
			json_object_new_string_len(called_name, called_len));
	} else {
		json_object_object_add(
			js_call, "target",
			json_object_new_string_len(called_name, called_len));
	}
out_struct_fptr:
	for (unsigned argno = 0; argno < n_args; argno++) {
		LLVMValueRef param = LLVMGetOperand(instr, argno);
		size_t target_len;
		const char *target_name;
		if (LLVMIsAFunction(param)) {
			js_fptrs = js_get_or_make(js_call, "funcptrs",
						  json_object_new_array);
			target_name = LLVMGetValueName2(param, &target_len);
			json_object_array_add(js_fptrs,
					      json_object_new_string_len(
						      target_name, target_len));
		}
	}
}
static void process_fn(struct json_object *funcs,
		       struct json_object *js_special,
		       LLVMValueRef function)
{
	struct json_object *js_func, *js_calls;
	size_t name_len = 0;
	const char *name_c = LLVMGetValueName2(function, &name_len);
	char *name;
	name = strndup(name_c, name_len);
	js_func = json_object_object_get(funcs, name);
	if (js_func) {
		unsigned file_len = 0;
		const char *file = LLVMGetDebugLocFilename(function, &file_len);
		unsigned line = LLVMGetDebugLocLine(function);
		fprintf(stderr, "%.*s:%d:%s(): duplicate definition!\n",
			(int)file_len, file, line, name);
		free(name);
		return;
	}
	js_func = json_object_new_object();
	json_object_object_add(funcs, name, js_func);
	free(name);
	js_calls = json_object_new_array();
	json_object_object_add(js_func, "calls", js_calls);
	dbgloc_add(js_func, function);
	for (LLVMBasicBlockRef basicBlock = LLVMGetFirstBasicBlock(function);
	     basicBlock; basicBlock = LLVMGetNextBasicBlock(basicBlock)) {
		for (LLVMValueRef instr = LLVMGetFirstInstruction(basicBlock);
		     instr; instr = LLVMGetNextInstruction(instr)) {
			if (LLVMIsAIntrinsicInst(instr))
				continue;
			if (LLVMIsACallInst(instr) || LLVMIsAInvokeInst(instr))
				process_call(js_calls, js_special, instr,
					     function);
		}
	}
}
static void help(int retcode)
{
	fprintf(stderr,
		"FRR LLVM bitcode to callgraph analyzer\n"
		"\n"
		"usage:  frr-llvm-cg [-q|-v] [-o ] BITCODEINPUT\n"
		"\n"
		"\t-o FILENAME\twrite JSON output to file instead of stdout\n"
		"\t-v\t\tbe more verbose\n"
		"\t-q\t\tbe quiet\n"
		"\n"
		"BITCODEINPUT must be a LLVM binary bitcode file (not text\n"
		"representation.)  Use - to read from stdin.\n"
		"\n"
		"Note it may be necessary to build this binary tool against\n"
		"the specific LLVM version that created the bitcode file.\n");
	exit(retcode);
}
int main(int argc, char **argv)
{
	int opt;
	const char *out = NULL;
	const char *inp = NULL;
	char v_or_q = '\0';
	while ((opt = getopt(argc, argv, "hvqo:")) != -1) {
		switch (opt) {
		case 'o':
			if (out)
				help(1);
			out = optarg;
			break;
		case 'v':
			if (v_or_q && v_or_q != 'v')
				help(1);
			details_fptr_vars = true;
			details_fptr_consts = true;
			v_or_q = 'v';
			break;
		case 'q':
			if (v_or_q && v_or_q != 'q')
				help(1);
			details_fptr_vars = false;
			details_fptr_consts = false;
			v_or_q = 'q';
			break;
		case 'h':
			help(0);
			return 0;
		default:
			help(1);
		}
	}
	if (optind != argc - 1)
		help(1);
	inp = argv[optind];
	LLVMMemoryBufferRef memoryBuffer;
	char *message;
	int ret;
	// check if we are to read our input file from stdin
	if (!strcmp(inp, "-")) {
		inp = "";
		ret = LLVMCreateMemoryBufferWithSTDIN(&memoryBuffer, &message);
	} else {
		ret = LLVMCreateMemoryBufferWithContentsOfFile(
			inp, &memoryBuffer, &message);
	}
	if (ret) {
		fprintf(stderr, "failed to open %s: %s\n", inp, message);
		free(message);
		return 1;
	}
	// now create our module using the memorybuffer
	LLVMModuleRef module;
	if (LLVMParseBitcode2(memoryBuffer, &module)) {
		fprintf(stderr, "%s: invalid bitcode\n", inp);
		LLVMDisposeMemoryBuffer(memoryBuffer);
		return 1;
	}
	// done with the memory buffer now, so dispose of it
	LLVMDisposeMemoryBuffer(memoryBuffer);
	dbginfo = dbginfo_load(module);
	struct json_object *js_root, *js_funcs, *js_special;
	js_root = json_object_new_object();
	js_funcs = json_object_new_object();
	json_object_object_add(js_root, "functions", js_funcs);
	js_special = json_object_new_object();
	json_object_object_add(js_root, "special", js_special);
	// loop through all the functions in the module
	for (LLVMValueRef function = LLVMGetFirstFunction(module); function;
	     function = LLVMGetNextFunction(function)) {
		if (LLVMIsDeclaration(function))
			continue;
		process_fn(js_funcs, js_special, function);
	}
	if (out) {
		char tmpout[strlen(out) + 5];
		snprintf(tmpout, sizeof(tmpout), "%s.tmp", out);
		ret = json_object_to_file_ext(tmpout, js_root,
					JSON_C_TO_STRING_PRETTY |
					JSON_C_TO_STRING_PRETTY_TAB |
					JSON_C_TO_STRING_NOSLASHESCAPE);
		if (ret < 0) {
			fprintf(stderr, "could not write JSON to file\n");
			return 1;
		}
		if (rename(tmpout, out)) {
			fprintf(stderr, "could not rename JSON output: %s\n",
				strerror(errno));
			unlink(tmpout);
			return 1;
		}
	} else {
		ret = json_object_to_fd(1, js_root,
					JSON_C_TO_STRING_PRETTY |
					JSON_C_TO_STRING_PRETTY_TAB |
					JSON_C_TO_STRING_NOSLASHESCAPE);
		if (ret < 0) {
			fprintf(stderr, "could not write JSON to stdout\n");
			return 1;
		}
	}
	LLVMDisposeModule(module);
	return 0;
}