Skip to content

Commit

Permalink
Convert unininitalized parameter to an annotation (#581)
Browse files Browse the repository at this point in the history
* Convert unininitalized parameter to an annotation

* Make UninitializedAnnotation non-eliminatable

* Implement __eq__ for UninitializedAnnotation
  • Loading branch information
twizmwazin authored Jan 2, 2025
1 parent a32e4e1 commit 0f437a4
Show file tree
Hide file tree
Showing 8 changed files with 34 additions and 47 deletions.
3 changes: 0 additions & 3 deletions claripy/algorithm/simplify.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,6 @@ def simplify(expr: T) -> T:
simplification_cache[expr.hash()] = expr
return expr

# Copy some parameters (that should really go to the Annotation backend)
simplified._uninitialized = expr.uninitialized

# dealing with annotations
if expr.annotations:
ast_args = tuple(a for a in expr.args if isinstance(a, Base))
Expand Down
18 changes: 18 additions & 0 deletions claripy/annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,21 @@ def __hash__(self):

def __repr__(self):
return f"<RegionAnnotation {self.region_id}:{self.offset:#08x}>"


class UninitializedAnnotation(Annotation):
"""
Use UninitializedAnnotation to annotate ASTs that are uninitialized.
"""

eliminatable = False
relocatable = True

def __hash__(self):
return hash("uninitialized")

def __eq__(self, other):
return isinstance(other, UninitializedAnnotation)

def __repr__(self):
return "<UninitializedAnnotation>"
28 changes: 0 additions & 28 deletions claripy/ast/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,17 +112,13 @@ class Base:
# Caching
_cached_encoded_name: bytes | None

# Extra information
_uninitialized: bool

__slots__ = [
"__weakref__",
"_cached_encoded_name",
"_errored",
"_hash",
"_relocatable_annotations",
"_uneliminatable_annotations",
"_uninitialized",
"annotations",
"args",
"depth",
Expand All @@ -143,7 +139,6 @@ def __new__( # pylint:disable=redefined-builtin
symbolic: bool | None = None,
variables: frozenset[str] | None = None,
errored: set[Backend] | None = None,
uninitialized: bool = False,
annotations: tuple[Annotation, ...] = (),
skip_child_annotations: bool = False,
length: int | None = None,
Expand Down Expand Up @@ -217,7 +212,6 @@ def __new__( # pylint:disable=redefined-builtin
symbolic=symbolic,
length=length,
errored=errored,
uninitialized=uninitialized,
annotations=annotations,
encoded_name=encoded_name,
depth=depth,
Expand All @@ -240,7 +234,6 @@ def make_like(
annotations: tuple[Annotation, ...] | None = None,
variables: frozenset[str] | None = None,
symbolic: bool | None = None,
uninitialized: bool = False,
skip_child_annotations: bool = False,
length: int | None = None,
) -> Self:
Expand All @@ -253,7 +246,6 @@ def make_like(
and annotations
and variables is None
and symbolic is None
and uninitialized is False
and skip_child_annotations
and length is not None
):
Expand Down Expand Up @@ -281,7 +273,6 @@ def make_like(
symbolic=self.symbolic,
annotations=annotations,
length=length,
uninitialized=self._uninitialized,
)

result._hash = h
Expand All @@ -299,8 +290,6 @@ def make_like(
annotations = simplified.annotations
if variables is None and op in all_operations:
variables = self.variables
if uninitialized is None:
uninitialized = self._uninitialized
if symbolic is None and op in all_operations:
symbolic = self.symbolic

Expand All @@ -309,7 +298,6 @@ def make_like(
args if simplified is None else simplified.args,
annotations=annotations,
variables=variables,
uninitialized=uninitialized,
symbolic=symbolic,
skip_child_annotations=skip_child_annotations,
length=length,
Expand All @@ -324,7 +312,6 @@ def __a_init__(
symbolic: bool | None = None,
length: int | None = None,
errored: set[Backend] | None = None,
uninitialized: bool = False,
annotations: tuple[Annotation, ...] | None = None,
encoded_name: bytes | None = None,
depth: int | None = None,
Expand Down Expand Up @@ -355,8 +342,6 @@ def __a_init__(

self._errored = errored if errored is not None else set()

self._uninitialized = uninitialized

if len(self.args) == 0:
raise ClaripyOperationError("AST with no arguments!")

Expand Down Expand Up @@ -899,16 +884,3 @@ def cardinality(self) -> int:
@property
def concrete(self) -> bool:
return not self.symbolic

@property
def uninitialized(self) -> bool:
"""
Whether this AST comes from an uninitialized dereference or not. It's only used in under-constrained symbolic
execution mode.
:returns: True/False/None (unspecified).
"""

# TODO: It should definitely be moved to the proposed Annotation backend.

return self._uninitialized
4 changes: 2 additions & 2 deletions claripy/ast/bv.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,9 +291,9 @@ def SI(
)


def TSI(bits, name=None, uninitialized=False, explicit_name=None):
def TSI(bits, name=None, explicit_name=None):
name = "unnamed" if name is None else name
return BVS(name, bits, uninitialized=uninitialized, explicit_name=explicit_name)
return BVS(name, bits, explicit_name=explicit_name)


def ESI(bits, **kwargs):
Expand Down
5 changes: 1 addition & 4 deletions claripy/ast/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,11 @@ def indexOf(self, pattern, start_idx):
return StrIndexOf(self, pattern, start_idx)


def StringS(name, uninitialized=False, explicit_name=False, **kwargs):
def StringS(name, explicit_name=False, **kwargs):
"""
Create a new symbolic string (analogous to z3.String())
:param name: The name of the symbolic string (i. e. the name of the variable)
:param uninitialized: Whether this value should be counted as an "uninitialized" value in the course of an
analysis.
:param bool explicit_name: If False, an identifier is appended to the name to ensure uniqueness.
:returns: The String object representing the symbolic string
Expand All @@ -63,7 +61,6 @@ def StringS(name, uninitialized=False, explicit_name=False, **kwargs):
"StringS",
(n,),
symbolic=True,
uninitialized=uninitialized,
variables=frozenset((n,)),
**kwargs,
)
Expand Down
15 changes: 12 additions & 3 deletions claripy/backends/backend_vsa/backend_vsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from functools import reduce

import claripy
from claripy.annotation import RegionAnnotation, StridedIntervalAnnotation
from claripy.annotation import RegionAnnotation, StridedIntervalAnnotation, UninitializedAnnotation
from claripy.ast import BV, Base
from claripy.backends.backend import Backend
from claripy.backends.backend_vsa.balancer import Balancer
Expand Down Expand Up @@ -270,6 +270,11 @@ def apply_annotation(self, o, a):
raise ClaripyVSAError(f"Unsupported offset type {type(offset)}")
return vs

if isinstance(a, UninitializedAnnotation):
o2 = o.copy()
o2.uninitialized = True
return o2

if isinstance(o, ValueSet) and isinstance(a, StridedIntervalAnnotation):
si = CreateStridedInterval(
bits=o.bits,
Expand All @@ -282,6 +287,10 @@ def apply_annotation(self, o, a):
vs._merge_si(a.region_id, a.region_base_addr, si)
return vs

if isinstance(o, BoolResult) and isinstance(a, UninitializedAnnotation):
# TODO: Do we want to do anything here?
return o

raise ValueError(f"Unsupported annotation type {type(a)} for object {type(o)}")

@staticmethod
Expand Down Expand Up @@ -462,8 +471,8 @@ def widen(self, ast):
return ret

@staticmethod
def CreateTopStridedInterval(bits, name=None, uninitialized=False):
return StridedInterval.top(bits, name, uninitialized=uninitialized)
def CreateTopStridedInterval(bits, name=None):
return StridedInterval.top(bits, name)

@staticmethod
def constraint_to_si(expr):
Expand Down
4 changes: 1 addition & 3 deletions claripy/backends/backend_z3.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,9 +289,7 @@ def __init__(self, reuse_z3_solver=None, ast_cache_size=10000):

self.solve_count = 0

# XXX this is a HUGE HACK that should be removed whenever uninitialized gets moved to the
# "proposed annotation backend" or wherever will prevent it from being part of the object
# identity. also whenever the VSA attributes get the fuck out of BVS as well
# This is necessary to be able to carry over annotations when round-tripping through Z3
@property
def bvs_annotations(self) -> dict[bytes, tuple[Annotation, ...]]:
if not hasattr(self._tls, "bvs_annotations"):
Expand Down
4 changes: 0 additions & 4 deletions claripy/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,6 @@ def _op(*args):
if calc_length is not None:
kwargs["length"] = calc_length(*fixed_args)

kwargs["uninitialized"] = None
if any(a.uninitialized is True for a in args if isinstance(a, claripy.ast.Base)):
kwargs["uninitialized"] = True

return return_type(name, fixed_args, **kwargs)

_op.calc_length = calc_length
Expand Down

0 comments on commit 0f437a4

Please sign in to comment.