+static void mark_vars_read(expression_t *expr, variable_t *lhs_var);
+
+static variable_t *determine_lhs_var(expression_t *const expr,
+ variable_t *lhs_var)
+{
+ switch (expr->kind) {
+ case EXPR_REFERENCE: {
+ entity_t *const entity = expr->reference.entity;
+ /* we should only find variables as lavlues... */
+ if (entity->base.kind != ENTITY_VARIABLE)
+ return NULL;
+
+ return &entity->variable;
+ }
+
+ case EXPR_ARRAY_ACCESS: {
+ expression_t *const ref = expr->array_access.array_ref;
+ variable_t * var = NULL;
+ if (is_type_array(skip_typeref(revert_automatic_type_conversion(ref)))) {
+ var = determine_lhs_var(ref, lhs_var);
+ lhs_var = var;
+ } else {
+ mark_vars_read(expr->select.compound, lhs_var);
+ }
+ mark_vars_read(expr->array_access.index, lhs_var);
+ return var;
+ }
+
+ case EXPR_SELECT: {
+ if (is_type_compound(skip_typeref(expr->base.type))) {
+ return determine_lhs_var(expr->select.compound, lhs_var);
+ } else {
+ mark_vars_read(expr->select.compound, lhs_var);
+ 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_var(val->unary.value, lhs_var);
+ } else {
+ mark_vars_read(val, NULL);
+ return NULL;
+ }
+ }
+
+ default:
+ mark_vars_read(expr, NULL);
+ return NULL;
+ }
+}
+
+#define VAR_ANY ((variable_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_vars_read(expression_t *const expr, variable_t *lhs_var)
+{
+ switch (expr->kind) {
+ case EXPR_REFERENCE: {
+ entity_t *const entity = expr->reference.entity;
+ if (entity->kind != ENTITY_VARIABLE)
+ return;
+
+ variable_t *variable = &entity->variable;
+ if (lhs_var != variable && lhs_var != VAR_ANY) {
+ variable->read = true;
+ }
+ return;
+ }
+
+ case EXPR_CALL:
+ // TODO respect pure/const
+ mark_vars_read(expr->call.function, NULL);
+ for (call_argument_t *arg = expr->call.arguments; arg != NULL; arg = arg->next) {
+ mark_vars_read(arg->expression, NULL);
+ }
+ return;
+
+ case EXPR_CONDITIONAL:
+ // TODO lhs_decl should depend on whether true/false have an effect
+ mark_vars_read(expr->conditional.condition, NULL);
+ if (expr->conditional.true_expression != NULL)
+ mark_vars_read(expr->conditional.true_expression, lhs_var);
+ mark_vars_read(expr->conditional.false_expression, lhs_var);
+ return;
+
+ case EXPR_SELECT:
+ if (lhs_var == VAR_ANY && !is_type_compound(skip_typeref(expr->base.type)))
+ lhs_var = NULL;
+ mark_vars_read(expr->select.compound, lhs_var);
+ return;
+
+ case EXPR_ARRAY_ACCESS: {
+ expression_t *const ref = expr->array_access.array_ref;
+ mark_vars_read(ref, lhs_var);
+ lhs_var = determine_lhs_var(ref, lhs_var);
+ mark_vars_read(expr->array_access.index, lhs_var);
+ return;
+ }
+
+ case EXPR_VA_ARG:
+ mark_vars_read(expr->va_arge.ap, lhs_var);
+ 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_var = NULL;
+ goto unary;
+
+
+ case EXPR_UNARY_THROW:
+ if (expr->unary.value == NULL)
+ return;
+ /* FALLTHROUGH */
+ case EXPR_UNARY_DEREFERENCE:
+ case EXPR_UNARY_DELETE:
+ case EXPR_UNARY_DELETE_ARRAY:
+ if (lhs_var == VAR_ANY)
+ lhs_var = 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_vars_read(expr->unary.value, lhs_var);
+ 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_vars_read(expr->binary.left, lhs_var);
+ mark_vars_read(expr->binary.right, lhs_var);
+ 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_var == VAR_ANY)
+ lhs_var = NULL;
+ lhs_var = determine_lhs_var(expr->binary.left, lhs_var);
+ mark_vars_read(expr->binary.right, lhs_var);
+ return;
+ }
+
+ case EXPR_VA_START:
+ determine_lhs_var(expr->va_starte.ap, lhs_var);
+ 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:
+ case EXPR_REFERENCE_ENUM_VALUE:
+ return;
+ }
+
+ panic("unhandled expression");
+}
+