Repository: angr/angr

Issue:
Poor/incorrect decompiler output on direvent::main
the source:

```
int
main(int argc, char **argv)
{
	int i;

#ifdef ENABLE_NLS
	setlocale(LC_ALL, "");
	bindtextdomain(PACKAGE, LOCALEDIR);
	textdomain(PACKAGE);
#endif

	char *p = strrchr(argv[0], '/');
	if (p)
		program_name = p + 1;
	else
		program_name = argv[0];
	tag = estrdup(program_name);

	genev_init();
	config_init();
	
	parse_options(argc, argv, &i);

	argc -= i;
	argv += i;

	switch (argc) {
	default:
		diag(LOG_CRIT, _("too many arguments"));
		exit(1);
	case 1:
		conffile = argv[0];
		break;
	case 0:
		break;
	}

	config_parse(conffile);
	if (lint_only)
		return 0;

	if (opt_debug_level)
		debug_level += opt_debug_level;
	if (opt_foreground)
		foreground = opt_foreground;
	if (opt_pidfile)
		pidfile = opt_pidfile;
	if (opt_facility != -1)
		facility = opt_facility;
	if (!foreground && facility <= 0)
		facility = LOG_DAEMON;
	if (opt_user)
		user = opt_user;
	
	if (facility > 0) {
		openlog(tag, LOG_PID, facility);
		grecs_log_to_stderr = 0;
	}

	if (foreground)
		setup_watchers();
	else {
		/* Become a daemon */
		if (detach(setup_watchers)) {
			diag(LOG_CRIT, "daemon: %s", strerror(errno));
			exit(1);
		}
		log_to_stderr = -1;
	}
	
	diag(LOG_INFO, _("%s %s started"), program_name, VERSION);

	/* Write pidfile */
	if (pidfile)
		storepid(pidfile);

	/* Relinquish superuser privileges */
	if (user && getuid() == 0)
		setuser(user);

	signal_setup(sigmain);

	if (self_test_prog)
		self_test();
	
	/* Main loop */
	while (!stop && sysev_select() == 0) {
		process_timeouts();
		process_cleanup(0);
		watchpoint_gc();
	}

	shutdown_watchers();

	diag(LOG_INFO, _("%s %s stopped"), program_name, VERSION);

	if (pidfile)
		unlink(pidfile);
	
	return exit_code;
}
```

The binary:
[direvent.gz](https://github.com/angr/angr/files/7567324/direvent.gz)

The decompilation (notice the continuously stranger guard conditions):

```
int main(long long a0, struct_0 *a1)
{
    BOT v0;  // [bp-0x24]
    unsigned long long v11;  // rax
    unsigned long long v12;  // rax
    unsigned long long v17;  // rax
    unsigned long long v21;  // rax
    unsigned long long v25;  // rax
    unsigned long long v35;  // rax
    BOT v37;  // rcx
    BOT v38;  // rcx
    BOT v39;  // rdx
    unsigned long long v42;  // rbp
    BOT v43;  // r8
    BOT v44;  // r8
    BOT v45;  // r8
    BOT v46;  // r9
    BOT v47;  // r9
    BOT v48;  // r9
    struct_0 *v49;  // r12
    struct_1 *v9;  // rax

    v49 = a1;
    setlocale(0x6, 0x421624);
    bindtextdomain("direvent", "/usr/local/share/locale");
    textdomain("direvent");
    v42 = v49->field_0;
    v11 = strrchr(v49->field_0, 0x2f);
    program_name = (v11 != 0? v11 + 1 : rbp<8>);
    tag = estrdup((v11 != 0? v11 + 1 : rbp<8>));
    genev_init();
    config_init();
    parse_options(a0, v49, &v0);
    if (((int)a0) != v0)
    {
        if (((int)((int)a0) - v0) == 1)
        {
            conffile = *(None);
        }
        else
        {
            v17 = dcgettext(NULL, "too many arguments", 0x5);
            diag(2, v17, v39, v37, v43, v46);
            exit(0x1); /* do not return */
        }
    }
    config_parse();
    if (lint_only == 0)
    {
        if (opt_debug_level != 0)
        {
            debug_level = debug_level + ((int)opt_debug_level);
        }
        if (opt_foreground != 0)
        {
            foreground = ((int)opt_foreground);
        }
        if (opt_pidfile != 0)
        {
            pidfile = opt_pidfile;
        }
        if (opt_facility != -1)
        {
            facility = ((int)opt_facility);
        }
        if (foreground == 0 && facility <= 0)
        {
            facility = 24;
        }
        if (lint_only == 0 || opt_user != 0 || facility <= 0 || opt_user != 0)
        {
            user = opt_user;
        }
        if (foreground != 0 && opt_user == 0 && facility > 0 || (foreground == 0 && opt_user != 0 || foreground != 0 && opt_user != 0) && facility > 0 || foreground == 0 && opt_user == 0)
        {
            openlog(tag, 0x1, ((long long)lint_only));
            grecs_log_to_stderr = 0;
        }
        if (foreground != 0 && opt_user == 0 && facility <= 0 || (foreground != 0 && opt_user == 0 && facility > 0 || (foreground == 0 && opt_user != 0 || foreground != 0 && opt_user != 0) && facility > 0 || foreground == 0 && opt_user == 0 || (foreground == 0 && opt_user != 0 || foreground != 0 && opt_user != 0) && facility <= 0) && foreground != 0)
        {
            setup_watchers();
            goto 4214886;
        }
        if ((foreground != 0 && opt_user == 0 && facility > 0 || (foreground == 0 && opt_user != 0 || foreground != 0 && opt_user != 0) && facility > 0 || foreground == 0 && opt_user == 0 || (foreground == 0 && opt_user != 0 || foreground != 0 && opt_user != 0) && facility <= 0) && foreground == 0)
        {
            if ((int)detach(4233792) == 0)
            {
                log_to_stderr = -1;
            }
            else
            {
                v9 = __errno_location();
                v25 = strerror(((long long)v9->field_0));
                diag(2, "daemon: %s", v25, v38, v45, v48);
                exit(0x1); /* do not return */
            }
        }
        if ((foreground != 0 && opt_user == 0 && facility > 0 || (foreground == 0 && opt_user != 0 || foreground != 0 && opt_user != 0) && facility > 0 || foreground == 0 && opt_user == 0 || (foreground == 0 && opt_user != 0 || foreground != 0 && opt_user != 0) && facility <= 0) && foreground == 0 && (int)detach(4233792) == 0 || foreground != 0 && opt_user == 0 && facility <= 0 || (foreground != 0 && opt_user == 0 && facility > 0 || (foreground == 0 && opt_user != 0 || foreground != 0 && opt_user != 0) && facility > 0 || foreground == 0 && opt_user == 0 || (foreground == 0 && opt_user != 0 || foreground != 0 && opt_user != 0) && facility <= 0) && foreground != 0)
        {
            v12 = dcgettext(NULL, "%s %s started", 0x5);
            diag(6, v12, program_name, "5.2", v44, v47);
            if (pidfile != 0)
            {
                storepid(pidfile);
            }
            if (user != 0 && (int)getuid() == 0)
            {
                setuser(user);
            }
            sigv_set_all(4215616, 8, 4366368, 0);
            if (self_test_prog != 0)
            {
                self_test();
            }
            while ((int)sysev_select() == 0 && stop == 0)
            {
                process_timeouts();
                process_cleanup(0);
                watchpoint_gc();
            }
            shutdown_watchers();
            v21 = dcgettext(NULL, "%s %s stopped", 0x5);
            diag(6, v21, program_name, "5.2", v44, v47);
            if (pidfile != 0)
            {
                unlink(pidfile);
            }
            v35 = ((long long)exit_code);
        }
    }
    else
    {
        v35 = 0;
    }
    return v35;
}
```


Discussion:

Resolving patch:
diff --git a/angr/analyses/decompiler/condition_processor.py b/angr/analyses/decompiler/condition_processor.py
--- a/angr/analyses/decompiler/condition_processor.py
+++ b/angr/analyses/decompiler/condition_processor.py
@@ -1,9 +1,10 @@
 from collections import defaultdict
-from typing import Iterable, Generator, Dict, Any
+from typing import Generator, Dict, Any
 import operator
 import logging
 
 import networkx
+import sympy
 
 import claripy
 import ailment
@@ -18,25 +19,9 @@
 l = logging.getLogger(__name__)
 
 
-class TagsAnnotation(claripy.Annotation):
-    def __init__(self, **tags):
-        self.tags = tags
-        super().__init__()
-
-    def __hash__(self):
-        keys = list(sorted(self.tags.keys()))
-        return hash(tuple((k, self.tags[k]) for k in keys))
-
-    @property
-    def eliminatable(self):
-        return False
-
-    @property
-    def relocatable(self):
-        return True
-
-    def relocate(self, src, dst):
-        return self
+_UNIFIABLE_COMPARISONS = {
+    '__ne__', '__gt__', '__ge__', 'UGT', 'UGE', 'SGT', 'SGE',
+}
 
 
 class ConditionProcessor:
@@ -47,11 +32,13 @@ def __init__(self, condition_mapping=None):
         self._condition_mapping: Dict[str,Any] = {} if condition_mapping is None else condition_mapping
         self.reaching_conditions = {}
         self.guarding_conditions = {}
+        self._ast2annotations = {}
 
     def clear(self):
-        self._condition_mapping.clear()
-        self.reaching_conditions.clear()
-        self.guarding_conditions.clear()
+        self._condition_mapping = {}
+        self.reaching_conditions = {}
+        self.guarding_conditions = {}
+        self._ast2annotations = {}
 
     def recover_reaching_conditions(self, region, with_successors=False, jump_tables=None):
 
@@ -293,7 +280,8 @@ def remove_claripy_bool_asts(self, node, memo=None):
         elif isinstance(node, LoopNode):
 
             result = node.copy()
-            result.condition = self.convert_claripy_bool_ast(node.condition, memo=memo) if node.condition is not None else None
+            result.condition = self.convert_claripy_bool_ast(node.condition, memo=memo) if node.condition is not None \
+                else None
             result.sequence_node = self.remove_claripy_bool_asts(node.sequence_node, memo=memo)
             return result
 
@@ -462,8 +450,12 @@ def _extract_predicate(self, src_block, dst_block, edge_type):
             # TODO: THIS IS ABSOLUTELY A HACK. AT THIS MOMENT YOU SHOULD NOT ATTEMPT TO MAKE SENSE OF EXCEPTION EDGES.
             self.EXC_COUNTER += 1
             return self.claripy_ast_from_ail_condition(
-                ailment.Expr.BinaryOp(None, 'CmpEQ', (ailment.Expr.Register(0x400000 + self.EXC_COUNTER, None, self.EXC_COUNTER, 64),
-                                                      ailment.Expr.Const(None, None, self.EXC_COUNTER, 64)), False)
+                ailment.Expr.BinaryOp(
+                    None,
+                    'CmpEQ',
+                    (ailment.Expr.Register(0x400000 + self.EXC_COUNTER, None, self.EXC_COUNTER, 64),
+                     ailment.Expr.Const(None, None, self.EXC_COUNTER, 64)),
+                    False)
             )
 
         if type(src_block) is ConditionalBreakNode:
@@ -500,17 +492,12 @@ def _extract_predicate(self, src_block, dst_block, edge_type):
     # Expression conversion
     #
 
-    def _convert_extract(self, hi, lo, expr, annotations, memo=None):
+    def _convert_extract(self, hi, lo, expr, memo=None):
         # ailment does not support Extract. We translate Extract to Convert and shift.
         if lo == 0:
-            try:
-                tag_annotation = next(iter(anno for anno in annotations if isinstance(anno, TagsAnnotation)))
-                tags = tag_annotation.tags
-            except StopIteration:
-                tags = {}
+            # TODO: Keep track of tags
             return ailment.Expr.Convert(None, expr.size(), hi + 1, False,
                                         self.convert_claripy_bool_ast(expr, memo=memo),
-                                        **tags,
                                         )
 
         raise NotImplementedError("This case will be implemented once encountered.")
@@ -539,62 +526,52 @@ def convert_claripy_bool_ast_core(self, cond, memo):
         if cond.args[0] in self._condition_mapping:
             return self._condition_mapping[cond.args[0]]
 
-        def _binary_op_reduce(op, args, annotations: Iterable[claripy.Annotation], signed=False):
+        def _binary_op_reduce(op, args, signed=False):
             r = None
             for arg in args:
                 if r is None:
                     r = self.convert_claripy_bool_ast(arg, memo=memo)
                 else:
-                    try:
-                        tag_annotation = next(iter(anno for anno in annotations if isinstance(anno, TagsAnnotation)))
-                        tags = tag_annotation.tags
-                    except StopIteration:
-                        tags = {}
-                    r = ailment.Expr.BinaryOp(None, op, (r, self.convert_claripy_bool_ast(arg, memo=memo)), signed,
-                                              **tags
-                                              )
+                    # TODO: Keep track of tags
+                    r = ailment.Expr.BinaryOp(None, op, (r, self.convert_claripy_bool_ast(arg, memo=memo)), signed)
             return r
 
-        def _unary_op_reduce(op, arg, annotations: Iterable[claripy.Annotation]):
+        def _unary_op_reduce(op, arg):
             r = self.convert_claripy_bool_ast(arg, memo=memo)
-            try:
-                tag_annotation = next(iter(anno for anno in annotations if isinstance(anno, TagsAnnotation)))
-                tags = tag_annotation.tags
-            except StopIteration:
-                tags = {}
-            return ailment.Expr.UnaryOp(None, op, r, **tags)
+            # TODO: Keep track of tags
+            return ailment.Expr.UnaryOp(None, op, r)
 
         _mapping = {
-            'Not': lambda cond_: _unary_op_reduce('Not', cond_.args[0], cond_.annotations),
-            'And': lambda cond_: _binary_op_reduce('LogicalAnd', cond_.args, cond_.annotations),
-            'Or': lambda cond_: _binary_op_reduce('LogicalOr', cond_.args, cond_.annotations),
-            '__le__': lambda cond_: _binary_op_reduce('CmpLE', cond_.args, cond_.annotations, signed=True),
-            'SLE': lambda cond_: _binary_op_reduce('CmpLE', cond_.args, cond_.annotations, signed=True),
-            '__lt__': lambda cond_: _binary_op_reduce('CmpLT', cond_.args, cond_.annotations, signed=True),
-            'SLT': lambda cond_: _binary_op_reduce('CmpLT', cond_.args, cond_.annotations, signed=True),
-            'UGT': lambda cond_: _binary_op_reduce('CmpGT', cond_.args, cond_.annotations),
-            'UGE': lambda cond_: _binary_op_reduce('CmpGE', cond_.args, cond_.annotations),
-            '__gt__': lambda cond_: _binary_op_reduce('CmpGT', cond_.args, cond_.annotations, signed=True),
-            '__ge__': lambda cond_: _binary_op_reduce('CmpGE', cond_.args, cond_.annotations, signed=True),
-            'SGT': lambda cond_: _binary_op_reduce('CmpGT', cond_.args, cond_.annotations, signed=True),
-            'SGE': lambda cond_: _binary_op_reduce('CmpGE', cond_.args, cond_.annotations, signed=True),
-            'ULT': lambda cond_: _binary_op_reduce('CmpLT', cond_.args, cond_.annotations),
-            'ULE': lambda cond_: _binary_op_reduce('CmpLE', cond_.args, cond_.annotations),
-            '__eq__': lambda cond_: _binary_op_reduce('CmpEQ', cond_.args, cond_.annotations),
-            '__ne__': lambda cond_: _binary_op_reduce('CmpNE', cond_.args, cond_.annotations),
-            '__add__': lambda cond_: _binary_op_reduce('Add', cond_.args, cond_.annotations, signed=False),
-            '__sub__': lambda cond_: _binary_op_reduce('Sub', cond_.args, cond_.annotations),
-            '__mul__': lambda cond_: _binary_op_reduce('Mul', cond_.args, cond_.annotations),
-            '__xor__': lambda cond_: _binary_op_reduce('Xor', cond_.args, cond_.annotations),
-            '__or__': lambda cond_: _binary_op_reduce('Or', cond_.args, cond_.annotations, signed=False),
-            '__and__': lambda cond_: _binary_op_reduce('And', cond_.args, cond_.annotations),
-            '__lshift__': lambda cond_: _binary_op_reduce('Shl', cond_.args, cond_.annotations),
-            '__rshift__': lambda cond_: _binary_op_reduce('Sar', cond_.args, cond_.annotations),
-            'LShR': lambda cond_: _binary_op_reduce('Shr', cond_.args, cond_.annotations),
+            'Not': lambda cond_: _unary_op_reduce('Not', cond_.args[0]),
+            'And': lambda cond_: _binary_op_reduce('LogicalAnd', cond_.args),
+            'Or': lambda cond_: _binary_op_reduce('LogicalOr', cond_.args),
+            '__le__': lambda cond_: _binary_op_reduce('CmpLE', cond_.args, signed=True),
+            'SLE': lambda cond_: _binary_op_reduce('CmpLE', cond_.args, signed=True),
+            '__lt__': lambda cond_: _binary_op_reduce('CmpLT', cond_.args, signed=True),
+            'SLT': lambda cond_: _binary_op_reduce('CmpLT', cond_.args, signed=True),
+            'UGT': lambda cond_: _binary_op_reduce('CmpGT', cond_.args),
+            'UGE': lambda cond_: _binary_op_reduce('CmpGE', cond_.args),
+            '__gt__': lambda cond_: _binary_op_reduce('CmpGT', cond_.args, signed=True),
+            '__ge__': lambda cond_: _binary_op_reduce('CmpGE', cond_.args, signed=True),
+            'SGT': lambda cond_: _binary_op_reduce('CmpGT', cond_.args, signed=True),
+            'SGE': lambda cond_: _binary_op_reduce('CmpGE', cond_.args, signed=True),
+            'ULT': lambda cond_: _binary_op_reduce('CmpLT', cond_.args),
+            'ULE': lambda cond_: _binary_op_reduce('CmpLE', cond_.args),
+            '__eq__': lambda cond_: _binary_op_reduce('CmpEQ', cond_.args),
+            '__ne__': lambda cond_: _binary_op_reduce('CmpNE', cond_.args),
+            '__add__': lambda cond_: _binary_op_reduce('Add', cond_.args, signed=False),
+            '__sub__': lambda cond_: _binary_op_reduce('Sub', cond_.args),
+            '__mul__': lambda cond_: _binary_op_reduce('Mul', cond_.args),
+            '__xor__': lambda cond_: _binary_op_reduce('Xor', cond_.args),
+            '__or__': lambda cond_: _binary_op_reduce('Or', cond_.args, signed=False),
+            '__and__': lambda cond_: _binary_op_reduce('And', cond_.args),
+            '__lshift__': lambda cond_: _binary_op_reduce('Shl', cond_.args),
+            '__rshift__': lambda cond_: _binary_op_reduce('Sar', cond_.args),
+            'LShR': lambda cond_: _binary_op_reduce('Shr', cond_.args),
             'BVV': lambda cond_: ailment.Expr.Const(None, None, cond_.args[0], cond_.size()),
             'BoolV': lambda cond_: ailment.Expr.Const(None, None, True, 1) if cond_.args[0] is True
                                                                         else ailment.Expr.Const(None, None, False, 1),
-            'Extract': lambda cond_: self._convert_extract(*cond_.args, cond_.annotations, memo=memo),
+            'Extract': lambda cond_: self._convert_extract(*cond_.args, memo=memo),
         }
 
         if cond.op in _mapping:
@@ -696,17 +673,59 @@ def _dummy_bvs(condition):
         if r is NotImplemented:
             r = claripy.BVS("ailexpr_%r" % condition, condition.bits, explicit_name=True)
             self._condition_mapping[r.args[0]] = condition
-        else:
-            # don't lose tags
-            r = r.annotate(TagsAnnotation(**condition.tags))
+        # don't lose tags
+        self._ast2annotations[r] = condition.tags
         return r
 
     #
     # Expression simplification
     #
 
+    @staticmethod
+    def claripy_ast_to_sympy_expr(ast, memo=None):
+        if ast.op == "And":
+            return sympy.And(*(ConditionProcessor.claripy_ast_to_sympy_expr(arg, memo=memo) for arg in ast.args))
+        if ast.op == "Or":
+            return sympy.Or(*(ConditionProcessor.claripy_ast_to_sympy_expr(arg, memo=memo) for arg in ast.args))
+        if ast.op == "Not":
+            return sympy.Not(ConditionProcessor.claripy_ast_to_sympy_expr(ast.args[0], memo=memo))
+
+        if ast.op in _UNIFIABLE_COMPARISONS:
+            # unify comparisons to enable more simplification opportunities without going "deep" in sympy
+            inverse_op = getattr(ast.args[0], claripy.operations.inverse_operations[ast.op])
+            return sympy.Not(ConditionProcessor.claripy_ast_to_sympy_expr(inverse_op(ast.args[1]), memo=memo))
+
+        if memo is not None and ast in memo:
+            return memo[ast]
+        symbol = sympy.Symbol(str(hash(ast)))
+        if memo is not None:
+            memo[symbol] = ast
+        return symbol
+
+    @staticmethod
+    def sympy_expr_to_claripy_ast(expr, memo: Dict):
+        if expr.is_Symbol:
+            return memo[expr]
+        if isinstance(expr, sympy.Or):
+            return claripy.Or(*(ConditionProcessor.sympy_expr_to_claripy_ast(arg, memo) for arg in expr.args))
+        if isinstance(expr, sympy.And):
+            return claripy.And(*(ConditionProcessor.sympy_expr_to_claripy_ast(arg, memo) for arg in expr.args))
+        if isinstance(expr, sympy.Not):
+            return claripy.Not(ConditionProcessor.sympy_expr_to_claripy_ast(expr.args[0], memo))
+        if isinstance(expr, sympy.logic.boolalg.BooleanTrue):
+            return claripy.true
+        if isinstance(expr, sympy.logic.boolalg.BooleanFalse):
+            return claripy.false
+        raise RuntimeError("Unreachable reached")
+
     @staticmethod
     def simplify_condition(cond):
+        memo = { }
+        sympy_expr = ConditionProcessor.claripy_ast_to_sympy_expr(cond, memo=memo)
+        return ConditionProcessor.sympy_expr_to_claripy_ast(sympy.simplify_logic(sympy_expr), memo)
+
+    @staticmethod
+    def simplify_condition_deprecated(cond):
 
         # Z3's simplification may yield weird and unreadable results
         # hence we mostly rely on our own simplification. we only use Z3's simplification results when it returns a
@@ -723,8 +742,25 @@ def simplify_condition(cond):
         cond = simplified if simplified is not None else cond
         # simplified = ConditionProcessor._remove_redundant_terms(cond)
         # cond = simplified if simplified is not None else cond
+        # in the end, use claripy's simplification to handle really easy cases again
+        simplified = ConditionProcessor._simplify_trivial_cases(cond)
+        cond = simplified if simplified is not None else cond
         return cond
 
+    @staticmethod
+    def _simplify_trivial_cases(cond):
+
+        if cond.op == "And":
+            new_args = [ ]
+            for arg in cond.args:
+                claripy_simplified = claripy.simplify(arg)
+                if claripy.is_true(claripy_simplified):
+                    continue
+                new_args.append(arg)
+            return claripy.And(*new_args)
+
+        return None
+
     @staticmethod
     def _revert_short_circuit_conditions(cond):
 
@@ -825,7 +861,7 @@ def _expr_inside_collection(expr_, coll_) -> bool:
 
         if cond.op == "Or":
             args = [ ConditionProcessor._extract_common_subexpressions(arg) for arg in cond.args ]
-            args = [ (arg if arg is not None else ori_arg) for arg, ori_arg in zip(args, cond.args) ]
+            args = [(arg if arg is not None else ori_arg) for arg, ori_arg in zip(args, cond.args)]
 
             expr_ctrs = defaultdict(int)
             for arg in args:
@@ -838,6 +874,7 @@ def _expr_inside_collection(expr_, coll_) -> bool:
             common_exprs = [ ]
             for expr, ctr in expr_ctrs.items():
                 if ctr == len(args):
+                    # found a common one
                     common_exprs.append(expr)
 
             if not common_exprs:
@@ -846,7 +883,8 @@ def _expr_inside_collection(expr_, coll_) -> bool:
             new_args = [ ]
             for arg in args:
                 if arg.op == "And":
-                    new_subexprs = [ subexpr for subexpr in arg.args if not _expr_inside_collection(subexpr, common_exprs) ]
+                    new_subexprs = [ subexpr for subexpr in arg.args
+                                     if not _expr_inside_collection(subexpr, common_exprs) ]
                     new_args.append(claripy.And(*new_subexprs))
                 elif arg in common_exprs:
                     continue
@@ -902,8 +940,10 @@ def _replace_term_in_ast(ast: claripy.ast.Bool,
                     r1,
                     r1_with),))
         else:
-            if ast is r0: return r0_with
-            if ast is r1: return r1_with
+            if ast is r0:
+                return r0_with
+            if ast is r1:
+                return r1_with
             return ast
 
     @staticmethod
@@ -951,5 +991,6 @@ def _remove_redundant_terms(cond):
             # TODO: Finish the implementation
             print(term, "is redundant")
 
+
 # delayed import
-from .region_identifier import GraphRegion, MultiNode
+from .region_identifier import GraphRegion, MultiNode  # pylint:disable=wrong-import-position
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -162,6 +162,7 @@ def run(self, *args):
         'CppHeaderParser',
         'protobuf>=3.12.0',
         'nampa',
+        'sympy',
     ],
     setup_requires=[_UNICORN, 'pyvex'],
     extras_require={


Test patch: