+static void mark_decls_read(expression_t *expr, declaration_t *lhs_decl);
+
+static declaration_t *determine_lhs_decl(expression_t *const expr, declaration_t *lhs_decl)
+{
+ switch (expr->kind) {
+ case EXPR_REFERENCE: {
+ declaration_t *const decl = expr->reference.declaration;
+ return decl;
+ }
+
+ case EXPR_ARRAY_ACCESS: {
+ expression_t *const ref = expr->array_access.array_ref;
+ declaration_t * decl = NULL;
+ if (is_type_array(skip_typeref(revert_automatic_type_conversion(ref)))) {
+ decl = determine_lhs_decl(ref, lhs_decl);
+ lhs_decl = decl;
+ } else {
+ mark_decls_read(expr->select.compound, lhs_decl);
+ }
+ mark_decls_read(expr->array_access.index, lhs_decl);
+ return decl;
+ }
+
+ case EXPR_SELECT: {
+ if (is_type_compound(skip_typeref(expr->base.type))) {
+ return determine_lhs_decl(expr->select.compound, lhs_decl);
+ } else {
+ mark_decls_read(expr->select.compound, lhs_decl);
+ return NULL;
+ }
+ }
+
+ case EXPR_UNARY_DEREFERENCE: {
+ expression_t *const val = expr->unary.value;
+ if (val->kind == EXPR_UNARY_TAKE_ADDRESS) {
+ /* *&x is a NOP */
+ return determine_lhs_decl(val->unary.value, lhs_decl);
+ } else {
+ mark_decls_read(val, NULL);
+ return NULL;
+ }
+ }
+
+ default:
+ mark_decls_read(expr, NULL);
+ return NULL;
+ }
+}
+
+#define DECL_ANY ((declaration_t*)-1)
+
+/**
+ * Mark declarations, which are read. This is used to deted variables, which
+ * are never read.
+ * Example:
+ * x = x + 1;
+ * x is not marked as "read", because it is only read to calculate its own new
+ * value.
+ *
+ * x += y; y += x;
+ * x and y are not detected as "not read", because multiple variables are
+ * involved.
+ */
+static void mark_decls_read(expression_t *const expr, declaration_t *lhs_decl)
+{
+ switch (expr->kind) {
+ case EXPR_REFERENCE: {
+ declaration_t *const decl = expr->reference.declaration;
+ if (lhs_decl != decl && lhs_decl != DECL_ANY)
+ decl->read = true;
+ return;
+ }
+
+ case EXPR_CALL:
+ // TODO respect pure/const
+ mark_decls_read(expr->call.function, NULL);
+ for (call_argument_t *arg = expr->call.arguments; arg != NULL; arg = arg->next) {
+ mark_decls_read(arg->expression, NULL);
+ }
+ return;
+
+ case EXPR_CONDITIONAL:
+ // TODO lhs_decl should depend on whether true/false have an effect
+ mark_decls_read(expr->conditional.condition, NULL);
+ if (expr->conditional.true_expression != NULL)
+ mark_decls_read(expr->conditional.true_expression, lhs_decl);
+ mark_decls_read(expr->conditional.false_expression, lhs_decl);
+ return;
+
+ case EXPR_SELECT:
+ if (lhs_decl == DECL_ANY && !is_type_compound(skip_typeref(expr->base.type)))
+ lhs_decl = NULL;
+ mark_decls_read(expr->select.compound, lhs_decl);
+ return;
+
+ case EXPR_ARRAY_ACCESS: {
+ expression_t *const ref = expr->array_access.array_ref;
+ mark_decls_read(ref, lhs_decl);
+ lhs_decl = determine_lhs_decl(ref, lhs_decl);
+ mark_decls_read(expr->array_access.index, lhs_decl);
+ return;
+ }
+
+ case EXPR_VA_ARG:
+ mark_decls_read(expr->va_arge.ap, lhs_decl);
+ return;
+
+ case EXPR_UNARY_CAST:
+ /* Special case: Use void cast to mark a variable as "read" */
+ if (is_type_atomic(skip_typeref(expr->base.type), ATOMIC_TYPE_VOID))
+ lhs_decl = NULL;
+ goto unary;
+
+ case EXPR_UNARY_DEREFERENCE:
+ if (lhs_decl == DECL_ANY)
+ lhs_decl = NULL;
+ goto unary;
+
+ case EXPR_UNARY_NEGATE:
+ case EXPR_UNARY_PLUS:
+ case EXPR_UNARY_BITWISE_NEGATE:
+ case EXPR_UNARY_NOT:
+ case EXPR_UNARY_TAKE_ADDRESS:
+ case EXPR_UNARY_POSTFIX_INCREMENT:
+ case EXPR_UNARY_POSTFIX_DECREMENT:
+ case EXPR_UNARY_PREFIX_INCREMENT:
+ case EXPR_UNARY_PREFIX_DECREMENT:
+ case EXPR_UNARY_CAST_IMPLICIT:
+ case EXPR_UNARY_ASSUME:
+unary:
+ mark_decls_read(expr->unary.value, lhs_decl);
+ return;
+
+ case EXPR_BINARY_ADD:
+ case EXPR_BINARY_SUB:
+ case EXPR_BINARY_MUL:
+ case EXPR_BINARY_DIV:
+ case EXPR_BINARY_MOD:
+ case EXPR_BINARY_EQUAL:
+ case EXPR_BINARY_NOTEQUAL:
+ case EXPR_BINARY_LESS:
+ case EXPR_BINARY_LESSEQUAL:
+ case EXPR_BINARY_GREATER:
+ case EXPR_BINARY_GREATEREQUAL:
+ case EXPR_BINARY_BITWISE_AND:
+ case EXPR_BINARY_BITWISE_OR:
+ case EXPR_BINARY_BITWISE_XOR:
+ case EXPR_BINARY_LOGICAL_AND:
+ case EXPR_BINARY_LOGICAL_OR:
+ case EXPR_BINARY_SHIFTLEFT:
+ case EXPR_BINARY_SHIFTRIGHT:
+ case EXPR_BINARY_COMMA:
+ case EXPR_BINARY_ISGREATER:
+ case EXPR_BINARY_ISGREATEREQUAL:
+ case EXPR_BINARY_ISLESS:
+ case EXPR_BINARY_ISLESSEQUAL:
+ case EXPR_BINARY_ISLESSGREATER:
+ case EXPR_BINARY_ISUNORDERED:
+ mark_decls_read(expr->binary.left, lhs_decl);
+ mark_decls_read(expr->binary.right, lhs_decl);
+ return;
+
+ case EXPR_BINARY_ASSIGN:
+ case EXPR_BINARY_MUL_ASSIGN:
+ case EXPR_BINARY_DIV_ASSIGN:
+ case EXPR_BINARY_MOD_ASSIGN:
+ case EXPR_BINARY_ADD_ASSIGN:
+ case EXPR_BINARY_SUB_ASSIGN:
+ case EXPR_BINARY_SHIFTLEFT_ASSIGN:
+ case EXPR_BINARY_SHIFTRIGHT_ASSIGN:
+ case EXPR_BINARY_BITWISE_AND_ASSIGN:
+ case EXPR_BINARY_BITWISE_XOR_ASSIGN:
+ case EXPR_BINARY_BITWISE_OR_ASSIGN: {
+ if (lhs_decl == DECL_ANY)
+ lhs_decl = NULL;
+ lhs_decl = determine_lhs_decl(expr->binary.left, lhs_decl);
+ mark_decls_read(expr->binary.right, lhs_decl);
+ return;
+ }
+
+ case EXPR_VA_START:
+ determine_lhs_decl(expr->va_starte.ap, lhs_decl);
+ return;
+
+ case EXPR_UNKNOWN:
+ case EXPR_INVALID:
+ 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 init?
+ case EXPR_SIZEOF:
+ case EXPR_CLASSIFY_TYPE:
+ case EXPR_ALIGNOF:
+ case EXPR_FUNCNAME:
+ case EXPR_BUILTIN_SYMBOL:
+ case EXPR_BUILTIN_CONSTANT_P:
+ case EXPR_BUILTIN_PREFETCH:
+ case EXPR_OFFSETOF:
+ case EXPR_STATEMENT: // TODO
+ case EXPR_LABEL_ADDRESS:
+ case EXPR_BINARY_BUILTIN_EXPECT:
+ return;
+ }
+
+ panic("unhandled expression");
+}
+