Print this page
new smatch

@@ -55,10 +55,19 @@
                 return NULL;
         vs = first_ptr_list((struct ptr_list *)vsl);
         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,
                 int comparison,
                 struct expression *right,

@@ -66,11 +75,11 @@
 {
         struct smatch_state *state;
         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);
         data->left_vsl = clone_var_sym_list(left_vsl);
         data->comparison = comparison;

@@ -82,22 +91,22 @@
 }
 
 int state_to_comparison(struct smatch_state *state)
 {
         if (!state || !state->data)
-                return 0;
+                return UNKNOWN_COMPARISON;
         return ((struct compare_data *)state->data)->comparison;
 }
 
 /*
  * flip_comparison() reverses the op left and right.  So "x >= y" becomes "y <= x".
  */
 int flip_comparison(int op)
 {
         switch (op) {
-        case 0:
-                return 0;
+        case UNKNOWN_COMPARISON:
+                return UNKNOWN_COMPARISON;
         case '<':
                 return '>';
         case SPECIAL_UNSIGNED_LT:
                 return SPECIAL_UNSIGNED_GT;
         case SPECIAL_LTE:

@@ -114,21 +123,23 @@
                 return SPECIAL_UNSIGNED_LTE;
         case '>':
                 return '<';
         case SPECIAL_UNSIGNED_GT:
                 return SPECIAL_UNSIGNED_LT;
+        case IMPOSSIBLE_COMPARISON:
+                return UNKNOWN_COMPARISON;
         default:
                 sm_perror("unhandled comparison %d", op);
                 return op;
         }
 }
 
 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:
                 return SPECIAL_UNSIGNED_GTE;
         case SPECIAL_LTE:

@@ -145,10 +156,12 @@
                 return SPECIAL_UNSIGNED_LT;
         case '>':
                 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;
         }
 }

@@ -157,11 +170,11 @@
 {
         sval_t left_min, left_max, right_min, right_max;
         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);
         if (type_positive_bits(rl_type(right_rl)) > type_positive_bits(type))
                 type = rl_type(right_rl);

@@ -186,21 +199,21 @@
         if (sval_cmp(left_min, right_max) > 0)
                 return '>';
         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)
 {
         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);
 }
 
 static struct range_list *get_orig_rl(struct var_sym_list *vsl)

@@ -219,33 +232,36 @@
 
 static struct smatch_state *unmatched_comparison(struct sm_state *sm)
 {
         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,
+
+alloc:
+        return alloc_compare_state(data->left, data->left_var, data->left_vsl,
                                 op,
                                 data->right, data->right_var, data->right_vsl);
-
-        return &undefined;
 }
 
 /* remove_unsigned_from_comparison() is obviously a hack. */
 int remove_unsigned_from_comparison(int op)
 {

@@ -269,13 +285,18 @@
  */
 int merge_comparisons(int one, int two)
 {
         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);
 
         if (one == two)
                 return one;

@@ -319,11 +340,11 @@
         case '>':
                 GT = 1;
         }
 
         if (LT && EQ && GT)
-                return 0;
+                return UNKNOWN_COMPARISON;
         if (LT && EQ)
                 return SPECIAL_LTE;
         if (LT && GT)
                 return SPECIAL_NOTEQUAL;
         if (LT)

@@ -330,21 +351,17 @@
                 return '<';
         if (EQ && GT)
                 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)
 {
         int LT, EQ, GT;
 

@@ -398,163 +415,168 @@
         if (GT == 2) {
                 if (EQ == 2)
                         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 '<':
+                LT++;
+                total += 1;
+                break;
                 case SPECIAL_LTE:
+                LT++;
+                EQ++;
+                total += 2;
+                break;
+        case SPECIAL_EQUAL:
+                EQ++;
+                total += 1;
+                break;
                 case SPECIAL_NOTEQUAL:
-                        return '<';
+                NE++;
+                total += 1;
+                break;
+        case SPECIAL_GTE:
+                GT++;
+                EQ++;
+                total += 2;
+                break;
+        case '>':
+                GT++;
+                total += 1;
+                break;
+        default:
+                return UNKNOWN_COMPARISON;
                 }
-                return 0;
-        case SPECIAL_LTE:
-                switch (op) {
+
+        switch (right_compare) {
                 case '<':
+                LT++;
+                total += 1;
+                break;
                 case SPECIAL_LTE:
+                LT++;
+                EQ++;
+                total += 2;
+                break;
                 case SPECIAL_EQUAL:
-                        return op;
+                EQ++;
+                total += 1;
+                break;
                 case SPECIAL_NOTEQUAL:
-                        return '<';
-                }
-                return 0;
-        case SPECIAL_EQUAL:
-                switch (op) {
-                case SPECIAL_LTE:
-                case SPECIAL_EQUAL:
+                NE++;
+                total += 1;
+                break;
                 case SPECIAL_GTE:
-                case SPECIAL_UNSIGNED_LTE:
-                case SPECIAL_UNSIGNED_GTE:
-                        return SPECIAL_EQUAL;
+                GT++;
+                EQ++;
+                total += 2;
+                break;
+        case '>':
+                GT++;
+                total += 1;
+                break;
+        default:
+                return UNKNOWN_COMPARISON;
                 }
-                return 0;
-        case SPECIAL_NOTEQUAL:
-                switch (op) {
-                case '<':
-                case SPECIAL_LTE:
+
+        if (LT == 2) {
+                if (EQ == 2)
+                        return 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;
-        case SPECIAL_GTE:
-                switch (op) {
-                case SPECIAL_LTE:
-                        return SPECIAL_EQUAL;
-                case '>':
-                case SPECIAL_GTE:
-                case SPECIAL_EQUAL:
-                        return op;
-                case SPECIAL_NOTEQUAL:
+
+        if (GT == 2) {
+                if (EQ == 2)
+                        return SPECIAL_GTE;
                         return '>';
                 }
-                return 0;
-        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:
+        if (EQ == 2)
                         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;
-        }
-        return 0;
+        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));
 }
 
 struct smatch_state *merge_compare_states(struct smatch_state *s1, struct smatch_state *s2)
 {
         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;
 }
 
 static struct smatch_state *alloc_link_state(struct string_list *links)
 {
         struct smatch_state *state;

@@ -688,11 +710,15 @@
                                         flip ? SPECIAL_GTE : SPECIAL_LTE,
                                         data->right, data->right_var, data->right_vsl);
                         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);
 }
 
 static void match_dec(struct sm_state *sm, bool preserve)

@@ -702,21 +728,25 @@
         char *tmp;
 
         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:
                 case SPECIAL_UNSIGNED_LTE:
                 case '<':
                 case SPECIAL_UNSIGNED_LT: {
-                        struct compare_data *data = state->data;
-                        struct smatch_state *new;
-
                         if (preserve)
                                 break;
 
                         new = alloc_compare_state(
                                         data->left, data->left_var, data->left_vsl,

@@ -724,11 +754,15 @@
                                         data->right, data->right_var, data->right_vsl);
                         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);
 }
 
 static void reset_sm(struct sm_state *sm)

@@ -737,11 +771,24 @@
         char *tmp;
 
         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);
 }
 
 static bool match_add_sub_assign(struct sm_state *sm, struct expression *expr)

@@ -847,11 +894,11 @@
         if (!get_implied_rl(expr->unop, &left) ||
            !get_implied_rl(parent->right, &right))
                 return;
 
         op = rl_comparison(left, right);
-        if (!op)
+        if (op == UNKNOWN_COMPARISON)
                 return;
 
         add_comparison(expr->unop, op, parent->right);
 }
 

@@ -1014,12 +1061,12 @@
                 orig_comparison = get_orig_comparison(pre_stree, left_var, right_var);
 
                 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;
                         const char *tmp_var = left_var;
                         struct var_sym_list *tmp_vsl = left_vsl;

@@ -1274,12 +1321,12 @@
                 op = flip_comparison(op);
                 false_op = flip_comparison(false_op);
         }
 
         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(
                         left_expr, left, left_vsl,
                         op,

@@ -1332,11 +1379,10 @@
                 new_left = left->right;
                 new_right = binop_expression(right, '-', left->left);
                 handle_comparison(new_left, expr->op, new_right, NULL, NULL);
         }
 
-
         redo = 0;
         left = strip_parens(expr->left);
         right = strip_parens(expr->right);
         if (get_last_expr_from_expression_stmt(expr->left)) {
                 left = get_last_expr_from_expression_stmt(expr->left);

@@ -1616,11 +1662,11 @@
         struct smatch_state *state;
         int invert = 0;
         int ret = 0;
 
         if (!one || !two)
-                return 0;
+                return UNKNOWN_COMPARISON;
 
         if (strcmp(one, two) == 0)
                 return SPECIAL_EQUAL;
 
         if (strcmp(one, two) > 0) {

@@ -1644,14 +1690,16 @@
 
 static int get_comparison_helper(struct expression *a, struct expression *b, bool use_extra)
 {
         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);
 
         move_plus_to_minus(&a, &b);

@@ -1675,27 +1723,26 @@
                 free_string(two);
                 two = chunk_to_var(b->left);
                 ret = get_comparison_strings(one, two);
         }
 
-        if (!ret)
+        if (ret == UNKNOWN_COMPARISON)
                 goto free;
 
         if ((is_plus_one(a) || is_minus_one(b)) && ret == '<')
                 ret = SPECIAL_LTE;
         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)
 {
         return get_comparison_helper(a, b, true);

@@ -1903,15 +1950,15 @@
 
 void __add_return_comparison(struct expression *call, const char *range)
 {
         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);
 }
 
 void __add_comparison_info(struct expression *expr, struct expression *call, const char *range)

@@ -1969,15 +2016,16 @@
                         continue;
                 if (!param->ident)
                         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);
 
         free_string(var);

@@ -2004,13 +2052,14 @@
                 i++;
                 if (!param->ident)
                         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);
 
         return NULL;
 }

@@ -2047,11 +2096,11 @@
                         continue;
                 snprintf(buf, sizeof(buf), "%s orig", param->ident->name);
                 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);
 
 free:

@@ -2126,11 +2175,13 @@
                                 continue;
                         sm = get_sm_state(compare_id, link, NULL);
                         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)
                                 continue;
 

@@ -2151,11 +2202,11 @@
                         if (strcmp(right_vs->var, right_name) != 0)
                                 goto free;
                         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:
                         free_string(arg_name);
                 } END_FOR_EACH_PTR(link);

@@ -2201,11 +2252,11 @@
                         continue;
 
                 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);
 }
 
 static void print_return_value_comparison(int return_id, char *return_ranges, struct expression *expr)

@@ -2272,11 +2323,13 @@
                 FOR_EACH_PTR(links, link) {
                         sm = get_sm_state(compare_id, link, NULL);
                         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)
                                 continue;
                         left = first_ptr_list((struct ptr_list *)data->left_vsl);

@@ -2314,11 +2367,11 @@
                          * FIXME: this should reject $ type variables (as
                          * opposed to $->foo type).  Those should come from
                          * 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);
 
         } END_FOR_EACH_SM(tmp);

@@ -2491,11 +2544,11 @@
                 goto free;
         state_op = state_to_comparison(state);
         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);
         free_string(right_name);
         return ret;

@@ -2589,41 +2642,51 @@
 static void filter_by_sm(struct sm_state *sm, int op,
                        struct state_list **true_stack,
                        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) {
+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,
                                 struct state_list **true_stack,
                                 struct state_list **false_stack)

@@ -2663,10 +2726,10 @@
 
         filter_by_sm(sm, op, true_stack, false_stack);
         if (!*true_stack && !*false_stack)
                 return NULL;
 
-        if (option_debug)
+        if (debug_implied())
                 sm_msg("implications from comparison: (%s)", show_sm(sm));
 
         return sm;
 }