--- old/usr/src/tools/smatch/src/smatch_implied.c	Tue May 19 02:50:33 2020
+++ new/usr/src/tools/smatch/src/smatch_implied.c	Tue May 19 02:50:33 2020
@@ -72,6 +72,8 @@
 
 bool debug_implied(void)
 {
+	if (option_debug)
+		return true;
 	return implied_debug;
 }
 
@@ -91,6 +93,13 @@
 	return my_list;
 }
 
+static const char *show_comparison(int op)
+{
+	if (op == PARAM_LIMIT)
+		return "<lim>";
+	return show_special(op);
+}
+
 static void print_debug_tf(struct sm_state *sm, int istrue, int isfalse)
 {
 	if (!implied_debug && !option_debug)
@@ -115,6 +124,18 @@
 	}
 }
 
+void split_comparison_helper(struct range_list *left_orig, int op, struct range_list *right_orig,
+		struct range_list **left_true_rl, struct range_list **left_false_rl)
+{
+	if (op == PARAM_LIMIT) {
+		*left_true_rl = rl_intersection(left_orig, right_orig);
+		*left_false_rl = rl_filter(left_orig, right_orig);
+		return;
+	}
+
+	split_comparison_rl(left_orig, op, right_orig, left_true_rl, left_false_rl, NULL, NULL);
+}
+
 static int create_fake_history(struct sm_state *sm, int comparison, struct range_list *rl)
 {
 	struct range_list *orig_rl;
@@ -131,7 +152,7 @@
 		return 0;
 
 	orig_rl = cast_rl(rl_type(rl), estate_rl(sm->state));
-	split_comparison_rl(orig_rl, comparison, rl, &true_rl, &false_rl, NULL, NULL);
+	split_comparison_helper(orig_rl, comparison, rl, &true_rl, &false_rl);
 
 	true_rl = rl_truncate_cast(estate_type(sm->state), true_rl);
 	false_rl = rl_truncate_cast(estate_type(sm->state), false_rl);
@@ -154,7 +175,7 @@
 
 	if (implied_debug)
 		sm_msg("fake_history: %s vs %s.  %s %s %s. --> T: %s F: %s",
-		       sm->name, show_rl(rl), sm->state->name, show_special(comparison), show_rl(rl),
+		       sm->name, show_rl(rl), sm->state->name, show_comparison(comparison), show_rl(rl),
 		       show_rl(true_rl), show_rl(false_rl));
 
 	true_sm = clone_sm(sm);
@@ -233,6 +254,32 @@
 	return ret;
 }
 
+static bool possibly_true_helper(struct range_list *var_rl, int comparison, struct range_list *rl)
+{
+	if (comparison == PARAM_LIMIT) {
+		struct range_list *intersect;
+
+		intersect = rl_intersection(var_rl, rl);
+		if (intersect)
+			return true;
+		return false;
+	}
+	return possibly_true_rl(var_rl, comparison, rl);
+}
+
+static bool possibly_false_helper(struct range_list *var_rl, int comparison, struct range_list *rl)
+{
+	if (comparison == PARAM_LIMIT) {
+		struct range_list *intersect;
+
+		intersect = rl_intersection(var_rl, rl);
+		if (!rl_equiv(var_rl, intersect))
+			return true;
+		return false;
+	}
+	return possibly_false_rl(var_rl, comparison, rl);
+}
+
 /*
  * If 'foo' == 99 add it that pool to the true pools.  If it's false, add it to
  * the false pools.  If we're not sure, then we don't add it to either.
@@ -252,8 +299,8 @@
 
 	var_rl = cast_rl(rl_type(rl), estate_rl(sm->state));
 
-	istrue = !possibly_false_rl(var_rl, comparison, rl);
-	isfalse = !possibly_true_rl(var_rl, comparison, rl);
+	istrue = !possibly_false_helper(var_rl, comparison, rl);
+	isfalse = !possibly_true_helper(var_rl, comparison, rl);
 
 	print_debug_tf(sm, istrue, isfalse);
 
@@ -315,7 +362,7 @@
 	if (diff.tv_sec >= 1) {
 		if (implied_debug) {
 			sm_msg("debug: %s: implications taking too long.  (%s %s %s)",
-			       __func__, sm->state->name, show_special(comparison), show_rl(rl));
+			       __func__, sm->state->name, show_comparison(comparison), show_rl(rl));
 		}
 		if (mixed)
 			*mixed = 1;
@@ -538,9 +585,9 @@
 }
 
 static struct stree *filter_stack(struct sm_state *gate_sm,
-				       struct stree *pre_stree,
-				       const struct state_list *remove_stack,
-				       const struct state_list *keep_stack)
+				  struct stree *pre_stree,
+				  const struct state_list *remove_stack,
+				  const struct state_list *keep_stack)
 {
 	struct stree *ret = NULL;
 	struct sm_state *tmp;
@@ -593,7 +640,7 @@
 	gettimeofday(&time_before, NULL);
 
 	DIMPLIED("checking implications: (%s (%s) %s %s)\n",
-		 sm->name, sm->state->name, show_special(comparison), show_rl(rl));
+		 sm->name, sm->state->name, show_comparison(comparison), show_rl(rl));
 
 	if (!is_merged(sm)) {
 		DIMPLIED("%d '%s' from line %d is not merged.\n", get_lineno(), sm->name, sm->line);
@@ -752,7 +799,7 @@
 	if (!name || !sym)
 		goto free;
 	sm = get_sm_state(SMATCH_EXTRA, name, sym);
-	if (!sm)
+	if (!sm || !sm->merged)
 		goto free;
 
 	separate_and_filter(sm, SPECIAL_NOTEQUAL, tmp_range_list(estate_type(sm->state), 0), __get_cur_stree(), implied_true, implied_false, &mixed);
@@ -770,13 +817,13 @@
 }
 
 static int handled_by_comparison_hook(struct expression *expr,
-				   struct stree **implied_true,
-				   struct stree **implied_false)
+				      struct stree **implied_true,
+				      struct stree **implied_false)
 {
+	struct sm_state *sm, *true_sm, *false_sm;
 	struct state_list *true_stack = NULL;
 	struct state_list *false_stack = NULL;
 	struct stree *pre_stree;
-	struct sm_state *sm;
 
 	sm = comparison_implication_hook(expr, &true_stack, &false_stack);
 	if (!sm)
@@ -787,6 +834,13 @@
 	*implied_true = filter_stack(sm, pre_stree, false_stack, true_stack);
 	*implied_false = filter_stack(sm, pre_stree, true_stack, false_stack);
 
+	true_sm = get_sm_state_stree(*implied_true, sm->owner, sm->name, sm->sym);
+	false_sm = get_sm_state_stree(*implied_false, sm->owner, sm->name, sm->sym);
+	if (true_sm && strcmp(true_sm->state->name, "unknown") == 0)
+		delete_state_stree(implied_true, sm->owner, sm->name, sm->sym);
+	if (false_sm && strcmp(false_sm->state->name, "unknown") == 0)
+		delete_state_stree(implied_false, sm->owner, sm->name, sm->sym);
+
 	free_stree(&pre_stree);
 	free_slist(&true_stack);
 	free_slist(&false_stack);
@@ -953,9 +1007,9 @@
 	set_implied_states(NULL);
 }
 
-void param_limit_implications(struct expression *expr, int param, char *key, char *value)
+void param_limit_implications(struct expression *expr, int param, char *key, char *value, struct stree **implied)
 {
-	struct expression *arg;
+	struct expression *orig_expr, *arg;
 	struct symbol *compare_type;
 	char *name;
 	struct symbol *sym;
@@ -964,10 +1018,14 @@
 	struct stree *implied_true = NULL;
 	struct stree *implied_false = NULL;
 	struct range_list *orig, *limit;
+	char *left_name = NULL;
+	struct symbol *left_sym = NULL;
+	int mixed = 0;
 
 	if (time_parsing_function() > 40)
 		return;
 
+	orig_expr = expr;
 	while (expr->type == EXPR_ASSIGNMENT)
 		expr = strip_expr(expr->right);
 	if (expr->type != EXPR_CALL)
@@ -999,9 +1057,25 @@
 
 	call_results_to_rl(expr, compare_type, value, &limit);
 
-	separate_and_filter(sm, SPECIAL_EQUAL, limit, __get_cur_stree(), &implied_true, &implied_false, NULL);
+	separate_and_filter(sm, PARAM_LIMIT, limit, __get_cur_stree(), &implied_true, &implied_false, &mixed);
 
+	if (orig_expr->type == EXPR_ASSIGNMENT)
+		left_name = expr_to_var_sym(orig_expr->left, &left_sym);
+
 	FOR_EACH_SM(implied_true, tmp) {
+		/*
+		 * What we're trying to do here is preserve the sm state so that
+		 * smatch extra doesn't create a new sm state when it parses the
+		 * PARAM_LIMIT.
+		 */
+		if (!mixed && tmp->sym == sym &&
+		    strcmp(tmp->name, name) == 0 &&
+		    (!left_name || strcmp(left_name, name) != 0)) {
+			overwrite_sm_state_stree(implied, tmp);
+			continue;
+		}
+
+		// TODO why can't this just be __set_sm()?
 		__set_sm_fake_stree(tmp);
 	} END_FOR_EACH_SM(tmp);
 
