| /* |
| * sparse/dissect.c |
| * |
| * Started by Oleg Nesterov <oleg@tv-sign.ru> |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy |
| * of this software and associated documentation files (the "Software"), to deal |
| * in the Software without restriction, including without limitation the rights |
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| * copies of the Software, and to permit persons to whom the Software is |
| * furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * 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 OR COPYRIGHT HOLDERS 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. |
| */ |
| |
| #include "dissect.h" |
| |
| #define U_VOID 0x00 |
| #define U_SELF ((1 << U_SHIFT) - 1) |
| #define U_MASK (U_R_VAL | U_W_VAL | U_R_AOF) |
| |
| #define DO_LIST(l__, p__, expr__) \ |
| do { \ |
| typeof(l__->list[0]) p__; \ |
| FOR_EACH_PTR(l__, p__) \ |
| expr__; \ |
| END_FOR_EACH_PTR(p__); \ |
| } while (0) |
| |
| #define DO_2_LIST(l1__,l2__, p1__,p2__, expr__) \ |
| do { \ |
| typeof(l1__->list[0]) p1__; \ |
| typeof(l2__->list[0]) p2__; \ |
| PREPARE_PTR_LIST(l1__, p1__); \ |
| FOR_EACH_PTR(l2__, p2__) \ |
| expr__; \ |
| NEXT_PTR_LIST(p1__); \ |
| END_FOR_EACH_PTR(p2__); \ |
| FINISH_PTR_LIST(p1__); \ |
| } while (0) |
| |
| |
| typedef unsigned usage_t; |
| |
| static struct reporter *reporter; |
| static struct symbol *return_type; |
| |
| static void do_sym_list(struct symbol_list *list); |
| |
| static struct symbol |
| *base_type(struct symbol *sym), |
| *do_initializer(struct symbol *type, struct expression *expr), |
| *do_expression(usage_t mode, struct expression *expr), |
| *do_statement(usage_t mode, struct statement *stmt); |
| |
| static inline int is_ptr(struct symbol *type) |
| { |
| return type->type == SYM_PTR || type->type == SYM_ARRAY; |
| } |
| |
| static inline usage_t u_rval(usage_t mode) |
| { |
| return mode & (U_R_VAL | (U_MASK << U_SHIFT)) |
| ? U_R_VAL : 0; |
| } |
| |
| static inline usage_t u_addr(usage_t mode) |
| { |
| return mode = mode & U_MASK |
| ? U_R_AOF | (mode & U_W_AOF) : 0; |
| } |
| |
| static usage_t u_lval(struct symbol *type) |
| { |
| int wptr = is_ptr(type) && !(type->ctype.modifiers & MOD_CONST); |
| return wptr || type == &bad_ctype |
| ? U_W_AOF | U_R_VAL : U_R_VAL; |
| } |
| |
| static usage_t fix_mode(struct symbol *type, usage_t mode) |
| { |
| mode &= (U_SELF | (U_SELF << U_SHIFT)); |
| |
| switch (type->type) { |
| case SYM_BASETYPE: |
| if (!type->ctype.base_type) |
| break; |
| case SYM_ENUM: |
| case SYM_BITFIELD: |
| if (mode & U_MASK) |
| mode &= U_SELF; |
| default: |
| |
| break; case SYM_FN: |
| if (mode & U_R_VAL) |
| mode |= U_R_AOF; |
| mode &= ~(U_R_VAL | U_W_AOF); |
| |
| break; case SYM_ARRAY: |
| if (mode & (U_MASK << U_SHIFT)) |
| mode >>= U_SHIFT; |
| else if (mode != U_W_VAL) |
| mode = u_addr(mode); |
| } |
| |
| if (!(mode & U_R_AOF)) |
| mode &= ~U_W_AOF; |
| |
| return mode; |
| } |
| |
| static inline struct symbol *no_member(struct ident *name) |
| { |
| static struct symbol sym = { |
| .type = SYM_BAD, |
| }; |
| |
| sym.ctype.base_type = &bad_ctype; |
| sym.ident = name; |
| |
| return &sym; |
| } |
| |
| static struct symbol *report_member(mode_t mode, struct position *pos, |
| struct symbol *type, struct symbol *mem) |
| { |
| struct symbol *ret = mem->ctype.base_type; |
| |
| if (reporter->r_member) |
| reporter->r_member(fix_mode(ret, mode), pos, type, mem); |
| |
| return ret; |
| } |
| |
| static void report_implicit(usage_t mode, struct position *pos, struct symbol *type) |
| { |
| if (type->type != SYM_STRUCT && type->type != SYM_UNION) |
| return; |
| |
| if (!reporter->r_member) |
| return; |
| |
| if (type->ident != NULL) |
| reporter->r_member(mode, pos, type, NULL); |
| |
| DO_LIST(type->symbol_list, mem, |
| report_implicit(mode, pos, base_type(mem))); |
| } |
| |
| static inline struct symbol *expr_symbol(struct expression *expr) |
| { |
| struct symbol *sym = expr->symbol; |
| |
| if (!sym) { |
| sym = lookup_symbol(expr->symbol_name, NS_SYMBOL); |
| |
| if (!sym) { |
| sym = alloc_symbol(expr->pos, SYM_BAD); |
| bind_symbol(sym, expr->symbol_name, NS_SYMBOL); |
| sym->ctype.modifiers = MOD_EXTERN; |
| } |
| } |
| |
| if (!sym->ctype.base_type) |
| sym->ctype.base_type = &bad_ctype; |
| |
| return sym; |
| } |
| |
| static struct symbol *report_symbol(usage_t mode, struct expression *expr) |
| { |
| struct symbol *sym = expr_symbol(expr); |
| struct symbol *ret = base_type(sym); |
| |
| if (0 && ret->type == SYM_ENUM) |
| return report_member(mode, &expr->pos, ret, expr->symbol); |
| |
| if (reporter->r_symbol) |
| reporter->r_symbol(fix_mode(ret, mode), &expr->pos, sym); |
| |
| return ret; |
| } |
| |
| static inline struct ident *mk_name(struct ident *root, struct ident *node) |
| { |
| char name[256]; |
| |
| snprintf(name, sizeof(name), "%.*s:%.*s", |
| root ? root->len : 0, root ? root->name : "", |
| node ? node->len : 0, node ? node->name : ""); |
| |
| return built_in_ident(name); |
| } |
| |
| static void examine_sym_node(struct symbol *node, struct ident *root) |
| { |
| struct symbol *base; |
| struct ident *name; |
| |
| if (node->examined) |
| return; |
| |
| node->examined = 1; |
| name = node->ident; |
| |
| while ((base = node->ctype.base_type) != NULL) |
| switch (base->type) { |
| case SYM_TYPEOF: |
| node->ctype.base_type = |
| do_expression(U_VOID, base->initializer); |
| break; |
| |
| case SYM_ARRAY: |
| do_expression(U_R_VAL, base->array_size); |
| case SYM_PTR: case SYM_FN: |
| node = base; |
| break; |
| |
| case SYM_STRUCT: case SYM_UNION: //case SYM_ENUM: |
| if (base->evaluated) |
| return; |
| if (!base->symbol_list) |
| return; |
| base->evaluated = 1; |
| |
| if (!base->ident && name) |
| base->ident = mk_name(root, name); |
| if (base->ident && reporter->r_symdef) |
| reporter->r_symdef(base); |
| DO_LIST(base->symbol_list, mem, |
| examine_sym_node(mem, base->ident ?: root)); |
| default: |
| return; |
| } |
| } |
| |
| static struct symbol *base_type(struct symbol *sym) |
| { |
| if (!sym) |
| return &bad_ctype; |
| |
| if (sym->type == SYM_NODE) |
| examine_sym_node(sym, NULL); |
| |
| return sym->ctype.base_type // builtin_fn_type |
| ?: &bad_ctype; |
| } |
| |
| static struct symbol *__lookup_member(struct symbol *type, struct ident *name, int *p_addr) |
| { |
| struct symbol *node; |
| int addr = 0; |
| |
| FOR_EACH_PTR(type->symbol_list, node) |
| if (!name) { |
| if (addr == *p_addr) |
| return node; |
| } |
| else if (node->ident == NULL) { |
| node = __lookup_member(node->ctype.base_type, name, NULL); |
| if (node) |
| goto found; |
| } |
| else if (node->ident == name) { |
| found: |
| if (p_addr) |
| *p_addr = addr; |
| return node; |
| } |
| addr++; |
| END_FOR_EACH_PTR(node); |
| |
| return NULL; |
| } |
| |
| static struct symbol *lookup_member(struct symbol *type, struct ident *name, int *addr) |
| { |
| return __lookup_member(type, name, addr) |
| ?: no_member(name); |
| } |
| |
| static struct expression *peek_preop(struct expression *expr, int op) |
| { |
| do { |
| if (expr->type != EXPR_PREOP) |
| break; |
| if (expr->op == op) |
| return expr->unop; |
| if (expr->op == '(') |
| expr = expr->unop; |
| else |
| break; |
| } while (expr); |
| |
| return NULL; |
| } |
| |
| static struct symbol *do_expression(usage_t mode, struct expression *expr) |
| { |
| struct symbol *ret = &int_ctype; |
| |
| again: |
| if (expr) switch (expr->type) { |
| default: |
| warning(expr->pos, "bad expr->type: %d", expr->type); |
| |
| case EXPR_TYPE: // [struct T]; Why ??? |
| case EXPR_VALUE: |
| case EXPR_FVALUE: |
| |
| break; case EXPR_LABEL: |
| ret = &label_ctype; |
| |
| break; case EXPR_STRING: |
| ret = &string_ctype; |
| |
| break; case EXPR_STATEMENT: |
| ret = do_statement(mode, expr->statement); |
| |
| break; case EXPR_SIZEOF: case EXPR_ALIGNOF: case EXPR_PTRSIZEOF: |
| do_expression(U_VOID, expr->cast_expression); |
| |
| break; case EXPR_COMMA: |
| do_expression(U_VOID, expr->left); |
| ret = do_expression(mode, expr->right); |
| |
| break; case EXPR_CAST: case EXPR_FORCE_CAST: //case EXPR_IMPLIED_CAST: |
| ret = base_type(expr->cast_type); |
| do_initializer(ret, expr->cast_expression); |
| |
| break; case EXPR_COMPARE: case EXPR_LOGICAL: |
| mode = u_rval(mode); |
| do_expression(mode, expr->left); |
| do_expression(mode, expr->right); |
| |
| break; case EXPR_CONDITIONAL: //case EXPR_SELECT: |
| do_expression(expr->cond_true |
| ? U_R_VAL : U_R_VAL | mode, |
| expr->conditional); |
| ret = do_expression(mode, expr->cond_true); |
| ret = do_expression(mode, expr->cond_false); |
| |
| break; case EXPR_CALL: |
| ret = do_expression(U_R_PTR, expr->fn); |
| if (is_ptr(ret)) |
| ret = ret->ctype.base_type; |
| DO_2_LIST(ret->arguments, expr->args, arg, val, |
| do_expression(u_lval(base_type(arg)), val)); |
| ret = ret->type == SYM_FN ? base_type(ret) |
| : &bad_ctype; |
| |
| break; case EXPR_ASSIGNMENT: |
| mode |= U_W_VAL | U_R_VAL; |
| if (expr->op == '=') |
| mode &= ~U_R_VAL; |
| ret = do_expression(mode, expr->left); |
| report_implicit(mode, &expr->pos, ret); |
| mode = expr->op == '=' |
| ? u_lval(ret) : U_R_VAL; |
| do_expression(mode, expr->right); |
| |
| break; case EXPR_BINOP: { |
| struct symbol *l, *r; |
| mode |= u_rval(mode); |
| l = do_expression(mode, expr->left); |
| r = do_expression(mode, expr->right); |
| if (expr->op != '+' && expr->op != '-') |
| ; |
| else if (!is_ptr_type(r)) |
| ret = l; |
| else if (!is_ptr_type(l)) |
| ret = r; |
| } |
| |
| break; case EXPR_PREOP: case EXPR_POSTOP: { |
| struct expression *unop = expr->unop; |
| |
| switch (expr->op) { |
| case SPECIAL_INCREMENT: |
| case SPECIAL_DECREMENT: |
| mode |= U_W_VAL | U_R_VAL; |
| default: |
| mode |= u_rval(mode); |
| case '(': |
| ret = do_expression(mode, unop); |
| |
| break; case '&': |
| if ((expr = peek_preop(unop, '*'))) |
| goto again; |
| ret = alloc_symbol(unop->pos, SYM_PTR); |
| ret->ctype.base_type = |
| do_expression(u_addr(mode), unop); |
| |
| break; case '*': |
| if ((expr = peek_preop(unop, '&'))) |
| goto again; |
| if (mode & (U_MASK << U_SHIFT)) |
| mode |= U_R_VAL; |
| mode <<= U_SHIFT; |
| if (mode & (U_R_AOF << U_SHIFT)) |
| mode |= U_R_VAL; |
| if (mode & (U_W_VAL << U_SHIFT)) |
| mode |= U_W_AOF; |
| ret = do_expression(mode, unop); |
| ret = is_ptr(ret) ? base_type(ret) |
| : &bad_ctype; |
| } |
| } |
| |
| break; case EXPR_DEREF: { |
| struct symbol *p_type; |
| usage_t p_mode; |
| |
| p_mode = mode & U_SELF; |
| if (!(mode & U_MASK) && (mode & (U_MASK << U_SHIFT))) |
| p_mode = U_R_VAL; |
| p_type = do_expression(p_mode, expr->deref); |
| |
| ret = report_member(mode, &expr->pos, p_type, |
| lookup_member(p_type, expr->member, NULL)); |
| } |
| |
| break; case EXPR_SYMBOL: |
| ret = report_symbol(mode, expr); |
| } |
| |
| return ret; |
| } |
| |
| static void do_asm_xputs(usage_t mode, struct expression_list *xputs) |
| { |
| int nr = 0; |
| |
| DO_LIST(xputs, expr, |
| if (++nr % 3 == 0) |
| do_expression(U_W_AOF | mode, expr)); |
| } |
| |
| static struct symbol *do_statement(usage_t mode, struct statement *stmt) |
| { |
| struct symbol *ret = &void_ctype; |
| |
| if (stmt) switch (stmt->type) { |
| default: |
| warning(stmt->pos, "bad stmt->type: %d", stmt->type); |
| |
| case STMT_NONE: |
| case STMT_RANGE: |
| case STMT_CONTEXT: |
| |
| break; case STMT_DECLARATION: |
| do_sym_list(stmt->declaration); |
| |
| break; case STMT_EXPRESSION: |
| ret = do_expression(mode, stmt->expression); |
| |
| break; case STMT_RETURN: |
| do_expression(u_lval(return_type), stmt->expression); |
| |
| break; case STMT_ASM: |
| do_expression(U_R_VAL, stmt->asm_string); |
| do_asm_xputs(U_W_VAL, stmt->asm_outputs); |
| do_asm_xputs(U_R_VAL, stmt->asm_inputs); |
| |
| break; case STMT_COMPOUND: { |
| int count; |
| |
| count = statement_list_size(stmt->stmts); |
| DO_LIST(stmt->stmts, st, |
| ret = do_statement(--count ? U_VOID : mode, st)); |
| } |
| |
| break; case STMT_ITERATOR: |
| do_sym_list(stmt->iterator_syms); |
| do_statement(U_VOID, stmt->iterator_pre_statement); |
| do_expression(U_R_VAL, stmt->iterator_pre_condition); |
| do_statement(U_VOID, stmt->iterator_post_statement); |
| do_statement(U_VOID, stmt->iterator_statement); |
| do_expression(U_R_VAL, stmt->iterator_post_condition); |
| |
| break; case STMT_IF: |
| do_expression(U_R_VAL, stmt->if_conditional); |
| do_statement(U_VOID, stmt->if_true); |
| do_statement(U_VOID, stmt->if_false); |
| |
| break; case STMT_SWITCH: |
| do_expression(U_R_VAL, stmt->switch_expression); |
| do_statement(U_VOID, stmt->switch_statement); |
| |
| break; case STMT_CASE: |
| do_expression(U_R_VAL, stmt->case_expression); |
| do_expression(U_R_VAL, stmt->case_to); |
| do_statement(U_VOID, stmt->case_statement); |
| |
| break; case STMT_GOTO: |
| do_expression(U_R_PTR, stmt->goto_expression); |
| |
| break; case STMT_LABEL: |
| do_statement(mode, stmt->label_statement); |
| |
| } |
| |
| return ret; |
| } |
| |
| static struct symbol *do_initializer(struct symbol *type, struct expression *expr) |
| { |
| struct symbol *m_type; |
| struct expression *m_expr; |
| int m_addr; |
| |
| if (expr) switch (expr->type) { |
| default: |
| do_expression(u_lval(type), expr); |
| |
| break; case EXPR_INDEX: |
| do_initializer(base_type(type), expr->idx_expression); |
| |
| break; case EXPR_INITIALIZER: |
| m_addr = 0; |
| FOR_EACH_PTR(expr->expr_list, m_expr) |
| if (type->type == SYM_ARRAY) { |
| m_type = base_type(type); |
| if (m_expr->type == EXPR_INDEX) |
| m_expr = m_expr->idx_expression; |
| } else { |
| struct position *pos = &m_expr->pos; |
| struct ident *m_name = NULL; |
| |
| if (m_expr->type == EXPR_IDENTIFIER) { |
| m_name = m_expr->expr_ident; |
| m_expr = m_expr->ident_expression; |
| } |
| |
| m_type = report_member(U_W_VAL, pos, type, |
| lookup_member(type, m_name, &m_addr)); |
| if (m_expr->type != EXPR_INITIALIZER) |
| report_implicit(U_W_VAL, pos, m_type); |
| } |
| do_initializer(m_type, m_expr); |
| m_addr++; |
| END_FOR_EACH_PTR(m_expr); |
| } |
| |
| return type; |
| } |
| |
| static inline struct symbol *do_symbol(struct symbol *sym) |
| { |
| struct symbol *type; |
| |
| type = base_type(sym); |
| |
| if (reporter->r_symdef) |
| reporter->r_symdef(sym); |
| |
| switch (type->type) { |
| default: |
| if (!sym->initializer) |
| break; |
| if (reporter->r_symbol) |
| reporter->r_symbol(U_W_VAL, &sym->pos, sym); |
| do_initializer(type, sym->initializer); |
| |
| break; case SYM_FN: |
| do_sym_list(type->arguments); |
| return_type = base_type(type); |
| do_statement(U_VOID, sym->ctype.modifiers & MOD_INLINE |
| ? type->inline_stmt |
| : type->stmt); |
| } |
| |
| return type; |
| } |
| |
| static void do_sym_list(struct symbol_list *list) |
| { |
| DO_LIST(list, sym, do_symbol(sym)); |
| } |
| |
| void dissect(struct symbol_list *list, struct reporter *rep) |
| { |
| reporter = rep; |
| do_sym_list(list); |
| } |