mirror of
https://github.com/radareorg/radare2.git
synced 2025-02-25 16:51:30 +00:00
Manage types enum more properly (#10139)
This commit is contained in:
parent
d5af26b3da
commit
97de3ff035
@ -947,7 +947,6 @@ static void cmd_print_format(RCore *core, const char *_input, const ut8* block,
|
||||
core->print->reg = core->dbg->reg;
|
||||
core->print->get_register = r_reg_get;
|
||||
core->print->get_register_value = r_reg_get_value;
|
||||
core->print->sdb_types = core->anal->sdb_types;
|
||||
|
||||
int o_blocksize = core->blocksize;
|
||||
|
||||
|
@ -14,17 +14,13 @@ static const char *help_msg_t[] = {
|
||||
"t*", "", "List types info in r2 commands",
|
||||
"t-", " <name>", "Delete types by its name",
|
||||
"t-*", "", "Remove all types",
|
||||
//"t-!", "", "Use to open $EDITOR",
|
||||
"ta", " <type>", "Mark immediate as a type offset",
|
||||
"tb", " <enum> <value>", "Show matching enum bitfield for given number",
|
||||
"tc", " ([cctype])", "calling conventions listing and manipulations",
|
||||
"te", "[?]", "List all loaded enums",
|
||||
"te", " <enum> <value>", "Show name for given enum number",
|
||||
"td", "[?] <string>", "Load types from string",
|
||||
"tf", "", "List all loaded functions signatures",
|
||||
"tk", " <sdb-query>", "Perform sdb query",
|
||||
"tl", "[?]", "Show/Link type to an address",
|
||||
//"to", "", "List opened files",
|
||||
"tn", "[?] [-][addr]", "manage noreturn function attributes and marks",
|
||||
"to", " -", "Open cfg.editor to load types",
|
||||
"to", " <path>", "Load types from C header file",
|
||||
@ -33,7 +29,6 @@ static const char *help_msg_t[] = {
|
||||
"ts", "[?]", "print loaded struct types",
|
||||
"tu", "[?]", "print loaded union types",
|
||||
"tt", "[?]", "List all loaded typedefs",
|
||||
//"| ts k=v k=v @ link.addr set fields at given linked type\n"
|
||||
NULL
|
||||
};
|
||||
|
||||
@ -67,7 +62,7 @@ static const char *help_msg_to[] = {
|
||||
|
||||
static const char *help_msg_tc[] = {
|
||||
"Usage: tc[...]", " [cctype]", "",
|
||||
"tc", "", "List all loaded structs",
|
||||
"tc", "", "List all loaded calling convention",
|
||||
"tc", " [cctype]", "Show convention rules for this type",
|
||||
"tc=", "([cctype])", "Select (or show) default calling convention",
|
||||
"tc-", "[cctype]", "TODO: remove given calling convention",
|
||||
@ -88,7 +83,9 @@ static const char *help_msg_td[] = {
|
||||
static const char *help_msg_te[] = {
|
||||
"Usage: te[...]", "", "",
|
||||
"te", "", "List all loaded enums",
|
||||
"te", " <enum>", "Print all values of enum for given name",
|
||||
"te", " <enum> <value>", "Show name for given enum number",
|
||||
"teb", " <enum> <name>", "Show matching enum bitfield for given name",
|
||||
"te?", "", "show this help",
|
||||
NULL
|
||||
};
|
||||
@ -495,30 +492,39 @@ static int cmd_type(void *data, const char *input) {
|
||||
break;
|
||||
}
|
||||
} break;
|
||||
case 'b': {
|
||||
char *p, *s = (strlen (input) > 1)? strdup (input + 2): NULL;
|
||||
const char *isenum;
|
||||
p = s? strchr (s, ' '): NULL;
|
||||
if (p) {
|
||||
*p++ = 0;
|
||||
// dupp in core.c (see getbitfield())
|
||||
isenum = sdb_const_get (TDB, s, 0);
|
||||
if (isenum && !strcmp (isenum, "enum")) {
|
||||
*--p = '.';
|
||||
const char *res = sdb_const_get (TDB, s, 0);
|
||||
if (res)
|
||||
r_cons_println (res);
|
||||
else eprintf ("Invalid enum member\n");
|
||||
} else {
|
||||
eprintf ("This is not an enum\n");
|
||||
}
|
||||
} else {
|
||||
eprintf ("Missing value\n");
|
||||
case 'e': { // "te"
|
||||
char *res = NULL, *temp = strchr(input, ' ');
|
||||
Sdb *TDB = core->anal->sdb_types;
|
||||
char *name = temp ? strdup (temp + 1): NULL;
|
||||
char *member_name = name ? strchr (name, ' '): NULL;
|
||||
|
||||
if (member_name) {
|
||||
*member_name++ = 0;
|
||||
}
|
||||
free (s);
|
||||
} break;
|
||||
case 'e': {
|
||||
if (!input[1]) {
|
||||
if (name && !r_type_isenum (TDB, name)) {
|
||||
eprintf ("%s is not an enum\n", name);
|
||||
break;
|
||||
}
|
||||
switch (input[1]) {
|
||||
case '?' :
|
||||
r_core_cmd_help (core, help_msg_te);
|
||||
break;
|
||||
case 'b' :
|
||||
res = r_type_enum_member (TDB, name, member_name, 0);
|
||||
break;
|
||||
case ' ' : {
|
||||
RListIter *iter;
|
||||
RTypeEnum *member = R_NEW0 (RTypeEnum);
|
||||
if (member_name) {
|
||||
res = r_type_enum_member (TDB, name, NULL, r_num_math (core->num, member_name));
|
||||
} else {
|
||||
RList *list = r_type_get_enum (TDB, name);
|
||||
r_list_foreach (list, iter, member) {
|
||||
r_cons_printf ("%s = %s\n", member->name, member->val);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case '\0' : {
|
||||
char *name = NULL;
|
||||
SdbKv *kv;
|
||||
SdbListIter *iter;
|
||||
@ -532,33 +538,15 @@ static int cmd_type(void *data, const char *input) {
|
||||
}
|
||||
}
|
||||
}
|
||||
free (name);
|
||||
ls_free (l);
|
||||
break;
|
||||
} break;
|
||||
}
|
||||
if (input[1] == '?') {
|
||||
r_core_cmd_help (core, help_msg_te);
|
||||
break;
|
||||
free (name);
|
||||
if (res) {
|
||||
r_cons_println (res);
|
||||
} else if (member_name) {
|
||||
eprintf ("Invalid enum member\n");
|
||||
}
|
||||
char *p, *s = strdup (input + 2);
|
||||
const char *isenum;
|
||||
p = strchr (s, ' ');
|
||||
if (p) {
|
||||
*p++ = 0;
|
||||
isenum = sdb_const_get (TDB, s, 0);
|
||||
if (isenum && !strncmp (isenum, "enum", 4)) {
|
||||
const char *q = sdb_fmt ("%s.0x%x", s, (ut32)r_num_math (core->num, p));
|
||||
const char *res = sdb_const_get (TDB, q, 0);
|
||||
if (res)
|
||||
r_cons_println (res);
|
||||
} else {
|
||||
eprintf ("This is not an enum\n");
|
||||
}
|
||||
} else {
|
||||
//eprintf ("Missing value\n");
|
||||
r_core_cmdf (core, "t~&%s,=0x", s);
|
||||
}
|
||||
free (s);
|
||||
} break;
|
||||
case ' ':
|
||||
showFormat (core, input + 1, 0);
|
||||
|
@ -1457,55 +1457,6 @@ static void update_sdb(RCore *core) {
|
||||
}
|
||||
}
|
||||
|
||||
// dupped in cmd_type.c
|
||||
static char *getenumname(void *_core, const char *name, ut64 val) {
|
||||
const char *isenum;
|
||||
RCore *core = (RCore*)_core;
|
||||
|
||||
isenum = sdb_const_get (core->anal->sdb_types, name, 0);
|
||||
if (isenum && !strncmp (isenum, "enum", 4)) {
|
||||
const char *q = sdb_fmt ("%s.0x%x", name, val);
|
||||
return sdb_get (core->anal->sdb_types, q, 0);
|
||||
} else {
|
||||
eprintf ("This is not an enum (%s)\n", name);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// TODO: dupped in cmd_type.c
|
||||
static char *getbitfield(void *_core, const char *name, ut64 val) {
|
||||
const char *isenum, *q, *res;
|
||||
RCore *core = (RCore*)_core;
|
||||
char *ret = NULL;
|
||||
int i;
|
||||
|
||||
isenum = sdb_const_get (core->anal->sdb_types, name, 0);
|
||||
if (isenum && !strcmp (isenum, "enum")) {
|
||||
int isFirst = true;
|
||||
ret = r_str_appendf (ret, "0x%08"PFMT64x" : ", val);
|
||||
for (i = 0; i < 32; i++) {
|
||||
if (!(val & (1 << i))) {
|
||||
continue;
|
||||
}
|
||||
q = sdb_fmt ("%s.0x%x", name, (1<<i));
|
||||
res = sdb_const_get (core->anal->sdb_types, q, 0);
|
||||
if (isFirst) {
|
||||
isFirst = false;
|
||||
} else {
|
||||
ret = r_str_append (ret, " | ");
|
||||
}
|
||||
if (res) {
|
||||
ret = r_str_append (ret, res);
|
||||
} else {
|
||||
ret = r_str_appendf (ret, "0x%x", (1<<i));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
eprintf ("This is not an enum\n");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define MINLEN 1
|
||||
static int is_string (const ut8 *buf, int size, int *len) {
|
||||
int i;
|
||||
@ -1770,8 +1721,6 @@ R_API bool r_core_init(RCore *core) {
|
||||
core->print = r_print_new ();
|
||||
core->print->user = core;
|
||||
core->print->num = core->num;
|
||||
core->print->get_enumname = getenumname;
|
||||
core->print->get_bitfield = getbitfield;
|
||||
core->print->offname = r_core_print_offname;
|
||||
core->print->cb_printf = r_cons_printf;
|
||||
core->print->cb_color = r_cons_rainbow_get;
|
||||
@ -1850,6 +1799,7 @@ R_API bool r_core_init(RCore *core) {
|
||||
core->anal->cb.on_fcn_new = on_fcn_new;
|
||||
core->anal->cb.on_fcn_delete = on_fcn_delete;
|
||||
core->anal->cb.on_fcn_rename = on_fcn_rename;
|
||||
core->print->sdb_types = core->anal->sdb_types;
|
||||
core->assembler->syscall = r_syscall_ref (core->anal->syscall); // BIND syscall anal/asm
|
||||
r_anal_set_user_ptr (core->anal, core);
|
||||
core->anal->cb_printf = (void *) r_cons_printf;
|
||||
|
@ -50,8 +50,6 @@ typedef struct r_print_t {
|
||||
char *(*cb_color)(int idx, int last, bool bg);
|
||||
int (*disasm)(void *p, ut64 addr);
|
||||
void (*oprintf)(const char *str, ...);
|
||||
char* (*get_bitfield)(void *user, const char *name, ut64 value);
|
||||
char* (*get_enumname)(void *user, const char *name, ut64 value);
|
||||
int interrupt;
|
||||
int big_endian;
|
||||
int width;
|
||||
|
@ -5,8 +5,17 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct r_type_enum {
|
||||
const char *name;
|
||||
const char *val;
|
||||
}RTypeEnum;
|
||||
|
||||
R_API int r_type_set(Sdb *TDB, ut64 at, const char *field, ut64 val);
|
||||
R_API void r_type_del(Sdb *TDB, const char *name);
|
||||
R_API bool r_type_isenum(Sdb *TDB, const char *name);
|
||||
R_API char *r_type_enum_member(Sdb *TDB, const char *name, const char *member, ut64 val);
|
||||
R_API char *r_type_enum_getbitfield(Sdb *TDB, const char *name, ut64 val);
|
||||
R_API RList* r_type_get_enum (Sdb *TDB, const char *name);
|
||||
R_API int r_type_get_bitsize (Sdb *TDB, const char *type);
|
||||
R_API RList* r_type_get_by_offset(Sdb *TDB, ut64 offset);
|
||||
R_API int r_type_link (Sdb *TDB, const char *val, ut64 addr);
|
||||
|
@ -1110,9 +1110,7 @@ static void r_print_format_bitfield(const RPrint* p, ut64 seeki, char* fmtname,
|
||||
if (MUSTSEE && !SEEVALUE) {
|
||||
p->cb_printf ("0x%08"PFMT64x" = ", seeki);
|
||||
}
|
||||
if (p->get_bitfield) {
|
||||
bitfield = p->get_bitfield (p->user, fmtname, addr);
|
||||
}
|
||||
bitfield = r_type_enum_getbitfield (p->sdb_types, fmtname, addr);
|
||||
if (bitfield && *bitfield) {
|
||||
if (MUSTSEEJSON) p->cb_printf ("\"%s\"}", bitfield);
|
||||
else if (MUSTSEE) p->cb_printf (" %s (bitfield) = %s\n", fieldname, bitfield);
|
||||
@ -1135,9 +1133,7 @@ static void r_print_format_enum (const RPrint* p, ut64 seeki, char* fmtname,
|
||||
if (MUSTSEE && !SEEVALUE) {
|
||||
p->cb_printf ("0x%08"PFMT64x" = ", seeki);
|
||||
}
|
||||
if (p->get_enumname) {
|
||||
enumvalue = p->get_enumname (p->user, fmtname, addr);
|
||||
}
|
||||
enumvalue = r_type_enum_member (p->sdb_types, fmtname, NULL, addr);
|
||||
if (enumvalue && *enumvalue) {
|
||||
if (mode & R_PRINT_DOT) {
|
||||
p->cb_printf ("%s.%s", fmtname, enumvalue);
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* radare - LGPL - Copyright 2013-2018 - pancake, oddcoder */
|
||||
/* radare - LGPL - Copyright 2013-2018 - pancake, oddcoder, sivaramaaa */
|
||||
|
||||
#include "r_util.h"
|
||||
|
||||
@ -21,53 +21,81 @@ R_API int r_type_set(Sdb *TDB, ut64 at, const char *field, ut64 val) {
|
||||
return false;
|
||||
}
|
||||
|
||||
R_API void r_type_del(Sdb *TDB, const char *name) {
|
||||
const char *kind = sdb_const_get (TDB, name, 0);
|
||||
if (!kind) {
|
||||
return;
|
||||
R_API bool r_type_isenum(Sdb *TDB, const char *name) {
|
||||
if (!name) {
|
||||
return false;
|
||||
}
|
||||
if (!strcmp (kind, "type")) {
|
||||
sdb_unset (TDB, sdb_fmt ("type.%s", name), 0);
|
||||
sdb_unset (TDB, sdb_fmt ("type.%s.size", name), 0);
|
||||
sdb_unset (TDB, sdb_fmt ("type.%s.meta", name), 0);
|
||||
sdb_unset (TDB, name, 0);
|
||||
} else if (!strcmp (kind, "struct") || !strcmp (kind, "union")) {
|
||||
int i, n = sdb_array_length(TDB, sdb_fmt ("%s.%s", kind, name));
|
||||
char *elements_key = r_str_newf ("%s.%s", kind, name);
|
||||
for (i = 0; i< n; i++) {
|
||||
char *p = sdb_array_get (TDB, elements_key, i, NULL);
|
||||
sdb_unset (TDB, sdb_fmt ("%s.%s", elements_key, p), 0);
|
||||
free (p);
|
||||
}
|
||||
sdb_unset (TDB, elements_key, 0);
|
||||
sdb_unset (TDB, name, 0);
|
||||
free (elements_key);
|
||||
} else if (!strcmp (kind, "func")) {
|
||||
int i, n = sdb_num_get (TDB, sdb_fmt ("func.%s.args", name), 0);
|
||||
for (i = 0; i < n; i++) {
|
||||
sdb_unset (TDB, sdb_fmt ("func.%s.arg.%d", name, i), 0);
|
||||
}
|
||||
sdb_unset (TDB, sdb_fmt ("func.%s.ret", name), 0);
|
||||
sdb_unset (TDB, sdb_fmt ("func.%s.cc", name), 0);
|
||||
sdb_unset (TDB, sdb_fmt ("func.%s.noreturn", name), 0);
|
||||
sdb_unset (TDB, sdb_fmt ("func.%s.args", name), 0);
|
||||
sdb_unset (TDB, name, 0);
|
||||
} else if (!strcmp (kind, "enum")) {
|
||||
int i;
|
||||
for (i=0;; i++) {
|
||||
const char *tmp = sdb_const_get (TDB, sdb_fmt ("%s.0x%x", name, i), 0);
|
||||
if (!tmp) {
|
||||
break;
|
||||
}
|
||||
sdb_unset (TDB, sdb_fmt ("%s.%s", name, tmp), 0);
|
||||
sdb_unset (TDB, sdb_fmt ("%s.0x%x", name, i), 0);
|
||||
}
|
||||
sdb_unset (TDB, name, 0);
|
||||
const char *type = sdb_const_get (TDB, name, 0);
|
||||
if (type && !strcmp (type, "enum")) {
|
||||
return true;
|
||||
} else {
|
||||
eprintf ("Unrecognized type \"%s\"\n", kind);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
R_API RList* r_type_get_enum (Sdb *TDB, const char *name) {
|
||||
RList *res = r_list_new ();
|
||||
char *p, *val, var[128], var2[128];
|
||||
int n;
|
||||
|
||||
if (!r_type_isenum (TDB, name)) {
|
||||
return NULL;
|
||||
}
|
||||
snprintf (var, sizeof (var), "enum.%s", name);
|
||||
for (n = 0; (p = sdb_array_get (TDB, var, n, NULL)); n++) {
|
||||
RTypeEnum *member = R_NEW0 (RTypeEnum);
|
||||
snprintf (var2, sizeof (var2), "%s.%s", var, p);
|
||||
val = sdb_array_get (TDB, var2, 0, NULL);
|
||||
member->name = p;
|
||||
member->val = val;
|
||||
r_list_append (res, member);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
R_API char *r_type_enum_member(Sdb *TDB, const char *name, const char *member, ut64 val) {
|
||||
const char *q;
|
||||
if (!r_type_isenum (TDB, name)) {
|
||||
return NULL;
|
||||
}
|
||||
if (member) {
|
||||
q = sdb_fmt ("enum.%s.%s", name, member);
|
||||
} else {
|
||||
q = sdb_fmt ("enum.%s.0x%x", name, val);
|
||||
}
|
||||
return sdb_get (TDB, q, 0);
|
||||
}
|
||||
|
||||
R_API char *r_type_enum_getbitfield(Sdb *TDB, const char *name, ut64 val) {
|
||||
char *q, *ret = NULL;
|
||||
const char *res;
|
||||
int i;
|
||||
|
||||
if (!r_type_isenum (TDB, name)) {
|
||||
return NULL;
|
||||
}
|
||||
bool isFirst = true;
|
||||
ret = r_str_appendf (ret, "0x%08"PFMT64x" : ", val);
|
||||
for (i = 0; i < 32; i++) {
|
||||
if (!(val & (1 << i))) {
|
||||
continue;
|
||||
}
|
||||
q = sdb_fmt ("enum.%s.0x%x", name, (1<<i));
|
||||
res = sdb_const_get (TDB, q, 0);
|
||||
if (isFirst) {
|
||||
isFirst = false;
|
||||
} else {
|
||||
ret = r_str_append (ret, " | ");
|
||||
}
|
||||
if (res) {
|
||||
ret = r_str_append (ret, res);
|
||||
} else {
|
||||
ret = r_str_appendf (ret, "0x%x", (1<<i));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
R_API int r_type_get_bitsize(Sdb *TDB, const char *type) {
|
||||
char *query;
|
||||
/* Filter out the structure keyword if type looks like "struct mystruc" */
|
||||
@ -315,6 +343,51 @@ R_API char *r_type_format(Sdb *TDB, const char *t) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
R_API void r_type_del(Sdb *TDB, const char *name) {
|
||||
const char *kind = sdb_const_get (TDB, name, 0);
|
||||
if (!kind) {
|
||||
return;
|
||||
}
|
||||
if (!strcmp (kind, "type")) {
|
||||
sdb_unset (TDB, sdb_fmt ("type.%s", name), 0);
|
||||
sdb_unset (TDB, sdb_fmt ("type.%s.size", name), 0);
|
||||
sdb_unset (TDB, sdb_fmt ("type.%s.meta", name), 0);
|
||||
sdb_unset (TDB, name, 0);
|
||||
} else if (!strcmp (kind, "struct") || !strcmp (kind, "union")) {
|
||||
int i, n = sdb_array_length(TDB, sdb_fmt ("%s.%s", kind, name));
|
||||
char *elements_key = r_str_newf ("%s.%s", kind, name);
|
||||
for (i = 0; i< n; i++) {
|
||||
char *p = sdb_array_get (TDB, elements_key, i, NULL);
|
||||
sdb_unset (TDB, sdb_fmt ("%s.%s", elements_key, p), 0);
|
||||
free (p);
|
||||
}
|
||||
sdb_unset (TDB, elements_key, 0);
|
||||
sdb_unset (TDB, name, 0);
|
||||
free (elements_key);
|
||||
} else if (!strcmp (kind, "func")) {
|
||||
int i, n = sdb_num_get (TDB, sdb_fmt ("func.%s.args", name), 0);
|
||||
for (i = 0; i < n; i++) {
|
||||
sdb_unset (TDB, sdb_fmt ("func.%s.arg.%d", name, i), 0);
|
||||
}
|
||||
sdb_unset (TDB, sdb_fmt ("func.%s.ret", name), 0);
|
||||
sdb_unset (TDB, sdb_fmt ("func.%s.cc", name), 0);
|
||||
sdb_unset (TDB, sdb_fmt ("func.%s.noreturn", name), 0);
|
||||
sdb_unset (TDB, sdb_fmt ("func.%s.args", name), 0);
|
||||
sdb_unset (TDB, name, 0);
|
||||
} else if (!strcmp (kind, "enum")) {
|
||||
RList *list = r_type_get_enum (TDB, name);
|
||||
RTypeEnum *member = R_NEW0 (RTypeEnum);
|
||||
RListIter *iter;
|
||||
r_list_foreach (list, iter, member) {
|
||||
sdb_unset (TDB, sdb_fmt ("enum.%s.%s", name, member->name), 0);
|
||||
sdb_unset (TDB, sdb_fmt ("enum.%s.0x%x", name, member->val), 0);
|
||||
}
|
||||
sdb_unset (TDB, name, 0);
|
||||
} else {
|
||||
eprintf ("Unrecognized type \"%s\"\n", kind);
|
||||
}
|
||||
}
|
||||
|
||||
// Function prototypes api
|
||||
R_API int r_type_func_exist(Sdb *TDB, const char *func_name) {
|
||||
const char *fcn = sdb_const_get (TDB, func_name, 0);
|
||||
|
@ -991,8 +991,6 @@ do_decl:
|
||||
if (!strcmp (name, "{")) {
|
||||
// UNNAMED
|
||||
fprintf (stderr, "anonymous enums are ignored\n");
|
||||
} else {
|
||||
tcc_appendf ("%s=enum\n", name);
|
||||
}
|
||||
for (;;) {
|
||||
v = tok;
|
||||
@ -1006,9 +1004,10 @@ do_decl:
|
||||
}
|
||||
if (strcmp (name, "{")) {
|
||||
char *varstr = get_tok_str (v, NULL);
|
||||
// eprintf("%s.%s @ 0x%"PFMT64x"\n", name, varstr, c);
|
||||
tcc_appendf ("%s.%s=0x%"PFMT64x "\n", name, varstr, c);
|
||||
tcc_appendf ("%s.0x%"PFMT64x "=%s\n", name, c, varstr);
|
||||
tcc_appendf ("%s=enum\n", name);
|
||||
tcc_appendf ("[+]enum.%s=%s\n",name, varstr);
|
||||
tcc_appendf ("enum.%s.%s=0x%"PFMT64x "\n", name, varstr, c);
|
||||
tcc_appendf ("enum.%s.0x%"PFMT64x "=%s\n", name, c, varstr);
|
||||
// TODO: if token already defined throw an error
|
||||
// if (varstr isInside (arrayOfvars)) { erprintf ("ERROR: DUP VAR IN ENUM\n"); }
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user