// SPDX-License-Identifier: GPL-2.0
#include "srcline.h"
#include "addr2line.h"
#include "dso.h"
#include "callchain.h"
#include "libbfd.h"
#include "llvm.h"
#include "symbol.h"
#include "libdw.h"
#include "debug.h"
#include "util.h"

#include <inttypes.h>
#include <string.h>
#include <linux/string.h>
#include <linux/zalloc.h>

bool srcline_full_filename;

char *srcline__unknown = (char *)"??:0";

static const char *srcline_dso_name(struct dso *dso)
{
	const char *dso_name;

	if (dso__symsrc_filename(dso))
		dso_name = dso__symsrc_filename(dso);
	else
		dso_name = dso__long_name(dso);

	if (dso_name[0] == '[')
		return NULL;

	if (is_perf_pid_map_name(dso_name))
		return NULL;

	return dso_name;
}

int inline_list__append(struct symbol *symbol, char *srcline, struct inline_node *node)
{
	struct inline_list *ilist;

	ilist = zalloc(sizeof(*ilist));
	if (ilist == NULL)
		return -1;

	ilist->symbol = symbol;
	ilist->srcline = srcline;

	if (callchain_param.order == ORDER_CALLEE)
		list_add_tail(&ilist->list, &node->val);
	else
		list_add(&ilist->list, &node->val);

	return 0;
}

int inline_list__append_tail(struct symbol *symbol, char *srcline, struct inline_node *node)
{
	struct inline_list *ilist;

	ilist = zalloc(sizeof(*ilist));
	if (ilist == NULL)
		return -1;

	ilist->symbol = symbol;
	ilist->srcline = srcline;

	if (callchain_param.order == ORDER_CALLEE)
		list_add(&ilist->list, &node->val);
	else
		list_add_tail(&ilist->list, &node->val);

	return 0;
}

char *srcline_from_fileline(const char *file, unsigned int line)
{
	char *srcline;

	if (!file)
		return NULL;

	if (!srcline_full_filename)
		file = perf_basename(file);

	if (asprintf(&srcline, "%s:%u", file, line) < 0)
		return NULL;

	return srcline;
}

struct symbol *new_inline_sym(struct dso *dso,
			      struct symbol *base_sym,
			      const char *funcname)
{
	struct symbol *inline_sym;
	char *demangled = NULL;

	if (!funcname)
		funcname = "??";

	if (dso) {
		demangled = dso__demangle_sym(dso, 0, funcname);
		if (demangled)
			funcname = demangled;
	}

	if (base_sym && strcmp(funcname, base_sym->name) == 0) {
		/* reuse the real, existing symbol */
		inline_sym = base_sym;
		/* ensure that we don't alias an inlined symbol, which could
		 * lead to double frees in inline_node__delete
		 */
		assert(!base_sym->inlined);
	} else {
		/* create a fake symbol for the inline frame */
		inline_sym = symbol__new(base_sym ? base_sym->start : 0,
					 base_sym ? (base_sym->end - base_sym->start) : 0,
					 base_sym ? base_sym->binding : 0,
					 base_sym ? base_sym->type : 0,
					 funcname);
		if (inline_sym)
			inline_sym->inlined = 1;
	}

	free(demangled);

	return inline_sym;
}

static int addr2line(const char *dso_name, u64 addr, char **file, unsigned int *line_nr,
		     struct dso *dso, bool unwind_inlines, struct inline_node *node,
		     struct symbol *sym)
{
	int ret = 0;

	if (symbol_conf.addr2line_style[0] == A2L_STYLE_UNKNOWN) {
		int i = 0;

		/* Default addr2line fallback order. */
#ifdef HAVE_LIBDW_SUPPORT
		symbol_conf.addr2line_style[i++] = A2L_STYLE_LIBDW;
#endif
#ifdef HAVE_LIBLLVM_SUPPORT
		symbol_conf.addr2line_style[i++] = A2L_STYLE_LLVM;
#endif
#ifdef HAVE_LIBBFD_SUPPORT
		symbol_conf.addr2line_style[i++] = A2L_STYLE_LIBBFD;
#endif
		symbol_conf.addr2line_style[i++] = A2L_STYLE_CMD;
	}

	for (size_t i = 0; i < ARRAY_SIZE(symbol_conf.addr2line_style); i++) {
		switch (symbol_conf.addr2line_style[i]) {
		case A2L_STYLE_LIBDW:
			ret = libdw__addr2line(addr, file, line_nr, dso, unwind_inlines,
					       node, sym);
			break;
		case A2L_STYLE_LLVM:
			ret = llvm__addr2line(dso_name, addr, file, line_nr, dso, unwind_inlines,
					      node, sym);
			break;
		case A2L_STYLE_LIBBFD:
			ret = libbfd__addr2line(dso_name, addr, file, line_nr, dso, unwind_inlines,
						node, sym);
			break;
		case A2L_STYLE_CMD:
			ret = cmd__addr2line(dso_name, addr, file, line_nr, dso, unwind_inlines,
					     node, sym);
			break;
		case A2L_STYLE_UNKNOWN:
		default:
			break;
		}
		if (ret > 0)
			return ret;
	}

	return 0;
}

int addr2line_configure(const char *var, const char *value, void *cb __maybe_unused)
{
	static const char * const a2l_style_names[] = {
		[A2L_STYLE_LIBDW] = "libdw",
		[A2L_STYLE_LLVM] = "llvm",
		[A2L_STYLE_LIBBFD] = "libbfd",
		[A2L_STYLE_CMD] = "addr2line",
		NULL
	};

	char *s, *p, *saveptr;
	size_t i = 0;

	if (strcmp(var, "addr2line.style"))
		return 0;

	if (!value)
		return -1;

	s = strdup(value);
	if (!s)
		return -1;

	p = strtok_r(s, ",", &saveptr);
	while (p && i < ARRAY_SIZE(symbol_conf.addr2line_style)) {
		bool found = false;
		char *q = strim(p);

		for (size_t j = A2L_STYLE_LIBDW; j < MAX_A2L_STYLE; j++) {
			if (!strcasecmp(q, a2l_style_names[j])) {
				symbol_conf.addr2line_style[i++] = j;
				found = true;
				break;
			}
		}
		if (!found)
			pr_warning("Unknown addr2line style: %s\n", q);
		p = strtok_r(NULL, ",", &saveptr);
	}

	free(s);
	return 0;
}

static struct inline_node *addr2inlines(const char *dso_name, u64 addr,
					struct dso *dso, struct symbol *sym)
{
	struct inline_node *node;

	node = zalloc(sizeof(*node));
	if (node == NULL) {
		perror("not enough memory for the inline node");
		return NULL;
	}

	INIT_LIST_HEAD(&node->val);
	node->addr = addr;

	addr2line(dso_name, addr, /*file=*/NULL, /*line_nr=*/NULL, dso,
		  /*unwind_inlines=*/true, node, sym);

	return node;
}

/*
 * Number of addr2line failures (without success) before disabling it for that
 * dso.
 */
#define A2L_FAIL_LIMIT 123

char *__get_srcline(struct dso *dso, u64 addr, struct symbol *sym,
		  bool show_sym, bool show_addr, bool unwind_inlines,
		  u64 ip)
{
	char *file = NULL;
	unsigned line = 0;
	char *srcline;
	const char *dso_name;

	if (!dso__has_srcline(dso))
		goto out;

	dso_name = srcline_dso_name(dso);
	if (dso_name == NULL)
		goto out_err;

	if (!addr2line(dso_name, addr, &file, &line, dso,
		       unwind_inlines, /*node=*/NULL, sym))
		goto out_err;

	srcline = srcline_from_fileline(file, line);
	free(file);

	if (!srcline)
		goto out_err;

	dso__set_a2l_fails(dso, 0);

	return srcline;

out_err:
	dso__set_a2l_fails(dso, dso__a2l_fails(dso) + 1);
	if (dso__a2l_fails(dso) > A2L_FAIL_LIMIT) {
		dso__set_has_srcline(dso, false);
		dso__free_a2l(dso);
	}
out:
	if (!show_addr)
		return (show_sym && sym) ?
			    strndup(sym->name, sym->namelen) : SRCLINE_UNKNOWN;

	if (sym) {
		if (asprintf(&srcline, "%s+%" PRIu64, show_sym ? sym->name : "",
					ip - sym->start) < 0)
			return SRCLINE_UNKNOWN;
	} else if (asprintf(&srcline, "%s[%" PRIx64 "]", dso__short_name(dso), addr) < 0)
		return SRCLINE_UNKNOWN;
	return srcline;
}

/* Returns filename and fills in line number in line */
char *get_srcline_split(struct dso *dso, u64 addr, unsigned *line)
{
	char *file = NULL;
	const char *dso_name;

	if (!dso__has_srcline(dso))
		return NULL;

	dso_name = srcline_dso_name(dso);
	if (dso_name == NULL)
		goto out_err;

	if (!addr2line(dso_name, addr, &file, line, dso, /*unwind_inlines=*/true,
			/*node=*/NULL, /*sym=*/NULL))
		goto out_err;

	dso__set_a2l_fails(dso, 0);
	return file;

out_err:
	dso__set_a2l_fails(dso, dso__a2l_fails(dso) + 1);
	if (dso__a2l_fails(dso) > A2L_FAIL_LIMIT) {
		dso__set_has_srcline(dso, false);
		dso__free_a2l(dso);
	}

	return NULL;
}

void zfree_srcline(char **srcline)
{
	if (*srcline == NULL)
		return;

	if (*srcline != SRCLINE_UNKNOWN)
		free(*srcline);

	*srcline = NULL;
}

char *get_srcline(struct dso *dso, u64 addr, struct symbol *sym,
		  bool show_sym, bool show_addr, u64 ip)
{
	return __get_srcline(dso, addr, sym, show_sym, show_addr, false, ip);
}

struct srcline_node {
	u64			addr;
	char			*srcline;
	struct rb_node		rb_node;
};

void srcline__tree_insert(struct rb_root_cached *tree, u64 addr, char *srcline)
{
	struct rb_node **p = &tree->rb_root.rb_node;
	struct rb_node *parent = NULL;
	struct srcline_node *i, *node;
	bool leftmost = true;

	node = zalloc(sizeof(struct srcline_node));
	if (!node) {
		perror("not enough memory for the srcline node");
		return;
	}

	node->addr = addr;
	node->srcline = srcline;

	while (*p != NULL) {
		parent = *p;
		i = rb_entry(parent, struct srcline_node, rb_node);
		if (addr < i->addr)
			p = &(*p)->rb_left;
		else {
			p = &(*p)->rb_right;
			leftmost = false;
		}
	}
	rb_link_node(&node->rb_node, parent, p);
	rb_insert_color_cached(&node->rb_node, tree, leftmost);
}

char *srcline__tree_find(struct rb_root_cached *tree, u64 addr)
{
	struct rb_node *n = tree->rb_root.rb_node;

	while (n) {
		struct srcline_node *i = rb_entry(n, struct srcline_node,
						  rb_node);

		if (addr < i->addr)
			n = n->rb_left;
		else if (addr > i->addr)
			n = n->rb_right;
		else
			return i->srcline;
	}

	return NULL;
}

void srcline__tree_delete(struct rb_root_cached *tree)
{
	struct srcline_node *pos;
	struct rb_node *next = rb_first_cached(tree);

	while (next) {
		pos = rb_entry(next, struct srcline_node, rb_node);
		next = rb_next(&pos->rb_node);
		rb_erase_cached(&pos->rb_node, tree);
		zfree_srcline(&pos->srcline);
		zfree(&pos);
	}
}

struct inline_node *dso__parse_addr_inlines(struct dso *dso, u64 addr,
					    struct symbol *sym)
{
	const char *dso_name;

	dso_name = srcline_dso_name(dso);
	if (dso_name == NULL)
		return NULL;

	return addr2inlines(dso_name, addr, dso, sym);
}

void inline_node__delete(struct inline_node *node)
{
	struct inline_list *ilist, *tmp;

	list_for_each_entry_safe(ilist, tmp, &node->val, list) {
		list_del_init(&ilist->list);
		zfree_srcline(&ilist->srcline);
		/* only the inlined symbols are owned by the list */
		if (ilist->symbol && ilist->symbol->inlined)
			symbol__delete(ilist->symbol);
		free(ilist);
	}

	free(node);
}

void inlines__tree_insert(struct rb_root_cached *tree,
			  struct inline_node *inlines)
{
	struct rb_node **p = &tree->rb_root.rb_node;
	struct rb_node *parent = NULL;
	const u64 addr = inlines->addr;
	struct inline_node *i;
	bool leftmost = true;

	while (*p != NULL) {
		parent = *p;
		i = rb_entry(parent, struct inline_node, rb_node);
		if (addr < i->addr)
			p = &(*p)->rb_left;
		else {
			p = &(*p)->rb_right;
			leftmost = false;
		}
	}
	rb_link_node(&inlines->rb_node, parent, p);
	rb_insert_color_cached(&inlines->rb_node, tree, leftmost);
}

struct inline_node *inlines__tree_find(struct rb_root_cached *tree, u64 addr)
{
	struct rb_node *n = tree->rb_root.rb_node;

	while (n) {
		struct inline_node *i = rb_entry(n, struct inline_node,
						 rb_node);

		if (addr < i->addr)
			n = n->rb_left;
		else if (addr > i->addr)
			n = n->rb_right;
		else
			return i;
	}

	return NULL;
}

void inlines__tree_delete(struct rb_root_cached *tree)
{
	struct inline_node *pos;
	struct rb_node *next = rb_first_cached(tree);

	while (next) {
		pos = rb_entry(next, struct inline_node, rb_node);
		next = rb_next(&pos->rb_node);
		rb_erase_cached(&pos->rb_node, tree);
		inline_node__delete(pos);
	}
}
