diff mbox

[1/3] evaluate: warn on identical exprs around '&&'

Message ID 1314501260-27254-1-git-send-email-chrisf@ijw.co.nz (mailing list archive)
State Rejected, archived
Headers show

Commit Message

Chris Forbes Aug. 28, 2011, 3:14 a.m. UTC
Adds a warning when identical expressions are found on both sides of the '&&' operator. This is a common error resulting from copy & paste.

Excludes identical expressions found while preprocessing, so we don't get upset about #if defined(FOO) && defined(BAR), which happens all the time, and is perfectly valid.

Signed-off-by: Chris Forbes <chrisf@ijw.co.nz>
---
 evaluate.c                                |  133 +++++++++++++++++++++++++++++
 validation/check_identical_exprs_on_and.c |   30 +++++++
 2 files changed, 163 insertions(+), 0 deletions(-)
 create mode 100644 validation/check_identical_exprs_on_and.c

Comments

J. Neuschäfer Aug. 29, 2011, 10:01 a.m. UTC | #1
On Sun, Aug 28, 2011 at 03:14:18PM +1200, Chris Forbes wrote:
> +	case EXPR_BINOP:
> +	case EXPR_COMMA:
> +	case EXPR_COMPARE:
> +	case EXPR_LOGICAL:
> +	case EXPR_ASSIGNMENT:
> +		return expr_equiv(lhs->left, rhs->left) &&
> +			expr_equiv(lhs->right, rhs->right);
[...]
> +	if ((a == b) && (a == b))	/* should warn */
> +		bar();
> +
> +	if ((a == b) && (b == c))	/* should not warn */
> +		bar();

Should it maybe also handle cases like this?

if ((a == b) && (b == a))
	bar();

Thanks,
	Jonathan Neuschäfer
--
To unsubscribe from this list: send the line "unsubscribe linux-sparse" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Josh Triplett Aug. 29, 2011, 10:25 a.m. UTC | #2
On Mon, Aug 29, 2011 at 12:01:06PM +0200, Jonathan Neuschäfer wrote:
> On Sun, Aug 28, 2011 at 03:14:18PM +1200, Chris Forbes wrote:
> > +	case EXPR_BINOP:
> > +	case EXPR_COMMA:
> > +	case EXPR_COMPARE:
> > +	case EXPR_LOGICAL:
> > +	case EXPR_ASSIGNMENT:
> > +		return expr_equiv(lhs->left, rhs->left) &&
> > +			expr_equiv(lhs->right, rhs->right);
> [...]
> > +	if ((a == b) && (a == b))	/* should warn */
> > +		bar();
> > +
> > +	if ((a == b) && (b == c))	/* should not warn */
> > +		bar();
> 
> Should it maybe also handle cases like this?
> 
> if ((a == b) && (b == a))
> 	bar();

That seems both significantly harder to handle and significantly less
likely (since it won't occur due to simple copy/paste).  Probably worth
doing at some point, but I don't think a first pass needs to consider
it.

- Josh Triplett
--
To unsubscribe from this list: send the line "unsubscribe linux-sparse" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/evaluate.c b/evaluate.c
index f7a5678..4914442 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -853,8 +853,141 @@  static struct symbol *evaluate_conditional(struct expression *expr, int iterator
 	return ctype;
 }
 
+static int str_equal(struct string *a, struct string *b)
+{
+	return a->length == b->length &&
+		!memcmp(a->data, b->data, a->length);
+}
+
+static int ident_equal(struct ident *a, struct ident *b)
+{
+	/* only correct when used in the context of expr_equiv.
+		should compare symbols? for general case? */
+	return a->len == b->len &&
+		!memcmp(a->name, b->name, a->len);
+}
+
+/* expr_equiv and expr_list_equiv are mutually recursive */
+static int expr_equiv(struct expression *lhs, struct expression *rhs);
+
+static int expr_list_equiv(struct expression_list *lhs,
+			   struct expression_list *rhs)
+{
+	int l_length = expression_list_size(lhs);
+	int r_length = expression_list_size(rhs);
+	struct expression **l_items, **r_items;
+	int i;
+
+	if (l_length != r_length)
+		return 0;
+
+	l_items = alloca(sizeof( *l_items ) * l_length);
+	r_items = alloca(sizeof( *r_items ) * r_length);
+
+	linearize_ptr_list((struct ptr_list *)lhs, (void **)l_items, l_length);
+	linearize_ptr_list((struct ptr_list *)rhs, (void **)r_items, r_length);
+
+	for (i = 0; i < l_length; i++) {
+		if (!expr_equiv(l_items[i], r_items[i]))
+			return 0;
+	}
+
+	return 1;
+}
+
+int expr_equiv(struct expression *lhs, struct expression *rhs)
+{
+	/* recursively determine if lhs ~ rhs. */
+	if (!lhs || !rhs) return 0;
+
+	if (lhs->type != rhs->type) return 0;
+	if (lhs->flags != rhs->flags) return 0;
+	if (lhs->op != rhs->op) return 0;
+	if (lhs->ctype != rhs->ctype) return 0;
+
+	switch (lhs->type) {
+	case EXPR_VALUE:
+		return lhs->value == rhs->value &&
+			lhs->taint == rhs->taint;
+	case EXPR_FVALUE:
+		return lhs->fvalue == rhs->fvalue;
+	case EXPR_STRING:
+		return lhs->wide == rhs->wide &&
+			str_equal(lhs->string, rhs->string);
+/*	case EXPR_UNOP:	*/	/* this is mentioned in the union, but doesn't
+				actually exist */
+	case EXPR_PREOP:
+	case EXPR_POSTOP:
+		return lhs->op_value == rhs->op_value &&
+			expr_equiv(lhs->unop, rhs->unop);
+	case EXPR_SYMBOL:
+	case EXPR_TYPE:
+		return lhs->symbol == rhs->symbol;
+	case EXPR_BINOP:
+	case EXPR_COMMA:
+	case EXPR_COMPARE:
+	case EXPR_LOGICAL:
+	case EXPR_ASSIGNMENT:
+		return expr_equiv(lhs->left, rhs->left) &&
+			expr_equiv(lhs->right, rhs->right);
+	case EXPR_DEREF:
+		return expr_equiv(lhs->deref, rhs->deref) &&
+			ident_equal(lhs->member, rhs->member);
+	case EXPR_SLICE:
+		return expr_equiv(lhs->base, rhs->base) &&
+			lhs->r_bitpos == rhs->r_bitpos &&
+			lhs->r_nrbits == rhs->r_nrbits;
+	case EXPR_CAST:
+	case EXPR_SIZEOF:
+		return lhs->cast_type == rhs->cast_type &&
+			expr_equiv(lhs->cast_expression,
+				rhs->cast_expression);
+	case EXPR_CONDITIONAL:
+	case EXPR_SELECT:
+		return expr_equiv(lhs->conditional, rhs->conditional) &&
+			expr_equiv(lhs->cond_true, rhs->cond_true) &&
+			expr_equiv(lhs->cond_false, rhs->cond_false);
+	case EXPR_CALL:
+		return expr_equiv(lhs->fn, rhs->fn) &&
+			expr_list_equiv(lhs->args, rhs->args);
+	case EXPR_LABEL:
+		return lhs->label_symbol == rhs->label_symbol;
+	case EXPR_INITIALIZER:
+		return expr_list_equiv(lhs->expr_list, rhs->expr_list);
+	case EXPR_IDENTIFIER:
+		return ident_equal(lhs->expr_ident, rhs->expr_ident) &&
+			lhs->field == rhs->field &&
+			expr_equiv(lhs->ident_expression,
+				rhs->ident_expression);
+	case EXPR_INDEX:
+		return lhs->idx_from == rhs->idx_from &&
+			lhs->idx_to == rhs->idx_to &&
+			expr_equiv(lhs->idx_expression, rhs->idx_expression);
+	case EXPR_POS:
+		return lhs->init_offset == rhs->init_offset &&
+			lhs->init_nr == rhs->init_nr &&
+			expr_equiv(lhs->init_expr, rhs->init_expr);
+	case EXPR_OFFSETOF:
+		return lhs->in == rhs->in &&
+			expr_equiv(lhs->down, rhs->down);
+
+	default:
+		return 0;
+	}
+
+	return 1;
+}
+
 static struct symbol *evaluate_logical(struct expression *expr)
 {
+	if (!preprocessing) {
+		if (expr->op == SPECIAL_LOGICAL_AND &&
+			expr_equiv(expr->left, expr->right)) {
+			warning(expr->pos, "identical expressions on both "
+				"sides of '&&'");
+		}
+	}
+
 	if (!evaluate_conditional(expr->left, 0))
 		return NULL;
 	if (!evaluate_conditional(expr->right, 0))
diff --git a/validation/check_identical_exprs_on_and.c b/validation/check_identical_exprs_on_and.c
new file mode 100644
index 0000000..6560429
--- /dev/null
+++ b/validation/check_identical_exprs_on_and.c
@@ -0,0 +1,30 @@ 
+extern void bar(void);
+
+static void foo(void *a, void *b, void *c)
+{
+	if (a && a)	/* should warn */
+		bar();
+
+	if (a && b)	/* should not warn */
+		bar();
+
+	if ((a == b) && (a == b))	/* should warn */
+		bar();
+
+	if ((a == b) && (b == c))	/* should not warn */
+		bar();
+}
+
+/* should not warn */
+#if defined(BLETCHEROUS_PLATFORM) && defined(BROKEN_VERSION_OF_SAME)
+	/* do something suitably bizarre */
+#endif
+
+/*
+ * check-name: A warning should be issued for identical expressions on both sides of the '&&' operator.
+ *
+ * check-error-start
+check_identical_exprs_on_and.c:5:15: warning: identical expressions on both sides of '&&'
+check_identical_exprs_on_and.c:11:22: warning: identical expressions on both sides of '&&'
+ * check-error-end
+ */