--- old/usr/src/tools/smatch/src/smatch_comparison.c	Mon Nov 18 13:19:09 2019
+++ new/usr/src/tools/smatch/src/smatch_comparison.c	Mon Nov 18 13:19:09 2019
@@ -57,6 +57,15 @@
 	return vs->sym;
 }
 
+static const char *show_comparison(int comparison)
+{
+	if (comparison == IMPOSSIBLE_COMPARISON)
+		return "impossible";
+	if (comparison == UNKNOWN_COMPARISON)
+		return "unknown";
+	return show_special(comparison);
+}
+
 struct smatch_state *alloc_compare_state(
 		struct expression *left,
 		const char *left_var, struct var_sym_list *left_vsl,
@@ -68,7 +77,7 @@
 	struct compare_data *data;
 
 	state = __alloc_smatch_state(0);
-	state->name = alloc_sname(show_special(comparison));
+	state->name = alloc_sname(show_comparison(comparison));
 	data = __alloc_compare_data(0);
 	data->left = left;
 	data->left_var = alloc_sname(left_var);
@@ -84,7 +93,7 @@
 int state_to_comparison(struct smatch_state *state)
 {
 	if (!state || !state->data)
-		return 0;
+		return UNKNOWN_COMPARISON;
 	return ((struct compare_data *)state->data)->comparison;
 }
 
@@ -94,8 +103,8 @@
 int flip_comparison(int op)
 {
 	switch (op) {
-	case 0:
-		return 0;
+	case UNKNOWN_COMPARISON:
+		return UNKNOWN_COMPARISON;
 	case '<':
 		return '>';
 	case SPECIAL_UNSIGNED_LT:
@@ -116,6 +125,8 @@
 		return '<';
 	case SPECIAL_UNSIGNED_GT:
 		return SPECIAL_UNSIGNED_LT;
+	case IMPOSSIBLE_COMPARISON:
+		return UNKNOWN_COMPARISON;
 	default:
 		sm_perror("unhandled comparison %d", op);
 		return op;
@@ -125,8 +136,8 @@
 int negate_comparison(int op)
 {
 	switch (op) {
-	case 0:
-		return 0;
+	case UNKNOWN_COMPARISON:
+		return UNKNOWN_COMPARISON;
 	case '<':
 		return SPECIAL_GTE;
 	case SPECIAL_UNSIGNED_LT:
@@ -147,6 +158,8 @@
 		return SPECIAL_LTE;
 	case SPECIAL_UNSIGNED_GT:
 		return SPECIAL_UNSIGNED_LTE;
+	case IMPOSSIBLE_COMPARISON:
+		return UNKNOWN_COMPARISON;
 	default:
 		sm_perror("unhandled comparison %d", op);
 		return op;
@@ -159,7 +172,7 @@
 	struct symbol *type = &int_ctype;
 
 	if (!left_rl || !right_rl)
-		return 0;
+		return UNKNOWN_COMPARISON;
 
 	if (type_positive_bits(rl_type(left_rl)) > type_positive_bits(type))
 		type = rl_type(left_rl);
@@ -188,7 +201,7 @@
 	if (sval_cmp(left_min, right_max) == 0)
 		return SPECIAL_GTE;
 
-	return 0;
+	return UNKNOWN_COMPARISON;
 }
 
 static int comparison_from_extra(struct expression *a, struct expression *b)
@@ -196,9 +209,9 @@
 	struct range_list *left, *right;
 
 	if (!get_implied_rl(a, &left))
-		return 0;
+		return UNKNOWN_COMPARISON;
 	if (!get_implied_rl(b, &right))
-		return 0;
+		return UNKNOWN_COMPARISON;
 
 	return rl_comparison(left, right);
 }
@@ -221,29 +234,32 @@
 {
 	struct compare_data *data = sm->state->data;
 	struct range_list *left_rl, *right_rl;
-	int op;
+	int op = UNKNOWN_COMPARISON;
 
 	if (!data)
 		return &undefined;
 
+	if (is_impossible_path()) {
+		op = IMPOSSIBLE_COMPARISON;
+		goto alloc;
+	}
+
 	if (strstr(data->left_var, " orig"))
 		left_rl = get_orig_rl(data->left_vsl);
 	else if (!get_implied_rl_var_sym(data->left_var, vsl_to_sym(data->left_vsl), &left_rl))
-		return &undefined;
+		goto alloc;
 
 	if (strstr(data->right_var, " orig"))
 		right_rl = get_orig_rl(data->right_vsl);
 	else if (!get_implied_rl_var_sym(data->right_var, vsl_to_sym(data->right_vsl), &right_rl))
-		return &undefined;
+		goto alloc;
 
 	op = rl_comparison(left_rl, right_rl);
-	if (op)
-		return alloc_compare_state(
-				data->left, data->left_var, data->left_vsl,
-				op,
-				data->right, data->right_var, data->right_vsl);
 
-	return &undefined;
+alloc:
+	return alloc_compare_state(data->left, data->left_var, data->left_vsl,
+				   op,
+				   data->right, data->right_var, data->right_vsl);
 }
 
 /* remove_unsigned_from_comparison() is obviously a hack. */
@@ -271,9 +287,14 @@
 {
 	int LT, EQ, GT;
 
-	if (!one || !two)
-		return 0;
+	if (one == UNKNOWN_COMPARISON || two == UNKNOWN_COMPARISON)
+		return UNKNOWN_COMPARISON;
 
+	if (one == IMPOSSIBLE_COMPARISON)
+		return two;
+	if (two == IMPOSSIBLE_COMPARISON)
+		return one;
+
 	one = remove_unsigned_from_comparison(one);
 	two = remove_unsigned_from_comparison(two);
 
@@ -321,7 +342,7 @@
 	}
 
 	if (LT && EQ && GT)
-		return 0;
+		return UNKNOWN_COMPARISON;
 	if (LT && EQ)
 		return SPECIAL_LTE;
 	if (LT && GT)
@@ -332,17 +353,13 @@
 		return SPECIAL_GTE;
 	if (GT)
 		return '>';
-	return 0;
+	return UNKNOWN_COMPARISON;
 }
 
 /*
- * This is for if you have "a < b" and "b <= c".  Or in other words,
- * "a < b <= c".  You would call this like get_combined_comparison('<', '<=').
+ * This is for if you have "a < b" and "b <= c" and you want to see how "a
+ * compares to c".  You would call this like get_combined_comparison('<', '<=').
  * The return comparison would be '<'.
- *
- * This function is different from merge_comparisons(), for example:
- * merge_comparison('<', '==') returns '<='
- * get_combined_comparison('<', '==') returns '<'
  */
 int combine_comparisons(int left_compare, int right_compare)
 {
@@ -400,144 +417,148 @@
 			return SPECIAL_GTE;
 		return '>';
 	}
-	return 0;
+	return UNKNOWN_COMPARISON;
 }
 
-int filter_comparison(int orig, int op)
+/*
+ * This is mostly used when you know from extra state that a <= b but you
+ * know from comparisons that a != b so then if take the intersection then
+ * we know that a < b.  The name is taken from the fact that the intersection
+ * of < and <= is <.
+ */
+int comparison_intersection(int left_compare, int right_compare)
 {
-	if (orig == op)
-		return orig;
+	int LT, GT, EQ, NE, total;
 
-	orig = remove_unsigned_from_comparison(orig);
-	op = remove_unsigned_from_comparison(op);
+	if (left_compare == IMPOSSIBLE_COMPARISON ||
+	    right_compare == IMPOSSIBLE_COMPARISON)
+		return IMPOSSIBLE_COMPARISON;
 
-	switch (orig) {
-	case 0:
-		return op;
+	left_compare = remove_unsigned_from_comparison(left_compare);
+	right_compare = remove_unsigned_from_comparison(right_compare);
+
+	LT = GT = EQ = NE = total = 0;
+
+	/* Only one side is known. */
+	if (!left_compare)
+		return right_compare;
+	if (!right_compare)
+		return left_compare;
+
+	switch (left_compare) {
 	case '<':
-		switch (op) {
-		case '<':
-		case SPECIAL_LTE:
-		case SPECIAL_NOTEQUAL:
-			return '<';
-		}
-		return 0;
+		LT++;
+		total += 1;
+		break;
 	case SPECIAL_LTE:
-		switch (op) {
-		case '<':
-		case SPECIAL_LTE:
-		case SPECIAL_EQUAL:
-			return op;
-		case SPECIAL_NOTEQUAL:
-			return '<';
-		}
-		return 0;
+		LT++;
+		EQ++;
+		total += 2;
+		break;
 	case SPECIAL_EQUAL:
-		switch (op) {
-		case SPECIAL_LTE:
-		case SPECIAL_EQUAL:
-		case SPECIAL_GTE:
-		case SPECIAL_UNSIGNED_LTE:
-		case SPECIAL_UNSIGNED_GTE:
-			return SPECIAL_EQUAL;
-		}
-		return 0;
+		EQ++;
+		total += 1;
+		break;
 	case SPECIAL_NOTEQUAL:
-		switch (op) {
-		case '<':
-		case SPECIAL_LTE:
-			return '<';
-		case SPECIAL_UNSIGNED_LT:
-		case SPECIAL_UNSIGNED_LTE:
-			return SPECIAL_UNSIGNED_LT;
-		case SPECIAL_NOTEQUAL:
-			return op;
-		case '>':
-		case SPECIAL_GTE:
-			return '>';
-		case SPECIAL_UNSIGNED_GT:
-		case SPECIAL_UNSIGNED_GTE:
-			return SPECIAL_UNSIGNED_GT;
-		}
-		return 0;
+		NE++;
+		total += 1;
+		break;
 	case SPECIAL_GTE:
-		switch (op) {
-		case SPECIAL_LTE:
-			return SPECIAL_EQUAL;
-		case '>':
-		case SPECIAL_GTE:
-		case SPECIAL_EQUAL:
-			return op;
-		case SPECIAL_NOTEQUAL:
-			return '>';
-		}
-		return 0;
+		GT++;
+		EQ++;
+		total += 2;
+		break;
 	case '>':
-		switch (op) {
-		case '>':
-		case SPECIAL_GTE:
-		case SPECIAL_NOTEQUAL:
-			return '>';
-		}
-		return 0;
-	case SPECIAL_UNSIGNED_LT:
-		switch (op) {
-		case SPECIAL_UNSIGNED_LT:
-		case SPECIAL_UNSIGNED_LTE:
-		case SPECIAL_NOTEQUAL:
-			return SPECIAL_UNSIGNED_LT;
-		}
-		return 0;
-	case SPECIAL_UNSIGNED_LTE:
-		switch (op) {
-		case SPECIAL_UNSIGNED_LT:
-		case SPECIAL_UNSIGNED_LTE:
-		case SPECIAL_EQUAL:
-			return op;
-		case SPECIAL_NOTEQUAL:
-			return SPECIAL_UNSIGNED_LT;
-		case SPECIAL_UNSIGNED_GTE:
-			return SPECIAL_EQUAL;
-		}
-		return 0;
-	case SPECIAL_UNSIGNED_GTE:
-		switch (op) {
-		case SPECIAL_UNSIGNED_LTE:
-			return SPECIAL_EQUAL;
-		case SPECIAL_NOTEQUAL:
-			return SPECIAL_UNSIGNED_GT;
-		case SPECIAL_EQUAL:
-		case SPECIAL_UNSIGNED_GTE:
-		case SPECIAL_UNSIGNED_GT:
-			return op;
-		}
-		return 0;
-	case SPECIAL_UNSIGNED_GT:
-		switch (op) {
-		case SPECIAL_UNSIGNED_GT:
-		case SPECIAL_UNSIGNED_GTE:
-		case SPECIAL_NOTEQUAL:
-			return SPECIAL_UNSIGNED_GT;
-		}
-		return 0;
+		GT++;
+		total += 1;
+		break;
+	default:
+		return UNKNOWN_COMPARISON;
 	}
-	return 0;
+
+	switch (right_compare) {
+	case '<':
+		LT++;
+		total += 1;
+		break;
+	case SPECIAL_LTE:
+		LT++;
+		EQ++;
+		total += 2;
+		break;
+	case SPECIAL_EQUAL:
+		EQ++;
+		total += 1;
+		break;
+	case SPECIAL_NOTEQUAL:
+		NE++;
+		total += 1;
+		break;
+	case SPECIAL_GTE:
+		GT++;
+		EQ++;
+		total += 2;
+		break;
+	case '>':
+		GT++;
+		total += 1;
+		break;
+	default:
+		return UNKNOWN_COMPARISON;
+	}
+
+	if (LT == 2) {
+		if (EQ == 2)
+			return SPECIAL_LTE;
+		return '<';
+	}
+
+	if (GT == 2) {
+		if (EQ == 2)
+			return SPECIAL_GTE;
+		return '>';
+	}
+	if (EQ == 2)
+		return SPECIAL_EQUAL;
+	if (total == 2 && EQ && NE)
+		return IMPOSSIBLE_COMPARISON;
+	if (GT && LT)
+		return IMPOSSIBLE_COMPARISON;
+	if (GT && NE)
+		return '>';
+	if (LT && NE)
+		return '<';
+	if (NE == 2)
+		return SPECIAL_NOTEQUAL;
+	if (total == 2 && (LT || GT) && EQ)
+		return IMPOSSIBLE_COMPARISON;
+
+	return UNKNOWN_COMPARISON;
 }
 
-static void pre_merge_hook(struct sm_state *sm)
+static void pre_merge_hook(struct sm_state *cur, struct sm_state *other)
 {
-	struct compare_data *data = sm->state->data;
-	int other;
+	struct compare_data *data = cur->state->data;
+	int extra, new;
+	static bool in_recurse;
 
 	if (!data)
 		return;
-	other = get_comparison(data->left, data->right);
-	if (!other)
+
+	if (in_recurse)
 		return;
+	in_recurse = true;
+	extra = comparison_from_extra(data->left, data->right);
+	in_recurse = false;
+	if (!extra)
+		return;
+	new = comparison_intersection(extra, data->comparison);
+	if (new == data->comparison)
+		return;
 
-	set_state(compare_id, sm->name, NULL,
+	set_state(compare_id, cur->name, NULL,
 		  alloc_compare_state(data->left, data->left_var, data->left_vsl,
-				      other,
+				      new,
 				      data->right, data->right_var, data->right_vsl));
 }
 
@@ -546,13 +567,14 @@
 	struct compare_data *data = s1->data;
 	int op;
 
+	if (!data)
+		return &undefined;
+
 	op = merge_comparisons(state_to_comparison(s1), state_to_comparison(s2));
-	if (op)
-		return alloc_compare_state(
-				data->left, data->left_var, data->left_vsl,
-				op,
-				data->right, data->right_var, data->right_vsl);
-	return &undefined;
+	return alloc_compare_state(
+			data->left, data->left_var, data->left_vsl,
+			op,
+			data->right, data->right_var, data->right_vsl);
 }
 
 static struct smatch_state *alloc_link_state(struct string_list *links)
@@ -690,7 +712,11 @@
 			set_state(compare_id, tmp, NULL, new);
 			break;
 		default:
-			set_state(compare_id, tmp, NULL, &undefined);
+			new = alloc_compare_state(
+					data->left, data->left_var, data->left_vsl,
+					UNKNOWN_COMPARISON,
+					data->right, data->right_var, data->right_vsl);
+			set_state(compare_id, tmp, NULL, new);
 		}
 	} END_FOR_EACH_PTR(tmp);
 }
@@ -704,8 +730,15 @@
 	links = sm->state->data;
 
 	FOR_EACH_PTR(links, tmp) {
+		struct compare_data *data;
+		struct smatch_state *new;
+
 		state = get_state(compare_id, tmp, NULL);
+		if (!state || !state->data)
+			continue;
 
+		data = state->data;
+
 		switch (state_to_comparison(state)) {
 		case SPECIAL_EQUAL:
 		case SPECIAL_LTE:
@@ -712,9 +745,6 @@
 		case SPECIAL_UNSIGNED_LTE:
 		case '<':
 		case SPECIAL_UNSIGNED_LT: {
-			struct compare_data *data = state->data;
-			struct smatch_state *new;
-
 			if (preserve)
 				break;
 
@@ -726,7 +756,11 @@
 			break;
 			}
 		default:
-			set_state(compare_id, tmp, NULL, &undefined);
+			new = alloc_compare_state(
+					data->left, data->left_var, data->left_vsl,
+					UNKNOWN_COMPARISON,
+					data->right, data->right_var, data->right_vsl);
+			set_state(compare_id, tmp, NULL, new);
 		}
 	} END_FOR_EACH_PTR(tmp);
 }
@@ -739,7 +773,20 @@
 	links = sm->state->data;
 
 	FOR_EACH_PTR(links, tmp) {
-		set_state(compare_id, tmp, NULL, &undefined);
+		struct smatch_state *old, *new;
+
+		old = get_state(compare_id, tmp, NULL);
+		if (!old || !old->data) {
+			new = &undefined;
+		} else {
+			struct compare_data *data = old->data;
+
+			new = alloc_compare_state(
+					data->left, data->left_var, data->left_vsl,
+					UNKNOWN_COMPARISON,
+					data->right, data->right_var, data->right_vsl);
+		}
+		set_state(compare_id, tmp, NULL, new);
 	} END_FOR_EACH_PTR(tmp);
 	set_state(link_id, sm->name, sm->sym, &undefined);
 }
@@ -849,7 +896,7 @@
 		return;
 
 	op = rl_comparison(left, right);
-	if (!op)
+	if (op == UNKNOWN_COMPARISON)
 		return;
 
 	add_comparison(expr->unop, op, parent->right);
@@ -1016,8 +1063,8 @@
 		true_comparison = combine_comparisons(left_comparison, right_comparison);
 		false_comparison = combine_comparisons(left_false_comparison, right_comparison);
 
-		true_comparison = filter_comparison(orig_comparison, true_comparison);
-		false_comparison = filter_comparison(orig_comparison, false_comparison);
+		true_comparison = comparison_intersection(orig_comparison, true_comparison);
+		false_comparison = comparison_intersection(orig_comparison, false_comparison);
 
 		if (strcmp(left_var, right_var) > 0) {
 		  	struct expression *tmp_expr = left_expr;
@@ -1276,8 +1323,8 @@
 	}
 
 	orig_comparison = get_comparison(left_expr, right_expr);
-	op = filter_comparison(orig_comparison, op);
-	false_op = filter_comparison(orig_comparison, false_op);
+	op = comparison_intersection(orig_comparison, op);
+	false_op = comparison_intersection(orig_comparison, false_op);
 
 	snprintf(state_name, sizeof(state_name), "%s vs %s", left, right);
 	true_state = alloc_compare_state(
@@ -1334,7 +1381,6 @@
 		handle_comparison(new_left, expr->op, new_right, NULL, NULL);
 	}
 
-
 	redo = 0;
 	left = strip_parens(expr->left);
 	right = strip_parens(expr->right);
@@ -1618,7 +1664,7 @@
 	int ret = 0;
 
 	if (!one || !two)
-		return 0;
+		return UNKNOWN_COMPARISON;
 
 	if (strcmp(one, two) == 0)
 		return SPECIAL_EQUAL;
@@ -1646,10 +1692,12 @@
 {
 	char *one = NULL;
 	char *two = NULL;
-	int ret = 0;
+	int ret = UNKNOWN_COMPARISON;
+	int extra = UNKNOWN_COMPARISON;
 
-	if (!a || !b)
-		return 0;
+	if (a == UNKNOWN_COMPARISON ||
+	    b == UNKNOWN_COMPARISON)
+		return UNKNOWN_COMPARISON;
 
 	a = strip_parens(a);
 	b = strip_parens(b);
@@ -1677,7 +1725,7 @@
 		ret = get_comparison_strings(one, two);
 	}
 
-	if (!ret)
+	if (ret == UNKNOWN_COMPARISON)
 		goto free;
 
 	if ((is_plus_one(a) || is_minus_one(b)) && ret == '<')
@@ -1685,15 +1733,14 @@
 	else if ((is_minus_one(a) || is_plus_one(b)) && ret == '>')
 		ret = SPECIAL_GTE;
 	else
-		ret = 0;
+		ret = UNKNOWN_COMPARISON;
 
 free:
 	free_string(one);
 	free_string(two);
 
-	if (!ret && use_extra)
-		return comparison_from_extra(a, b);
-	return ret;
+	extra = comparison_from_extra(a, b);
+	return comparison_intersection(ret, extra);
 }
 
 int get_comparison(struct expression *a, struct expression *b)
@@ -1905,11 +1952,11 @@
 {
 	struct expression *arg;
 	int comparison;
-	char buf[4];
+	char buf[16];
 
 	if (!str_to_comparison_arg(range, call, &comparison, &arg))
 		return;
-	snprintf(buf, sizeof(buf), "%s", show_special(comparison));
+	snprintf(buf, sizeof(buf), "%s", show_comparison(comparison));
 	update_links_from_call(call, comparison, arg);
 	add_comparison(call, comparison, arg);
 }
@@ -1971,11 +2018,12 @@
 			continue;
 		snprintf(buf, sizeof(buf), "%s orig", param->ident->name);
 		compare = get_comparison_strings(var, buf);
-		if (!compare)
+		if (compare == UNKNOWN_COMPARISON ||
+		    compare == IMPOSSIBLE_COMPARISON)
 			continue;
-		if (show_special(compare)[0] != starts_with)
+		if (show_comparison(compare)[0] != starts_with)
 			continue;
-		snprintf(buf, sizeof(buf), "[%s$%d]", show_special(compare), i);
+		snprintf(buf, sizeof(buf), "[%s$%d]", show_comparison(compare), i);
 		ret_str = alloc_sname(buf);
 		break;
 	} END_FOR_EACH_PTR(param);
@@ -2006,9 +2054,10 @@
 			continue;
 		snprintf(buf, sizeof(buf), "%s orig", param->ident->name);
 		compare = get_comparison_strings(name, buf);
-		if (!compare)
+		if (compare == UNKNOWN_COMPARISON ||
+		    compare == IMPOSSIBLE_COMPARISON)
 			continue;
-		snprintf(buf, sizeof(buf), "[%s$%d]", show_special(compare), i);
+		snprintf(buf, sizeof(buf), "[%s$%d]", show_comparison(compare), i);
 		return alloc_sname(buf);
 	} END_FOR_EACH_PTR(param);
 
@@ -2049,7 +2098,7 @@
 		compare = get_comparison_strings(var, buf);
 		if (!compare)
 			continue;
-		snprintf(buf, sizeof(buf), "[%s$%d]", show_special(compare), i);
+		snprintf(buf, sizeof(buf), "[%s$%d]", show_comparison(compare), i);
 		ret_str = alloc_sname(buf);
 		break;
 	} END_FOR_EACH_PTR(param);
@@ -2128,7 +2177,9 @@
 			if (!sm)
 				continue;
 			data = sm->state->data;
-			if (!data || !data->comparison)
+			if (!data ||
+			    data->comparison == UNKNOWN_COMPARISON ||
+			    data->comparison == IMPOSSIBLE_COMPARISON)
 				continue;
 			arg_name = expr_to_var(arg);
 			if (!arg_name)
@@ -2153,7 +2204,7 @@
 			right_name = get_printed_param_name(expr, right_vs->var, right_vs->sym);
 			if (!right_name)
 				goto free;
-			snprintf(info_buf, sizeof(info_buf), "%s %s", show_special(comparison), right_name);
+			snprintf(info_buf, sizeof(info_buf), "%s %s", show_comparison(comparison), right_name);
 			sql_insert_caller_info(expr, PARAM_COMPARE, i, "$", info_buf);
 
 free:
@@ -2203,7 +2254,7 @@
 		right_name = get_printed_param_name(call, right->var, right->sym);
 		if (!right_name)
 			continue;
-		snprintf(info_buf, sizeof(info_buf), "%s %s", show_special(data->comparison), right_name);
+		snprintf(info_buf, sizeof(info_buf), "%s %s", show_comparison(data->comparison), right_name);
 		sql_insert_caller_info(call, PARAM_COMPARE, param, printed_name, info_buf);
 	} END_FOR_EACH_PTR(link);
 }
@@ -2274,7 +2325,9 @@
 			if (!sm)
 				continue;
 			data = sm->state->data;
-			if (!data || !data->comparison)
+			if (!data ||
+			    data->comparison == UNKNOWN_COMPARISON ||
+			    data->comparison == IMPOSSIBLE_COMPARISON)
 				continue;
 			if (ptr_list_size((struct ptr_list *)data->left_vsl) != 1 ||
 			    ptr_list_size((struct ptr_list *)data->right_vsl) != 1)
@@ -2316,7 +2369,7 @@
 			 * smatch_param_compare_limit.c.
 			 */
 
-			snprintf(info_buf, sizeof(info_buf), "%s %s", show_special(data->comparison), right_buf);
+			snprintf(info_buf, sizeof(info_buf), "%s %s", show_comparison(data->comparison), right_buf);
 			sql_insert_return_states(return_id, return_ranges,
 					PARAM_COMPARE, left_param, left_buf, info_buf);
 		} END_FOR_EACH_PTR(link);
@@ -2493,7 +2546,7 @@
 	if (!state_op)
 		goto free;
 
-	if (!filter_comparison(remove_unsigned_from_comparison(state_op), op))
+	if (!comparison_intersection(remove_unsigned_from_comparison(state_op), op))
 		ret = 1;
 free:
 	free_string(left_name);
@@ -2591,37 +2644,47 @@
 		       struct state_list **false_stack)
 {
 	struct compare_data *data;
-	int istrue = 0;
-	int isfalse = 0;
+	int is_true = 0;
+	int is_false = 0;
 
 	if (!sm)
 		return;
 	data = sm->state->data;
-	if (!data) {
-		if (sm->merged) {
-			filter_by_sm(sm->left, op, true_stack, false_stack);
-			filter_by_sm(sm->right, op, true_stack, false_stack);
-		}
+	if (!data || data->comparison == UNKNOWN_COMPARISON)
+		goto split;
+	if (data->comparison == IMPOSSIBLE_COMPARISON)
 		return;
-	}
 
-	if (data->comparison &&
-	    data->comparison == filter_comparison(data->comparison, op))
-		istrue = 1;
+	/*
+	 * We want to check that "data->comparison" is totally inside "op".  So
+	 * if data->comparison is < and op is <= then that's true.  Or if
+	 * data->comparison is == and op is <= then that's true.  But if
+	 * data->comparison is <= and op is < than that's neither true nor
+	 * false.
+	 */
+	if (data->comparison == comparison_intersection(data->comparison, op))
+		is_true = 1;
+	if (data->comparison == comparison_intersection(data->comparison, negate_comparison(op)))
+		is_false = 1;
 
-	if (data->comparison &&
-	    data->comparison == filter_comparison(data->comparison, negate_comparison(op)))
-		isfalse = 1;
+	if (debug_implied()) {
+		sm_msg("%s: %s: op = '%s' negated '%s'. true_intersect = '%s' false_insersect = '%s' sm = '%s'",
+		       __func__,
+		       sm->state->name,
+		       alloc_sname(show_comparison(op)),
+		       alloc_sname(show_comparison(negate_comparison(op))),
+		       alloc_sname(show_comparison(comparison_intersection(data->comparison, op))),
+		       alloc_sname(show_comparison(comparison_intersection(data->comparison, negate_comparison(op)))),
+		       show_sm(sm));
+	}
 
-	if (istrue)
+	if (is_true)
 		add_ptr_list(true_stack, sm);
-	if (isfalse)
+	if (is_false)
 		add_ptr_list(false_stack, sm);
-
-	if (sm->merged) {
-		filter_by_sm(sm->left, op, true_stack, false_stack);
-		filter_by_sm(sm->right, op, true_stack, false_stack);
-	}
+split:
+	filter_by_sm(sm->left, op, true_stack, false_stack);
+	filter_by_sm(sm->right, op, true_stack, false_stack);
 }
 
 struct sm_state *comparison_implication_hook(struct expression *expr,
@@ -2665,7 +2728,7 @@
 	if (!*true_stack && !*false_stack)
 		return NULL;
 
-	if (option_debug)
+	if (debug_implied())
 		sm_msg("implications from comparison: (%s)", show_sm(sm));
 
 	return sm;
