Repository: angr/angr

Issue:
Decompilation of 16 bit operands, sign is not correct
Source:
```
            mov     eax, dword ptr [esp + 4]
            mov     ecx, dword ptr [esp + 8]
            sub ax,42
            sub ax,cx
            movzx eax,ax
            ret
```
b'\x8bD$\x04\x8bL$\x08f\x83\xe8Bf)\xc8\x0f\xb7\xc0\xc3'
Result:
```
int _start(unsigned int v0, unsigned int a0)
{
    return (short)v0 - 66 - (short)a0;
}
```
But it does not take into account movzx. It should be something like:
```
unsigned int _start(unsigned int v0, unsigned int a0)
{
    return (unsigned short) ( (short)v0 - 66 - (short)a0 );
}
```



Discussion:

Resolving patch:
diff --git a/angr/analyses/decompiler/structured_codegen/c.py b/angr/analyses/decompiler/structured_codegen/c.py
--- a/angr/analyses/decompiler/structured_codegen/c.py
+++ b/angr/analyses/decompiler/structured_codegen/c.py
@@ -57,18 +57,23 @@ def squash_array_reference(ty):
     return ty
 
 def qualifies_for_simple_cast(ty1, ty2):
-    # casting ty1 to ty2 - can this happen precisely?
+    # converting ty1 to ty2 - can this happen precisely?
     # used to decide whether to add explicit typecasts instead of doing *(int*)&v1
     return ty1.size == ty2.size and \
            isinstance(ty1, (SimTypeInt, SimTypeChar, SimTypeNum, SimTypePointer)) and \
            isinstance(ty2, (SimTypeInt, SimTypeChar, SimTypeNum, SimTypePointer))
 
 def qualifies_for_implicit_cast(ty1, ty2):
-    # casting ty1 to ty2 - can this happen without a cast?
+    # converting ty1 to ty2 - can this happen without a cast?
     # used to decide whether to omit typecasts from output during promotion
-    return isinstance(ty1, (SimTypeInt, SimTypeChar, SimTypeNum)) and \
-           isinstance(ty2, (SimTypeInt, SimTypeChar, SimTypeNum)) and \
-           ty1.size <= ty2.size
+    # this function need to answer the question:
+    # when does having a cast vs having an implicit promotion affect the result?
+    # the answer: I DON'T KNOW
+    if not isinstance(ty1, (SimTypeInt, SimTypeChar, SimTypeNum)) or \
+           not isinstance(ty2, (SimTypeInt, SimTypeChar, SimTypeNum)):
+        return False
+
+    return ty1.size <= ty2.size
 
 def extract_terms(expr: 'CExpression') -> Tuple[int, List[Tuple[int, 'CExpression']]]:
     if isinstance(expr, CConstant):
@@ -2159,14 +2164,22 @@ def _access(
         def bail_out():
             result = reduce(
                 lambda a1, a2: CBinaryOp("Add", a1, a2, codegen=self),
-                (CBinaryOp(
-                    "Mul",
-                    CConstant(c, t.type, codegen=self),
-                    t
+                (
+                    CBinaryOp(
+                        "Mul",
+                        CConstant(c, t.type, codegen=self),
+                        t
+                            if not isinstance(t.type, SimTypePointer)
+                            else CTypeCast(t.type, SimTypePointer(SimTypeBottom()), t, codegen=self),
+                        codegen=self
+                    )
+                    if c != 1
+                    else t
                         if not isinstance(t.type, SimTypePointer)
-                        else CTypeCast(t.type, SimTypePointer(SimTypeBottom()), t, codegen=self),
-                    codegen=self
-                ) if c != 1 else t for c, t in o_terms))
+                        else CTypeCast(t.type, SimTypePointer(SimTypeBottom()), t, codegen=self)
+                    for c, t in o_terms
+                )
+            )
             if o_constant != 0:
                 result = CBinaryOp("Add", CConstant(o_constant, SimTypeInt(), codegen=self), result, codegen=self)
 
@@ -2184,10 +2197,13 @@ def bail_out():
         kernel = None
         while i < len(terms):
             c, t = terms[i]
-            if c == 1 and isinstance(unpack_typeref(t.type), SimTypePointer):
+            if isinstance(unpack_typeref(t.type), SimTypePointer):
                 if kernel is not None:
                     l.warning("Summing two different pointers together. Uh oh!")
                     return bail_out()
+                if c != 1:
+                    l.warning("Multiplying a pointer by a constant??")
+                    return bail_out()
                 kernel = t
                 terms.pop(i)
                 continue
@@ -2648,7 +2664,7 @@ def _handle_Expr_BinaryOp(self, expr: BinaryOp):
                          collapsed=expr.depth > self.binop_depth_cutoff,
                          )
 
-    def _handle_Expr_Convert(self, expr):
+    def _handle_Expr_Convert(self, expr: Expr.Convert):
         if 64 >= expr.to_bits > 32:
             dst_type = SimTypeLongLong()
         elif 32 >= expr.to_bits > 16:
@@ -2662,6 +2678,8 @@ def _handle_Expr_Convert(self, expr):
         else:
             raise UnsupportedNodeTypeError("Unsupported conversion bits %s." % expr.to_bits)
 
+        dst_type.signed = expr.is_signed
+
         return CTypeCast(
             None,
             dst_type.with_arch(self.project.arch),
diff --git a/angr/sim_type.py b/angr/sim_type.py
--- a/angr/sim_type.py
+++ b/angr/sim_type.py
@@ -227,6 +227,13 @@ def __init__(self, label=None):
     def __repr__(self):
         return self.label or 'BOT'
 
+    def c_repr(self, name=None, full=0, memo=None, indent=0):
+        if name is None:
+            return 'void'
+        else:
+            return f'{"void" if self.label is None else self.label} {name}'
+
+
     def _init_str(self):
         return "%s(%s)" % (
             self.__class__.__name__,


Test patch:
diff --git a/tests/test_decompiler.py b/tests/test_decompiler.py
--- a/tests/test_decompiler.py
+++ b/tests/test_decompiler.py
@@ -1215,6 +1215,9 @@ def test_struct_access(self):
         unified.name = "b_ptr"
         unified.renamed = True
 
+        # NOTE TO WHOEVER SEES THIS
+        # this is an INCOMPLETE way to set the type of an argument
+        # you also need to change the function prototype
         vmi.set_variable_type(
             next(iter(vmi.find_variables_by_register('rdi'))),
             SimTypeInt(),