/*
Copyright(C) 2016, Red Hat, Inc., Jerome Marchand
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Internal representation and manipulation of symbols
*/
#ifndef _GNU_SOURCE /* We use GNU basename() that doesn't modify the arg */
#error "We need GNU version of basename()!"
#endif
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <strings.h>
#include <limits.h>
#include <errno.h>
#include <unistd.h>
#include <getopt.h>
#include <libgen.h>
#include <sys/stat.h>
#include "objects.h"
#include "utils.h"
#include "main.h"
#include "record.h"
/* Indentation offset for c-style and tree debug outputs */
#define C_INDENT_OFFSET 8
#define DBG_INDENT_OFFSET 4
obj_list_t *obj_list_new(obj_t *obj)
{
obj_list_t *list = safe_zmalloc(sizeof(obj_list_t));
list->member = obj;
list->next = NULL;
return list;
}
static void obj_list_init(obj_list_head_t *head, obj_t *obj)
{
obj_list_t *list = obj_list_new(obj);
head->first = head->last = list;
}
obj_list_head_t *obj_list_head_new(obj_t *obj)
{
obj_list_head_t *h = safe_zmalloc(sizeof(obj_list_head_t));
obj_list_init(h, obj);
return h;
}
static bool obj_list_empty(obj_list_head_t *head)
{
return head->first == NULL;
}
void obj_list_add(obj_list_head_t *head, obj_t *obj)
{
obj_list_t *list;
if (obj_list_empty(head)) {
obj_list_init(head, obj);
return;
}
list = obj_list_new(obj);
if (head->last->next)
fprintf(stderr, "head->last is not the last\n");
head->last->next = list;
head->last = list;
}
obj_t *obj_new(obj_types type, char *name)
{
obj_t *new = safe_zmalloc(sizeof(obj_t));
new->type = type;
new->name = global_string_get_move(name);
return new;
}
static void _obj_free(obj_t *o, obj_t *skip);
static void _obj_list_free(obj_list_head_t *l, obj_t *skip)
{
obj_list_t *list;
obj_list_t *next;
if (l == NULL)
return;
list = l->first;
free(l);
while (list) {
_obj_free(list->member, skip);
next = list->next;
free(list);
list = next;
}
}
static void obj_list_free(obj_list_head_t *l)
{
_obj_list_free(l, NULL);
}
/*
* Free the tree o, but keep the subtree skip.
*/
static void _obj_free(obj_t *o, obj_t *skip)
{
if (!o || (o == skip))
return;
if (o->type == __type_reffile && o->depend_rec_node) {
list_del(o->depend_rec_node);
o->depend_rec_node = NULL;
}
_obj_list_free(o->member_list, skip);
if (o->ptr)
_obj_free(o->ptr, skip);
if (is_weak(o))
free(o->link);
free(o);
}
/*
* Free the all object
*/
void obj_free(obj_t *o)
{
_obj_free(o, NULL);
}
#define _CREATE_NEW_FUNC(type, suffix) \
obj_t *obj_##type##_##suffix(char *name) \
{ \
obj_t *new = obj_new(__type_##type, name); \
return new; \
}
#define CREATE_NEW_FUNC(type) _CREATE_NEW_FUNC(type, new)
#define CREATE_NEW_FUNC_NONAME(type) \
_CREATE_NEW_FUNC(type, new_) \
obj_t *obj_##type##_new() \
{ \
return obj_##type##_new_(NULL); \
}
#define _CREATE_NEW_ADD_FUNC(type, infix) \
obj_t *obj_##type##_##infix##_##add(char *name, obj_t *obj) \
{ \
obj_t *new = obj_new(__type_##type, name); \
new->ptr = obj; \
return new; \
}
#define CREATE_NEW_ADD_FUNC(type) _CREATE_NEW_ADD_FUNC(type, new)
#define CREATE_NEW_ADD_FUNC_NONAME(type) \
_CREATE_NEW_ADD_FUNC(type, new_) \
obj_t *obj_##type##_new_add(obj_t *obj) \
{ \
return obj_##type##_new__add(NULL, obj); \
}
CREATE_NEW_FUNC(struct)
CREATE_NEW_FUNC(union)
CREATE_NEW_FUNC(enum)
CREATE_NEW_FUNC(constant)
CREATE_NEW_FUNC_NONAME(reffile)
CREATE_NEW_ADD_FUNC(func)
CREATE_NEW_ADD_FUNC(typedef)
CREATE_NEW_ADD_FUNC(var)
CREATE_NEW_ADD_FUNC(struct_member)
CREATE_NEW_ADD_FUNC_NONAME(ptr)
CREATE_NEW_ADD_FUNC_NONAME(array)
CREATE_NEW_ADD_FUNC_NONAME(qualifier)
CREATE_NEW_FUNC(assembly)
CREATE_NEW_FUNC(weak)
obj_t *obj_basetype_new(char *base_type)
{
obj_t *new = obj_new(__type_base, NULL);
new->base_type = global_string_get_move(base_type);
return new;
}
const char *obj_type_name[NR_OBJ_TYPES+1] = {
"reference file",
"struct",
"union",
"enum",
"func",
"ptr",
"typedef",
"array",
"var",
"struct member",
"type qualifier",
"base",
"constant",
"assembly",
"weak",
"unknown type"
};
static const char *typetostr(obj_t *o)
{
int t = o->type;
if (t >= NR_OBJ_TYPES)
t = NR_OBJ_TYPES;
return obj_type_name[t];
}
static int c_precedence(obj_t *o)
{
switch (o->type) {
case __type_func:
case __type_array:
return 1;
case __type_ptr:
return 2;
default:
return INT_MAX;
}
}
/*
* Returns whether parentheses are needed
*
* Pointer have a higher precedence than function and array, so we need to put
* parentheses around a pointer to a function of array.
*/
static bool is_paren_needed(obj_t *node)
{
obj_t *child = node->ptr;
while (child) {
if (c_precedence(child) < c_precedence(node))
return true;
child = child->ptr;
}
return false;
}
static char *print_margin_offset(const char *prefix, const char *s, int depth)
{
size_t len = snprintf(NULL, 0, "%-*s", depth * C_INDENT_OFFSET, s) + 1;
char *ret;
if (prefix)
len += strlen(prefix);
if (!len)
return NULL;
ret = safe_zmalloc(len);
snprintf(ret, len, "%s%-*s",
prefix ? prefix : "", depth * C_INDENT_OFFSET, s);
return ret;
}
static char *print_margin(const char *prefix, int depth)
{
return print_margin_offset(prefix, "", depth);
}
/*
* Return type for print_* functions
*
* Because C mixes prefix and postfix operator, the code generation from a node
* may need to add code before, after or in the middle of the code generated by
* subtrees. Thus we sometimes need two return two strings.
*
* Attention to the precedence and associativity sould be taken when
* deciding where a specific string should be inserted
*/
typedef struct {
char *prefix;
char *postfix;
} pp_t;
static void free_pp(pp_t pp)
{
free(pp.prefix);
free(pp.postfix);
}
static pp_t _print_tree(obj_t *o, int depth, bool newline, const char *prefix);
/*
* Add prefix p at the begining of string s (reallocated)
*
* space: add a space between p and s
* freep: free the string p
*/
static char *_prefix_str(char **s, char *p, bool space, bool freep)
{
size_t lenp = strlen(p), lens = 0, newlen;
if (*s)
lens = strlen(*s);
newlen = lens + lenp + 1;
if (space)
newlen++;
*s = safe_realloc(*s, newlen);
if (lens)
memmove(space ? *s+lenp+1 : *s+lenp, *s, lens + 1);
else
(*s)[lenp] = '\0';
memcpy(*s, p, lenp);
if (space)
(*s)[lenp] = ' ';
if (freep)
free(p);
return *s;
}
static char *prefix_str(char **s, char *p)
{
if (!p)
return *s;
return _prefix_str(s, p, false, false);
}
static char *prefix_str_free(char **s, char *p)
{
if (!p)
return *s;
return _prefix_str(s, p, false, true);
}
static char *prefix_str_space(char **s, const char *p)
{
if (!p)
return *s;
/* freep is false so we can pass const char * */
return _prefix_str(s, (char *)p, true, false);
}
/*
* Add suffix p at the end of string s (realocated)
*
* space: add a space between p and s
* freep: free the string p
*/
static char *_postfix_str(char **s, char *p, bool space, bool freep)
{
int lenp = strlen(p), lens = 0, newlen;
if (*s)
lens = strlen(*s);
newlen = lens + lenp + 1;
if (space)
newlen++;
*s = safe_realloc(*s, newlen);
if (lens == 0)
(*s)[0] = '\0';
if (space)
strcat(*s, " ");
strcat(*s, p);
if (freep)
free(p);
return *s;
}
static char *postfix_str(char **s, const char *p)
{
if (!p)
return *s;
/* freep is false so we can pass const char * */
return _postfix_str(s, (char *)p, false, false);
}
static char *postfix_str_free(char **s, char *p)
{
if (!p)
return *s;
return _postfix_str(s, p, false, true);
}
static pp_t print_base(obj_t *o, int depth, const char *prefix)
{
pp_t ret = {NULL, NULL};
safe_asprintf(&ret.prefix, "%s ", o->base_type);
return ret;
}
static pp_t print_constant(obj_t *o, int depth, const char *prefix)
{
pp_t ret = {NULL, NULL};
safe_asprintf(&ret.prefix, "%s = %li", o->name, (long)o->constant);
return ret;
}
static pp_t print_reffile(obj_t *o, int depth, const char *prefix)
{
pp_t ret = {NULL, NULL};
char *s = filenametotype(o->base_type);
s = safe_realloc(s, strlen(s) + 2);
strcat(s, " ");
ret.prefix = s;
return ret;
}
/* Print a struct, enum or an union */
static pp_t print_structlike(obj_t *o, int depth, const char *prefix)
{
pp_t ret = {NULL, NULL}, tmp;
obj_list_t *list = NULL;
char *s, *margin;
if (o->name)
safe_asprintf(&s, "%s %s {\n", typetostr(o), o->name);
else
safe_asprintf(&s, "%s {\n", typetostr(o));
if (o->member_list)
list = o->member_list->first;
while (list) {
tmp = _print_tree(list->member, depth+1, true, prefix);
postfix_str_free(&s, tmp.prefix);
postfix_str_free(&s, tmp.postfix);
postfix_str(&s, o->type == __type_enum ? ",\n" : ";\n");
list = list->next;
}
margin = print_margin(prefix, depth);
postfix_str_free(&s, margin);
postfix_str(&s, "}");
ret.prefix = s;
return ret;
}
static pp_t print_func(obj_t *o, int depth, const char *prefix)
{
pp_t ret = {NULL, NULL}, return_type;
obj_list_t *list = NULL;
obj_t *next = o->ptr;
char *s, *margin;
const char *name;
return_type = _print_tree(next, depth, false, prefix);
ret.prefix = return_type.prefix;
if (o->name)
name = o->name;
else
name = "";
safe_asprintf(&s, "%s(\n", name);
if (o->member_list)
list = o->member_list->first;
while (list) {
pp_t arg = _print_tree(list->member, depth+1, true, prefix);
postfix_str_free(&s, arg.prefix);
postfix_str_free(&s, arg.postfix);
list = list->next;
postfix_str(&s, list ? ",\n" : "\n");
}
margin = print_margin(prefix, depth);
postfix_str_free(&s, margin);
postfix_str(&s, ")");
ret.postfix = s;
return ret;
}
static pp_t print_array(obj_t *o, int depth, const char *prefix)
{
pp_t ret;
char *s;
obj_t *next = o->ptr;
ret = _print_tree(next, depth, false, prefix);
safe_asprintf(&s, "[%lu]", o->constant);
prefix_str_free(&ret.postfix, s);
return ret;
}
static pp_t print_ptr(obj_t *o, int depth, const char *prefix)
{
pp_t ret;
bool need_paren = is_paren_needed(o);
obj_t *next = o->ptr;
ret = _print_tree(next, depth, false, prefix);
if (need_paren) {
postfix_str(&ret.prefix, "(*");
prefix_str(&ret.postfix, ")");
} else
postfix_str(&ret.prefix, "*");
return ret;
}
/* Print a var or a struct_member */
static pp_t print_varlike(obj_t *o, int depth, const char *prefix)
{
pp_t ret;
char *s = NULL;
if (is_bitfield(o))
safe_asprintf(&s, "%s:%i",
o->name, o->last_bit - o->first_bit + 1);
else
s = (char *)o->name;
ret = _print_tree(o->ptr, depth, false, prefix);
if (s)
postfix_str(&ret.prefix, s);
if (is_bitfield(o))
free(s);
return ret;
}
static pp_t print_typedef(obj_t *o, int depth, const char *prefix)
{
pp_t ret;
ret = _print_tree(o->ptr, depth, false, prefix);
prefix_str(&ret.prefix, "typedef ");
postfix_str(&ret.prefix, o->name);
return ret;
}
static pp_t print_qualifier(obj_t *o, int depth, const char *prefix)
{
pp_t ret;
ret = _print_tree(o->ptr, depth, false, prefix);
prefix_str_space(&ret.prefix, o->base_type);
return ret;
}
static pp_t print_assembly(obj_t *o, int depth, const char *prefix)
{
pp_t ret = {NULL, NULL};
prefix_str(&ret.prefix, "assembly ");
postfix_str(&ret.prefix, o->name);
return ret;
}
static pp_t print_weak(obj_t *o, int depth, const char *prefix)
{
pp_t ret = {NULL, NULL};
prefix_str(&ret.prefix, "weak ");
postfix_str(&ret.prefix, o->name);
postfix_str(&ret.prefix, " -> ");
postfix_str(&ret.prefix, o->link);
return ret;
}
#define BASIC_CASE(type) \
case __type_##type: \
ret = print_##type(o, depth, prefix); \
break;
struct dopt display_options;
/*
* Display an object in a c-like format
*
* o: object to be displayed
* depth: current indentation depth
* newline: is this the begining of a new line?
* prefix: prefix to be printed at the begining of each line
*/
static pp_t _print_tree(obj_t *o, int depth, bool newline, const char *prefix)
{
pp_t ret = {NULL, NULL};
char *margin;
/* silence coverity on write-only variable */
(void)ret;
if (!o)
fail("NULL pointer in _print_tree\n");
debug("_print_tree(): %s\n", typetostr(o));
switch (o->type) {
BASIC_CASE(reffile);
BASIC_CASE(constant);
BASIC_CASE(base);
BASIC_CASE(typedef);
BASIC_CASE(qualifier);
BASIC_CASE(func);
BASIC_CASE(array);
BASIC_CASE(ptr);
BASIC_CASE(assembly);
BASIC_CASE(weak);
case __type_var:
case __type_struct_member:
ret = print_varlike(o, depth, prefix);
break;
case __type_struct:
case __type_union:
case __type_enum:
ret = print_structlike(o, depth, prefix);
break;
default:
fail("WIP: doesn't handle %s\n", typetostr(o));
}
if (!newline)
return ret;
if (o->type == __type_struct_member && !display_options.no_offset) {
char *offstr;
if (is_bitfield(o))
safe_asprintf(&offstr, "0x%lx:%2i-%-2i ",
o->offset, o->first_bit, o->last_bit);
else
safe_asprintf(&offstr, "0x%lx ", o->offset);
margin = print_margin_offset(prefix, offstr, depth);
free(offstr);
} else
margin = print_margin(prefix, depth);
prefix_str_free(&ret.prefix, margin);
return ret;
}
void obj_print_tree__prefix(obj_t *root, const char *prefix, FILE *stream)
{
pp_t s = _print_tree(root, 0, true, prefix);
fprintf(stream, "%s%s;\n",
s.prefix ? s.prefix : "",
s.postfix ? s.postfix : "");
free_pp(s);
}
void obj_print_tree(obj_t *root)
{
obj_print_tree__prefix(root, NULL, stdout);
}
static void fill_parent_rec(obj_t *o, obj_t *parent)
{
obj_list_t *list = NULL;
o->parent = parent;
if (o->member_list)
list = o->member_list->first;
while (list) {
fill_parent_rec(list->member, o);
list = list->next;
}
if (o->ptr)
fill_parent_rec(o->ptr, o);
}
/*
* Walk the tree and fill all the parents field
*/
void obj_fill_parent(obj_t *root)
{
fill_parent_rec(root, NULL);
}
static int walk_list(obj_list_t *list, cb_t cb_pre, cb_t cb_in, cb_t cb_post,
void *args, bool ptr_first)
{
int ret = CB_CONT;
while (list) {
ret = obj_walk_tree3(list->member, cb_pre, cb_in, cb_post,
args, ptr_first);
if (ret == CB_FAIL)
return ret;
else
ret = CB_CONT;
list = list->next;
}
return ret;
}
static int walk_ptr(obj_t *o, cb_t cb_pre, cb_t cb_in, cb_t cb_post,
void *args, bool ptr_first)
{
int ret = CB_CONT;
if (o->ptr) {
ret = obj_walk_tree3(o->ptr, cb_pre, cb_in, cb_post,
args, ptr_first);
if (ret == CB_FAIL)
return ret;
else
ret = CB_CONT;
}
return ret;
}
/*
* Tree walk with prefix, infix and postfix callbacks
*
* o: walked tree
* cb_pre: callback function called before walking the subtrees
* cb_in: callback function called between walking the subtrees
* cp_post: callback function called between walking the subtrees
* args: argument passed to the callbacks
* ptr_first: whether we walk member_list of ptr first
*/
int obj_walk_tree3(obj_t *o, cb_t cb_pre, cb_t cb_in, cb_t cb_post,
void *args, bool ptr_first)
{
obj_list_t *list = NULL;
int ret = CB_CONT;
if (cb_pre) {
ret = cb_pre(o, args);
if (ret)
return ret;
}
if (o->member_list)
list = o->member_list->first;
if (ptr_first)
ret = walk_ptr(o, cb_pre, cb_in, cb_post, args, ptr_first);
else
ret = walk_list(list, cb_pre, cb_in, cb_post, args, ptr_first);
if (ret == CB_FAIL)
return ret;
if (cb_in) {
ret = cb_in(o, args);
if (ret)
return ret;
}
if (ptr_first)
ret = walk_list(list, cb_pre, cb_in, cb_post, args, ptr_first);
else
ret = walk_ptr(o, cb_pre, cb_in, cb_post, args, ptr_first);
if (ret == CB_FAIL)
return ret;
if (cb_post) {
ret = cb_post(o, args);
if (ret)
return ret;
}
return ret;
}
/*
* Simple tree walk with a prefix callback
*
* It walks the member_list subtree first.
*/
int obj_walk_tree(obj_t *root, cb_t cb, void *args)
{
return obj_walk_tree3(root, cb, NULL, NULL, args, false);
}
static void _show_node(FILE *f, obj_t *o, int margin)
{
if (o)
fprintf(f,
"\%*s<%s, \"%s\", \"%s\", %p, %p, %p, %lu, %i, %i>\n",
margin, "", typetostr(o), o->name, o->base_type,
o, o->parent, o->ptr,
o->offset, o->first_bit, o->last_bit);
else
fprintf(f, "\%*s<(nil)>\n", margin, "");
}
static void show_node(obj_t *o, int margin)
{
_show_node(stdout, o, margin);
}
static int debug_node(obj_t *node, void *args)
{
int *depth = (int *) args;
show_node(node, *depth * DBG_INDENT_OFFSET);
(*depth)++;
return CB_CONT;
}
static int dec_depth(obj_t *node, void *args)
{
int *depth = (int *) args;
(*depth)--;
return CB_CONT;
}
/*
* Print a raw representation of the internal object tree
*/
int obj_debug_tree(obj_t *root)
{
int depth = 0;
return obj_walk_tree3(root, debug_node, NULL, dec_depth, &depth, false);
}
/*
* Hide some RH_KABI magic
*
* WARNING: this code is ugly and full of add-hoc hacks, but I'm
* afraid it can't be fixed. It has to follow the internals of
* RH_KABI_* macros. Also, it may have to change if RH_KABI_*
* functions change in the future.
*
* RH_KABI_REPLACE the old field by a complex construction of union
* and struct used to check that the new field didn't change the
* alignement. It is of the form:
* union {
* _new;
* struct {
* _orig;
* } __UNIQUE_ID_rh_kabi_hideXX
* union {};
* }
*
* RH_KABI_USE2(_P) replace a single field by two field that fits in
* the same space. It puts the two new field into an unnamed
* struct. We don't hide that as we have no way to know if that struct
* is an artifact from RH_KABI_USE2 or was added deliberately.
*
* RH_KABI_DEPRECATE(_FN) prefix the field name with
* rh_reserved_. This is not the most specific string. It currently
* appears in a few places that deprecates the field by hand, in which
* it's OK to hide it too, but for some reason in
* block_device_operations the reserved fields are of the form "void
* *rh_reserved_ptrsX" instead of the usual "unsigned long
* rh_reservedX". Treat this case as an exception.
*
* Most RH_KABI_* functions, don't add any recognizable code so we
* can't hide them here.
*/
static int hide_kabi_cb(obj_t *o, void *args)
{
obj_t *kabi_struct, *new, *old, *parent = o->parent, *keeper;
obj_list_head_t *lh;
obj_list_t *l;
bool show_new_field = (bool) args;
if (o->name) {
if (!strncmp(o->name, RH_KABI_HIDE, RH_KABI_HIDE_LEN))
fail("Missed a kabi unique ID\n");
/* Hide RH_KABI_DEPRECATE* */
if (!strncmp(o->name, "rh_reserved_", 12) &&
strncmp(o->name, "rh_reserved_ptrs", 16)) {
char *tmp = strdup(o->name+12);
o->name = global_string_get_move(tmp);
}
}
/* Hide RH_KABI_REPLACE */
if ((o->type != __type_union) || o->name ||
!(lh = o->member_list) || obj_list_empty(lh) ||
!(l = lh->first) || !(new = l->member) ||
!(l = l->next) || !(kabi_struct = l->member) ||
(kabi_struct->type != __type_var) ||
!kabi_struct->name ||
strncmp(kabi_struct->name, RH_KABI_HIDE, RH_KABI_HIDE_LEN))
return CB_CONT;
if (!kabi_struct->ptr || kabi_struct->ptr->type != __type_struct ||
!(lh = kabi_struct->ptr->member_list) || obj_list_empty(lh) ||
!(l = lh->first) || !(old = l->member))
fail("Unexpeted rh_kabi_hide struct format\n");
/*
* It is a rh_kabi_hide union
* old is the first member of kabi_struct
*
* Need to replace that:
* <struct member, "(null)", "(null)", 0x1ea9840 16 0 0> (parent)
* <union, "(null)", "(null)", (nil) 0 0 0> (o)
* <var, "new_field", "(null)", 0x1ea9540 0 0 0> (new)
* <base, "(null)", "int", (nil) 0 0 0>
* <var, "__UNIQUE_ID_rh_kabi_hide55", "(null)", 0x1ea9700 0 0 0>
* <struct, "(null)", "(null)", (nil) 0 0 0>
* <struct member, "old_field", "(null)", 0x1ea9640 0 0 0>
* <base, "(null)", "int", (nil) 0 0 0> ^(old)
* <var, "(null)", "(null)", 0x1ea97a0 0 0 0>
* <union, "(null)", "(null)", (nil) 0 0 0>
*
* by that:
* <struct member, "new_field", "(null)", 0x1ea9540 16 0 0>
* <base, "(null)", "int", (nil) 0 0 0>
*
* or that:
* <struct member, "old_field", "(null)", 0x1ea9640 0 0 0>
* <base, "(null)", "int", (nil) 0 0 0>
*
* Parent is always an unary node, struct_member or var
*/
if (!parent ||
!((parent->type == __type_var) ||
(parent->type == __type_struct_member)) ||
(parent->ptr != o) || parent->name) {
_show_node(stderr, parent, 0);
fail("Unexpected parent\n");
}
if (new->type != __type_var) {
_show_node(stderr, new, 0);
fail("Unexpected new field\n");
}
if (old->type != __type_struct_member) {
_show_node(stderr, old, 0);
fail("Unexpected old field\n");
}
keeper = show_new_field ? new : old;
parent->name = keeper->name;
parent->ptr = keeper->ptr;
parent->ptr->parent = parent;
_obj_free(o, keeper);
free(keeper);
return CB_SKIP;
}
int obj_hide_kabi(obj_t *root, bool show_new_field)
{
return obj_walk_tree(root, hide_kabi_cb, (void *)show_new_field);
}
static bool obj_is_declaration(obj_t *obj)
{
if (obj->type != __type_reffile || obj->ref_record == NULL)
return false;
return record_is_declaration(obj->ref_record);
}
static bool obj_is_kabi_hide(obj_t *obj)
{
if (obj->name == NULL)
return false;
return strncmp(obj->name, RH_KABI_HIDE, RH_KABI_HIDE_LEN) == 0;
}
bool obj_eq(obj_t *o1, obj_t *o2, bool ignore_versions)
{
if (o1->type != o2->type)
return false;
if (o1->type == __type_reffile) {
if (ignore_versions) {
return record_get_key(o1->ref_record) ==
record_get_key(o2->ref_record);
}
return o1->ref_record == o2->ref_record;
}
/* borrow parts from cmp_nodes */
if ((o1->name != o2->name) ||
((o1->ptr == NULL) != (o2->ptr == NULL)) ||
(has_constant(o1) && (o1->constant != o2->constant)) ||
(has_index(o1) && (o1->index != o2->index)) ||
(is_bitfield(o1) != is_bitfield(o2)) ||
(o1->alignment != o2->alignment) ||
(o1->byte_size != o2->byte_size))
return false;
/* just compare bitfields */
if (is_bitfield(o1) &&
((o1->last_bit != o2->last_bit) ||
(o1->first_bit != o2->first_bit)))
return false;
if ((o1->member_list == NULL) !=
(o2->member_list == NULL))
return false;
if (o1->base_type != o2->base_type)
return false;
return true;
}
static obj_t *obj_copy(obj_t *o1)
{
obj_t *o;
o = safe_zmalloc(sizeof(*o));
*o = *o1;
o->ptr = NULL;
o->member_list = NULL;
if (o1->type == __type_reffile && o1->depend_rec_node)
o->depend_rec_node = list_node_add(o1->depend_rec_node, o);
return o;
}
obj_t *obj_merge(obj_t *o1, obj_t *o2, unsigned int flags);
static obj_list_head_t *obj_members_merge(obj_list_head_t *list1,
obj_list_head_t *list2,
unsigned int flags)
{
obj_list_head_t *res = NULL;
obj_list_t *l1;
obj_list_t *l2;
obj_t *o;
if (list1 == NULL || list2 == NULL)
return NULL;
l1 = list1->first;
l2 = list2->first;
while (l1 && l2) {
o = obj_merge(l1->member, l2->member, flags);
if (o == NULL)
goto cleanup;
if (res == NULL)
res = obj_list_head_new(o);
else
obj_list_add(res, o);
l1 = l1->next;
l2 = l2->next;
};
if (l1 || l2)
goto cleanup;
return res;
cleanup:
obj_list_free(res);
return NULL;
}
static inline bool obj_can_merge_two_lines(obj_t *o1, obj_t *o2,
unsigned int flags)
{
/*
* We cannot merge two lines if:
* - their states of being declarations are not equivalent,
* and we require them to be
*/
if (flags & MERGE_FLAG_DECL_EQ &&
(obj_is_declaration(o1) != obj_is_declaration(o2)))
return false;
/*
* We can merge the two lines if:
* - they are the same, or
* - they are both RH_KABI_HIDE, or
* - at least one of them is a declaration,
* and we can merge declarations
*/
if (obj_eq(o1, o2, flags & MERGE_FLAG_VER_IGNORE))
return true;
if (obj_is_kabi_hide(o1) && obj_is_kabi_hide(o2))
return true;
if (flags & MERGE_FLAG_DECL_MERGE &&
(obj_is_declaration(o1) || obj_is_declaration(o2)))
return true;
return false;
}
obj_t *obj_merge(obj_t *o1, obj_t *o2, unsigned int flags)
{
obj_t *merged_ptr;
obj_list_head_t *merged_members;
obj_t *res = NULL;
if (o1 == NULL || o2 == NULL)
return NULL;
if (!obj_can_merge_two_lines(o1, o2, flags))
goto no_merge;
merged_ptr = obj_merge(o1->ptr, o2->ptr, flags);
if (o1->ptr && !merged_ptr)
goto no_merge_ptr;
merged_members = obj_members_merge(o1->member_list,
o2->member_list,
flags);
if (o1->member_list && !merged_members)
goto no_merge_members;
if (obj_is_declaration(o1))
res = obj_copy(o2);
else
res = obj_copy(o1);
res->ptr = merged_ptr;
if (merged_members != NULL)
merged_members->object = res;
res->member_list = merged_members;
return res;
no_merge_members:
obj_list_free(merged_members);
no_merge_ptr:
obj_free(merged_ptr);
no_merge:
return NULL;
}
static void dump_reffile(obj_t *o, FILE *f)
{
int version = record_get_version(o->ref_record);
fprintf(f, "@\"%s", record_get_key(o->ref_record));
if (version > 0)
fprintf(f, "-%i", version);
fprintf(f, ".txt\"\n");
}
static void _dump_members(obj_t *o, FILE *f, void (*dumper)(obj_t *, FILE *))
{
obj_list_head_t *l = o->member_list;
obj_list_t *list;
if (l == NULL)
return;
list = l->first;
while (list) {
dumper(list->member, f);
list = list->next;
}
}
static void dump_arg(obj_t *o, FILE *f)
{
fprintf(f, "%s ", o->name);
obj_dump(o->ptr, f);
}
static void dump_members(obj_t *o, FILE *f)
{
_dump_members(o, f, obj_dump);
}
static void dump_args(obj_t *o, FILE *f)
{
_dump_members(o, f, dump_arg);
}
static void dump_struct(obj_t *o, FILE *f)
{
fprintf(f, "struct %s {\n", o->name);
dump_members(o, f);
fprintf(f, "}\n");
}
static void dump_union(obj_t *o, FILE *f)
{
fprintf(f, "union %s {\n", o->name);
dump_args(o, f);
fprintf(f, "}\n");
}
static void dump_enum(obj_t *o, FILE *f)
{
fprintf(f, "enum %s {\n", o->name);
dump_members(o, f);
fprintf(f, "}\n");
}
static void dump_func(obj_t *o, FILE *f)
{
fprintf(f, "func %s (\n", o->name);
dump_args(o, f);
fprintf(f, ")\n");
obj_dump(o->ptr, f);
}
static void dump_ptr(obj_t *o, FILE *f)
{
fprintf(f, "* ");
obj_dump(o->ptr, f);
}
static void dump_typedef(obj_t *o, FILE *f)
{
fprintf(f, "typedef %s\n", o->name);
obj_dump(o->ptr, f);
}
static void dump_array(obj_t *o, FILE *f)
{
fprintf(f, "[%lu]", o->index);
obj_dump(o->ptr, f);
}
static void dump_var(obj_t *o, FILE *f)
{
fprintf(f, "var %s ", o->name);
obj_dump(o->ptr, f);
}
static void dump_struct_member(obj_t *o, FILE *f)
{
fprintf(f, "0x%lx", o->offset);
if (o->is_bitfield)
fprintf(f, ":%d-%d", o->first_bit, o->last_bit);
if (o->alignment != 0)
fprintf(f, " %u", o->alignment);
fprintf(f, " %s ", o->name);
obj_dump(o->ptr, f);
}
static void dump_qualifier(obj_t *o, FILE *f)
{
fprintf(f, "%s ", o->base_type);
obj_dump(o->ptr, f);
}
static void dump_base(obj_t *o, FILE *f)
{
const char *type = o->base_type;
/* variable args (...) is a special base case */
if (type[0] == '.')
fprintf(f, "%s\n", o->base_type);
else
fprintf(f, "\"%s\"\n", o->base_type);
}
static void dump_constant(obj_t *o, FILE *f)
{
fprintf(f, "%s = 0x%lx\n", o->name, o->constant);
}
static void dump_fail(obj_t *o, FILE *f)
{
fail("Dump call for this type unsupported!\n");
}
struct dumper {
void (*dumper)(obj_t *o, FILE *f);
};
static struct dumper dumpers[] = {
[__type_reffile].dumper = dump_reffile,
[__type_struct].dumper = dump_struct,
[__type_union].dumper = dump_union,
[__type_enum].dumper = dump_enum,
[__type_func].dumper = dump_func,
[__type_ptr].dumper = dump_ptr,
[__type_typedef].dumper = dump_typedef,
[__type_array].dumper = dump_array,
[__type_var].dumper = dump_var,
[__type_struct_member].dumper = dump_struct_member,
[__type_qualifier].dumper = dump_qualifier,
[__type_base].dumper = dump_base,
[__type_constant].dumper = dump_constant,
[__type_assembly].dumper = dump_fail,
[__type_weak].dumper = dump_fail,
};
void obj_dump(obj_t *o, FILE *f)
{
if (o == NULL)
return;
if (o->type >= NR_OBJ_TYPES)
fail("Wrong object type %d", o->type);
dumpers[o->type].dumper(o, f);
}
bool obj_same_declarations(obj_t *o1, obj_t *o2,
struct set *processed)
{
const int ignore_versions = true;
obj_list_t *list1;
obj_list_t *list2;
if (o1 == o2)
return true;
if (!obj_eq(o1, o2, ignore_versions))
return false;
if (o1->type != o2->type ||
(o1->ptr == NULL) != (o2->ptr == NULL) ||
(o1->member_list == NULL) != (o2->member_list == NULL)) {
return false;
}
if (o1->type == __type_reffile &&
!record_same_declarations(o1->ref_record, o2->ref_record,
processed)) {
return false;
}
if (o1->ptr &&
!obj_same_declarations(o1->ptr, o2->ptr, processed)) {
return false;
}
if (o1->member_list) {
list1 = o1->member_list->first;
list2 = o2->member_list->first;
while (list1) {
if (list2 == NULL)
return false;
if (!obj_same_declarations(list1->member,
list2->member,
processed))
return false;
list1 = list1->next;
list2 = list2->next;
}
if (list1 != list2) {
/* different member_list lengths */
return false;
}
}
return true;
}