+ /* skip computed gotos */
+ if (goto_statement->expression != NULL)
+ continue;
+
+ label_t *label = goto_statement->label;
+
+ label->used = true;
+ if (label->base.source_position.input_name == NULL) {
+ print_in_function();
+ errorf(&goto_statement->base.source_position,
+ "label '%Y' used but not defined", label->base.symbol);
+ }
+ }
+
+ if (warning.unused_label) {
+ for (const label_statement_t *label_statement = label_first;
+ label_statement != NULL;
+ label_statement = label_statement->next) {
+ label_t *label = label_statement->label;
+
+ if (! label->used) {
+ print_in_function();
+ warningf(&label_statement->base.source_position,
+ "label '%Y' defined but not used", label->base.symbol);
+ }
+ }
+ }
+}
+
+static void warn_unused_decl(entity_t *entity, entity_t *end,
+ char const *const what)
+{
+ for (; entity != NULL; entity = entity->base.next) {
+ if (!is_declaration(entity))
+ continue;
+
+ declaration_t *declaration = &entity->declaration;
+ if (declaration->implicit)
+ continue;
+
+ if (!declaration->used) {
+ print_in_function();
+ warningf(&entity->base.source_position, "%s '%Y' is unused",
+ what, entity->base.symbol);
+ } else if (entity->kind == ENTITY_VARIABLE && !entity->variable.read) {
+ print_in_function();
+ warningf(&entity->base.source_position, "%s '%Y' is never read",
+ what, entity->base.symbol);
+ }
+
+ if (entity == end)
+ break;
+ }
+}
+
+static void check_unused_variables(statement_t *const stmt, void *const env)
+{
+ (void)env;
+
+ switch (stmt->kind) {
+ case STATEMENT_DECLARATION: {
+ declaration_statement_t const *const decls = &stmt->declaration;
+ warn_unused_decl(decls->declarations_begin, decls->declarations_end,
+ "variable");
+ return;
+ }
+
+ case STATEMENT_FOR:
+ warn_unused_decl(stmt->fors.scope.entities, NULL, "variable");
+ return;
+
+ default:
+ return;
+ }
+}
+
+/**
+ * Check declarations of current_function for unused entities.
+ */
+static void check_declarations(void)
+{
+ if (warning.unused_parameter) {
+ const scope_t *scope = ¤t_function->parameters;
+
+ /* do not issue unused warnings for main */
+ if (!is_sym_main(current_function->base.base.symbol)) {
+ warn_unused_decl(scope->entities, NULL, "parameter");
+ }
+ }
+ if (warning.unused_variable) {
+ walk_statements(current_function->statement, check_unused_variables,
+ NULL);
+ }
+}
+
+static int determine_truth(expression_t const* const cond)
+{
+ return
+ !is_constant_expression(cond) ? 0 :
+ fold_constant(cond) != 0 ? 1 :
+ -1;
+}
+
+static bool expression_returns(expression_t const *const expr)
+{
+ switch (expr->kind) {
+ case EXPR_CALL: {
+ expression_t const *const func = expr->call.function;
+ if (func->kind == EXPR_REFERENCE) {
+ entity_t *entity = func->reference.entity;
+ if (entity->kind == ENTITY_FUNCTION
+ && entity->declaration.modifiers & DM_NORETURN)
+ return false;
+ }
+
+ if (!expression_returns(func))
+ return false;
+
+ for (call_argument_t const* arg = expr->call.arguments; arg != NULL; arg = arg->next) {
+ if (!expression_returns(arg->expression))
+ return false;
+ }
+
+ return true;
+ }
+
+ case EXPR_REFERENCE:
+ case EXPR_REFERENCE_ENUM_VALUE:
+ case EXPR_CONST:
+ case EXPR_CHARACTER_CONSTANT:
+ case EXPR_WIDE_CHARACTER_CONSTANT:
+ case EXPR_STRING_LITERAL:
+ case EXPR_WIDE_STRING_LITERAL:
+ case EXPR_COMPOUND_LITERAL: // TODO descend into initialisers
+ case EXPR_LABEL_ADDRESS:
+ case EXPR_CLASSIFY_TYPE:
+ case EXPR_SIZEOF: // TODO handle obscure VLA case
+ case EXPR_ALIGNOF:
+ case EXPR_FUNCNAME:
+ case EXPR_BUILTIN_SYMBOL:
+ case EXPR_BUILTIN_CONSTANT_P:
+ case EXPR_BUILTIN_PREFETCH:
+ case EXPR_OFFSETOF:
+ case EXPR_INVALID:
+ case EXPR_STATEMENT: // TODO implement
+ return true;
+
+ case EXPR_CONDITIONAL:
+ // TODO handle constant expression
+ return
+ expression_returns(expr->conditional.condition) && (
+ expression_returns(expr->conditional.true_expression) ||
+ expression_returns(expr->conditional.false_expression)
+ );
+
+ case EXPR_SELECT:
+ return expression_returns(expr->select.compound);
+
+ case EXPR_ARRAY_ACCESS:
+ return
+ expression_returns(expr->array_access.array_ref) &&
+ expression_returns(expr->array_access.index);
+
+ case EXPR_VA_START:
+ return expression_returns(expr->va_starte.ap);
+
+ case EXPR_VA_ARG:
+ return expression_returns(expr->va_arge.ap);
+
+ EXPR_UNARY_CASES_MANDATORY
+ return expression_returns(expr->unary.value);
+
+ case EXPR_UNARY_THROW:
+ return false;
+
+ EXPR_BINARY_CASES
+ // TODO handle constant lhs of && and ||
+ return
+ expression_returns(expr->binary.left) &&
+ expression_returns(expr->binary.right);
+
+ case EXPR_UNKNOWN:
+ break;
+ }
+
+ panic("unhandled expression");
+}
+
+static bool noreturn_candidate;
+
+static void check_reachable(statement_t *const stmt)
+{
+ if (stmt->base.reachable)
+ return;
+ if (stmt->kind != STATEMENT_DO_WHILE)
+ stmt->base.reachable = true;
+
+ statement_t *last = stmt;
+ statement_t *next;
+ switch (stmt->kind) {
+ case STATEMENT_INVALID:
+ case STATEMENT_EMPTY:
+ case STATEMENT_DECLARATION:
+ case STATEMENT_LOCAL_LABEL:
+ case STATEMENT_ASM:
+ next = stmt->base.next;
+ break;
+
+ case STATEMENT_COMPOUND:
+ next = stmt->compound.statements;
+ break;
+
+ case STATEMENT_RETURN:
+ noreturn_candidate = false;
+ return;
+
+ case STATEMENT_IF: {
+ if_statement_t const* const ifs = &stmt->ifs;
+ int const val = determine_truth(ifs->condition);
+
+ if (val >= 0)
+ check_reachable(ifs->true_statement);
+
+ if (val > 0)
+ return;
+
+ if (ifs->false_statement != NULL) {
+ check_reachable(ifs->false_statement);
+ return;
+ }
+
+ next = stmt->base.next;
+ break;
+ }
+
+ case STATEMENT_SWITCH: {
+ switch_statement_t const *const switchs = &stmt->switchs;
+ expression_t const *const expr = switchs->expression;
+
+ if (is_constant_expression(expr)) {
+ long const val = fold_constant(expr);
+ case_label_statement_t * defaults = NULL;
+ for (case_label_statement_t *i = switchs->first_case; i != NULL; i = i->next) {
+ if (i->expression == NULL) {
+ defaults = i;
+ continue;
+ }
+
+ if (i->first_case <= val && val <= i->last_case) {
+ check_reachable((statement_t*)i);
+ return;
+ }
+ }
+
+ if (defaults != NULL) {
+ check_reachable((statement_t*)defaults);
+ return;
+ }
+ } else {
+ bool has_default = false;
+ for (case_label_statement_t *i = switchs->first_case; i != NULL; i = i->next) {
+ if (i->expression == NULL)
+ has_default = true;
+
+ check_reachable((statement_t*)i);
+ }
+
+ if (has_default)
+ return;
+ }
+
+ next = stmt->base.next;
+ break;
+ }
+
+ case STATEMENT_EXPRESSION: {
+ /* Check for noreturn function call */
+ expression_t const *const expr = stmt->expression.expression;
+ if (!expression_returns(expr))
+ return;
+
+ next = stmt->base.next;
+ break;
+ }
+
+ case STATEMENT_CONTINUE: {
+ statement_t *parent = stmt;
+ for (;;) {
+ parent = parent->base.parent;
+ if (parent == NULL) /* continue not within loop */
+ return;
+
+ next = parent;
+ switch (parent->kind) {
+ case STATEMENT_WHILE: goto continue_while;
+ case STATEMENT_DO_WHILE: goto continue_do_while;
+ case STATEMENT_FOR: goto continue_for;
+
+ default: break;
+ }
+ }
+ }
+
+ case STATEMENT_BREAK: {
+ statement_t *parent = stmt;
+ for (;;) {
+ parent = parent->base.parent;
+ if (parent == NULL) /* break not within loop/switch */
+ return;
+
+ switch (parent->kind) {
+ case STATEMENT_SWITCH:
+ case STATEMENT_WHILE:
+ case STATEMENT_DO_WHILE:
+ case STATEMENT_FOR:
+ last = parent;
+ next = parent->base.next;
+ goto found_break_parent;
+
+ default: break;
+ }
+ }
+found_break_parent:
+ break;
+ }
+
+ case STATEMENT_GOTO:
+ if (stmt->gotos.expression) {
+ statement_t *parent = stmt->base.parent;
+ if (parent == NULL) /* top level goto */
+ return;
+ next = parent;
+ } else {
+ next = stmt->gotos.label->statement;
+ if (next == NULL) /* missing label */
+ return;
+ }
+ break;
+
+ case STATEMENT_LABEL:
+ next = stmt->label.statement;
+ break;
+
+ case STATEMENT_CASE_LABEL:
+ next = stmt->case_label.statement;
+ break;
+
+ case STATEMENT_WHILE: {
+ while_statement_t const *const whiles = &stmt->whiles;
+ int const val = determine_truth(whiles->condition);
+
+ if (val >= 0)
+ check_reachable(whiles->body);
+
+ if (val > 0)
+ return;
+
+ next = stmt->base.next;
+ break;
+ }
+
+ case STATEMENT_DO_WHILE:
+ next = stmt->do_while.body;
+ break;
+
+ case STATEMENT_FOR: {
+ for_statement_t *const fors = &stmt->fors;
+
+ if (fors->condition_reachable)
+ return;
+ fors->condition_reachable = true;
+
+ expression_t const *const cond = fors->condition;
+ int const val =
+ cond == NULL ? 1 : determine_truth(cond);
+
+ if (val >= 0)
+ check_reachable(fors->body);
+
+ if (val > 0)
+ return;
+
+ next = stmt->base.next;
+ break;
+ }
+
+ case STATEMENT_MS_TRY: {
+ ms_try_statement_t const *const ms_try = &stmt->ms_try;
+ check_reachable(ms_try->try_statement);
+ next = ms_try->final_statement;
+ break;
+ }
+
+ case STATEMENT_LEAVE: {
+ statement_t *parent = stmt;
+ for (;;) {
+ parent = parent->base.parent;
+ if (parent == NULL) /* __leave not within __try */
+ return;
+
+ if (parent->kind == STATEMENT_MS_TRY) {
+ last = parent;
+ next = parent->ms_try.final_statement;
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ while (next == NULL) {
+ next = last->base.parent;
+ if (next == NULL) {
+ noreturn_candidate = false;
+
+ type_t *const type = current_function->base.type;
+ assert(is_type_function(type));
+ type_t *const ret = skip_typeref(type->function.return_type);
+ if (warning.return_type &&
+ !is_type_atomic(ret, ATOMIC_TYPE_VOID) &&
+ is_type_valid(ret) &&
+ !is_sym_main(current_function->base.base.symbol)) {
+ warningf(&stmt->base.source_position,
+ "control reaches end of non-void function");
+ }
+ return;
+ }
+
+ switch (next->kind) {
+ case STATEMENT_INVALID:
+ case STATEMENT_EMPTY:
+ case STATEMENT_DECLARATION:
+ case STATEMENT_LOCAL_LABEL:
+ case STATEMENT_EXPRESSION:
+ case STATEMENT_ASM:
+ case STATEMENT_RETURN:
+ case STATEMENT_CONTINUE:
+ case STATEMENT_BREAK:
+ case STATEMENT_GOTO:
+ case STATEMENT_LEAVE:
+ panic("invalid control flow in function");
+
+ case STATEMENT_COMPOUND:
+ case STATEMENT_IF:
+ case STATEMENT_SWITCH:
+ case STATEMENT_LABEL:
+ case STATEMENT_CASE_LABEL:
+ last = next;
+ next = next->base.next;
+ break;
+
+ case STATEMENT_WHILE: {
+continue_while:
+ if (next->base.reachable)
+ return;
+ next->base.reachable = true;
+
+ while_statement_t const *const whiles = &next->whiles;
+ int const val = determine_truth(whiles->condition);
+
+ if (val >= 0)
+ check_reachable(whiles->body);
+
+ if (val > 0)
+ return;
+
+ last = next;
+ next = next->base.next;
+ break;
+ }
+
+ case STATEMENT_DO_WHILE: {
+continue_do_while:
+ if (next->base.reachable)
+ return;
+ next->base.reachable = true;
+
+ do_while_statement_t const *const dw = &next->do_while;
+ int const val = determine_truth(dw->condition);
+
+ if (val >= 0)
+ check_reachable(dw->body);
+
+ if (val > 0)
+ return;
+
+ last = next;
+ next = next->base.next;
+ break;
+ }
+
+ case STATEMENT_FOR: {
+continue_for:;
+ for_statement_t *const fors = &next->fors;
+
+ fors->step_reachable = true;
+
+ if (fors->condition_reachable)
+ return;
+ fors->condition_reachable = true;
+
+ expression_t const *const cond = fors->condition;
+ int const val =
+ cond == NULL ? 1 : determine_truth(cond);