def eval(self, p4_state): # z3 does not like to shift operators of different size # but casting both values could lead to missing an overflow # so after the operation cast the lvalue down to its original size lval_expr = p4_state.resolve_expr(self.lval) rval_expr = p4_state.resolve_expr(self.rval) if isinstance(lval_expr, int): # if lval_expr is an int we might get a signed value # the only size adjustment is to make the rval expr large enough # for some reason a small rval leads to erroneous shifts... return op.lshift(lval_expr, rval_expr) if isinstance(rval_expr, int): # shift is larger than width, all zero if lval_expr.size() <= rval_expr: return z3.BitVecVal(0, lval_expr.size()) # align the bitvectors to allow operations lval_is_bitvec = isinstance(lval_expr, z3.BitVecRef) rval_is_bitvec = isinstance(rval_expr, z3.BitVecRef) orig_lval_size = lval_expr.size() if lval_is_bitvec and rval_is_bitvec: # shift is larger than width, all zero if lval_expr.size() <= rval_expr.size(): lval_expr = z3_cast(lval_expr, rval_expr.size()) if lval_expr.size() > rval_expr.size(): rval_expr = z3_cast(rval_expr, lval_expr.size()) return z3_cast(op.lshift(lval_expr, rval_expr), orig_lval_size)
def operator(x, y): # this extra check is necessary because of z3... if z3.is_int(x) and isinstance(y, z3.BitVecRef): x = z3_cast(x, y) if z3.is_int(y) and isinstance(x, z3.BitVecRef): y = z3_cast(y, x) return op.or_(x, y)
def eval(self, p4_state): lval_expr = p4_state.resolve_expr(self.lval) rval_expr = p4_state.resolve_expr(self.rval) # align the bitvectors to allow operations lval_is_bitvec = isinstance(lval_expr, (z3.BitVecRef, z3.BitVecNumRef)) rval_is_bitvec = isinstance(rval_expr, (z3.BitVecRef, z3.BitVecNumRef)) if lval_is_bitvec and rval_is_bitvec: if lval_expr.size() < rval_expr.size(): rval_expr = z3_cast(rval_expr, lval_expr.size()) if lval_expr.size() > rval_expr.size(): lval_expr = z3_cast(lval_expr, rval_expr.size()) return self.operator(lval_expr, rval_expr)
def eval(self, p4_state): # resolve the expr before restoring the state if self.expr is None: expr = None else: expr = p4_state.resolve_expr(self.expr) chain_copy = p4_state.copy_expr_chain() # remove all expressions until we hit the end (typically a context) for p4z3_expr in chain_copy: p4_state.expr_chain.popleft() # this is tricky, we need to restore the state before returning # so update the p4_state and then move on to return the expression # this technique preserves the return value if isinstance(p4z3_expr, P4Context): p4z3_expr.restore_context(p4_state) break # since we popped the P4Context object that would take care of this # return the z3 expressions of the state AFTER restoring it if expr is None: # FIXME: issue1386 requires us to keep running down the chain... # Need to run down to the remaining execution path after the return. p4z3_expr = p4_state.pop_next_expr() expr = p4z3_expr.eval(p4_state) # functions cast the returned value down to their actual return type # FIXME: We can only cast bitvecs right now if isinstance(self.z3_type, z3.BitVecSortRef): return z3_cast(expr, self.z3_type) # we return a complex typed expression list, instantiate if isinstance(expr, list): instance = self.z3_type.instantiate("undefined") instance.set_list(expr) return instance return expr
def set_slice(self, ctx, lval, rval): slice_l = ctx.resolve_expr(lval.slice_l) slice_r = ctx.resolve_expr(lval.slice_r) lval = lval.val lval, slice_l, slice_r = self.find_nested_slice(lval, slice_l, slice_r) # need to resolve everything first, these can be members lval_expr = ctx.resolve_expr(lval) # z3 requires the extract value to be a bitvector, so we must cast ints # actually not sure where this could happen... if isinstance(lval_expr, int): lval_expr = lval_expr.as_bitvec rval_expr = ctx.resolve_expr(rval) lval_expr_max = lval_expr.size() - 1 if slice_l == lval_expr_max and slice_r == 0: # slice is full lval, nothing to do ctx.set_or_add_var(lval, rval_expr) return assemble = [] if slice_l < lval_expr_max: # left slice is smaller than the max, leave that chunk unchanged assemble.append(z3.Extract(lval_expr_max, slice_l + 1, lval_expr)) # fill the rval_expr into the slice # this cast is necessary to match the margins and to handle integers rval_expr = z3_cast(rval_expr, slice_l + 1 - slice_r) assemble.append(rval_expr) if slice_r > 0: # right slice is larger than zero, leave that chunk unchanged assemble.append(z3.Extract(slice_r - 1, 0, lval_expr)) rval_expr = z3.Concat(*assemble) ctx.set_or_add_var(lval, rval_expr) return
def eval(self, p4_state): context = p4_state.current_context() if self.expr is None: expr = None else: # resolve the expr before restoring the state expr = p4_state.resolve_expr(self.expr) if isinstance(context.return_type, z3.BitVecSortRef): expr = z3_cast(expr, context.return_type) # we return a complex typed expression list, instantiate if isinstance(expr, list): instance = gen_instance(p4_state, "undefined", context.return_type) instance.set_list(expr) expr = instance cond = z3.simplify( z3.And(z3.Not(z3.Or(*context.forward_conds)), context.tmp_forward_cond)) if not cond == z3.BoolVal(False): context.return_states.append((cond, p4_state.copy_attrs())) if expr is not None: context.return_exprs.append((cond, expr)) context.has_returned = True context.forward_conds.append(context.tmp_forward_cond)
def eval(self, p4_state): val = p4_state.resolve_expr(self.val) if self.instance_type is None: # no type defined, return just the value return val else: instance = gen_instance("None", self.instance_type) if isinstance(val, P4ComplexInstance): # copy the reference if we initialize with another complex type return copy.copy(val) if isinstance(instance, P4ComplexInstance): if isinstance(val, dict): instance.setValid() for name, val in val.items(): val_expr = p4_state.resolve_expr(val) instance.set_or_add_var(name, val_expr) elif isinstance(val, list): instance.set_list(val) else: raise RuntimeError( f"P4StructInitializer members {val} not supported!") return instance else: # cast the value we assign to the instance we create # TODO: I do not like this, there must be a better way to do this if isinstance(val, int) and isinstance( instance, (z3.BitVecSortRef, z3.BitVecRef)): val = z3_cast(val, instance.sort()) return val
def eval(self, p4_state): lval_expr = p4_state.resolve_expr(self.lval) # it can happen that we cast to a complex type... if isinstance(self.rval, P4ComplexType): instance = self.rval.instantiate(self.rval.name) initializer = P4Initializer(lval_expr, instance) return initializer.eval(p4_state) rval_expr = p4_state.resolve_expr(self.rval) # align the bitvectors to allow operations lval_is_bitvec = isinstance(lval_expr, z3.BitVecRef) rval_is_bitvec = isinstance(rval_expr, z3.BitVecRef) if lval_is_bitvec and rval_is_bitvec: if lval_expr.size() < rval_expr.size(): rval_expr = z3_cast(rval_expr, lval_expr.size()) if lval_expr.size() > rval_expr.size(): lval_expr = z3_cast(lval_expr, rval_expr.size()) return self.operator(lval_expr, rval_expr)
def eval(self, p4_state): log.debug("Assigning %s to %s ", self.rval, self.lval) rval_expr = p4_state.resolve_expr(self.rval) # in assignments all complex types values are copied if isinstance(rval_expr, StructInstance): rval_expr = copy.copy(rval_expr) if isinstance(rval_expr, int): lval = p4_state.resolve_expr(self.lval) rval_expr = z3_cast(rval_expr, lval.sort()) p4_state.set_or_add_var(self.lval, rval_expr)
def eval(self, p4_state): # z3 does not like to shift operators of different size # but casting both values could lead to missing an overflow # so after the operation cast the lvalue down to its original size lval_expr = p4_state.resolve_expr(self.lval) rval_expr = p4_state.resolve_expr(self.rval) if isinstance(lval_expr, int): # if x is an int we might get a signed value # we need to use the arithmetic right shift in this case return op.rshift(lval_expr, rval_expr) # align the bitvectors to allow operations lval_is_bitvec = isinstance(lval_expr, z3.BitVecRef) rval_is_bitvec = isinstance(rval_expr, z3.BitVecRef) orig_lval_size = lval_expr.size() if lval_is_bitvec and rval_is_bitvec: if lval_expr.size() < rval_expr.size(): lval_expr = z3_cast(lval_expr, rval_expr.size()) if lval_expr.size() > rval_expr.size(): rval_expr = z3_cast(rval_expr, lval_expr.size()) return z3_cast(z3.LShR(lval_expr, rval_expr), orig_lval_size)
def eval(self, p4_state): cond = p4_state.resolve_expr(self.cond) # handle side effects for function calls var_store, chain_copy = p4_state.checkpoint() then_val = p4_state.resolve_expr(self.then_val) then_vars = copy_attrs(p4_state.locals) p4_state.restore(var_store, chain_copy) else_val = p4_state.resolve_expr(self.else_val) p4_state.merge_attrs(cond, then_vars) then_expr = then_val else_expr = else_val # this is a really nasty hack, do not try this at home kids # because we have to be able to access the sub values again # we have to resolve the if condition in the case of complex types # we do this by splitting the if statement into a list # lists can easily be assigned to a target structure if isinstance(then_expr, P4ComplexInstance): then_expr = then_expr.flatten() if isinstance(else_expr, P4ComplexInstance): else_expr = else_expr.flatten() if isinstance(then_expr, list) and isinstance(else_expr, list): sub_cond = [] # handle nested complex types then_expr = self.unravel_datatype(then_val, then_expr) else_expr = self.unravel_datatype(else_val, else_expr) for idx, member in enumerate(then_expr): if_expr = z3.If(cond, member, else_expr[idx]) sub_cond.append(if_expr) return sub_cond then_is_const = isinstance(then_expr, (z3.BitVecRef, int)) else_is_const = isinstance(else_expr, (z3.BitVecRef, int)) if then_is_const and else_is_const: # align the bitvectors to allow operations, we cast ints downwards if else_expr.size() > then_expr.size(): else_expr = z3_cast(else_expr, then_expr.size()) if else_expr.size() < then_expr.size(): then_expr = z3_cast(then_expr, else_expr.size()) return z3.If(cond, then_expr, else_expr)
def copy_in(self, ctx, merged_args): param_buffer = OrderedDict() var_buffer = OrderedDict() for param_name, arg in merged_args.items(): # We need to resolve array indices appropriately arg_expr, _ = resolve_index(ctx, arg.p4_val) try: param_val = ctx.resolve_reference(param_name) var_buffer[param_name] = (arg.mode, arg_expr, param_val) except RuntimeError: # if the variable name does not exist, set the value to None var_buffer[param_name] = (arg.mode, arg_expr, None) # Sometimes expressions are passed, resolve those first arg_expr = ctx.resolve_expr(arg_expr) # it can happen that we receive a list # infer the type, generate, and set try: p4_type = ctx.resolve_type(arg.p4_type) except KeyError: # this is a generic type, we need to bind for this scope # FIXME: Clean this up p4_type = arg_expr.sort() ctx.add_type(arg.p4_type, p4_type) if isinstance(arg_expr, list): # if the type is undefined, do nothing if isinstance(p4_type, P4ComplexType): arg_instance = ctx.gen_instance(UNDEF_LABEL, p4_type) arg_instance.set_list(arg_expr) arg_expr = arg_instance # it is possible to pass an int as value, we need to cast it elif isinstance(arg_expr, int): arg_expr = z3_cast(arg_expr, p4_type) # need to work with an independent copy # the purpose is to handle indirect assignments in an action if arg.mode in ("in", "inout") and isinstance( arg_expr, StructInstance): arg_expr = copy.copy(arg_expr) if arg.mode == "out": # outs are left-values so the arg must be a string # infer the type value at runtime, param does not work yet # outs reset the input # In the case that the instance is a complex type make sure # to propagate the variable through all its members log.debug("Resetting %s to %s", arg_expr, param_name) arg_expr = ctx.gen_instance(UNDEF_LABEL, p4_type) log.debug("Copy-in: %s to %s", arg_expr, param_name) # buffer the value, do NOT set it yet param_buffer[param_name] = arg_expr return param_buffer, var_buffer
def eval(self, ctx): # An assignment, written with the = sign, first evaluates its left # sub-expression to an l-value, then evaluates its right sub-expression # to a value, and finally copies the value into the l-value. Derived # types (e.g. structs) are copied recursively, and all components of # headers are copied, including “validity” bits. Assignment is not # defined for extern values. log.debug("Assigning %s to %s ", self.rval, self.lval) lval, _ = resolve_index(ctx, self.lval) rval_expr = ctx.resolve_expr(self.rval) # in assignments all complex types values are copied if isinstance(rval_expr, StructInstance): rval_expr = copy.copy(rval_expr) if isinstance(rval_expr, int): rval_expr = z3_cast(rval_expr, ctx.resolve_expr(self.lval).sort()) ctx.set_or_add_var(lval, rval_expr)
def set_context(self, p4_state, merged_args, ref_criteria): # we have to subclass because of slight different behavior # inout and out parameters are not undefined they are set # with a new free constant param_buffer = OrderedDict() for param_name, arg in merged_args.items(): # Sometimes expressions are passed, resolve those first arg_expr = p4_state.resolve_expr(arg.p4_val) # for now use the param name, not the arg_name constructed here # FIXME: there are some passes that rename causing issues arg_name = f"{self.name}_{param_name}" # it can happen that we receive a list # infer the type, generate, and set if isinstance(arg_expr, list): # if the type is undefined, do nothing if isinstance(arg.p4_type, P4ComplexType): arg_instance = gen_instance(arg_name, arg.p4_type) arg_instance.set_list(arg_expr) arg_expr = arg_instance if arg.is_ref in ref_criteria: # outs are left-values so the arg must be a string # infer the type value at runtime, param does not work yet # outs reset the input # In the case that the instance is a complex type make sure # to propagate the variable through all its members log.debug("Resetting %s to %s", arg_expr, param_name) if isinstance(arg_expr, P4ComplexInstance): # assume that for inout header validity is not touched if arg.is_ref == "inout": arg_expr.bind_to_name(arg_name) else: arg_expr = arg_expr.p4z3_type.instantiate(arg_name) # we do not know whether the expression is valid afterwards arg_expr.propagate_validity_bit() else: arg_expr = z3.Const(f"{param_name}", arg_expr.sort()) log.debug("Copy-in: %s to %s", arg_expr, param_name) # it is possible to pass an int as value, we need to cast it if isinstance(arg_expr, int) and isinstance( arg.p4_type, (z3.BitVecSortRef, z3.BitVecRef)): arg_expr = z3_cast(arg_expr, arg.p4_type) # buffer the value, do NOT set it yet param_buffer[param_name] = arg_expr # now we can set the arguments without influencing subsequent variables for param_name, param_val in param_buffer.items(): p4_state.set_or_add_var(param_name, param_val)
def eval(self, p4_state): log.debug("Assigning %s to %s ", self.rval, self.lval) rval_expr = p4_state.resolve_expr(self.rval) # in assignments all complex types values are copied if isinstance(rval_expr, P4ComplexInstance): rval_expr = copy.copy(rval_expr) # make sure the assignment is aligned appropriately # this can happen because we also evaluate before the # BindTypeVariables pass # we can only align if tmp_val is a bitvector # example test: instance_overwrite.p4 lval = p4_state.resolve_expr(self.lval) if isinstance(rval_expr, int) and isinstance( lval, (z3.BitVecSortRef, z3.BitVecRef)): rval_expr = z3_cast(rval_expr, lval.sort()) p4_state.set_or_add_var(self.lval, rval_expr) p4z3_expr = p4_state.pop_next_expr() return p4z3_expr.eval(p4_state)
def copy_in(self, p4_state, merged_args): param_buffer = OrderedDict() for param_name, arg in merged_args.items(): # Sometimes expressions are passed, resolve those first arg_expr = p4_state.resolve_expr(arg.p4_val) # for now use the param name, not the arg_name constructed here # FIXME: there are some passes that rename causing issues arg_name = "undefined" # f"{self.name}_{param_name}" # it can happen that we receive a list # infer the type, generate, and set p4_type = resolve_type(p4_state, arg.p4_type) if isinstance(arg_expr, list): # if the type is undefined, do nothing if isinstance(p4_type, P4ComplexType): arg_instance = gen_instance(p4_state, arg_name, p4_type) arg_instance.set_list(arg_expr) arg_expr = arg_instance # it is possible to pass an int as value, we need to cast it elif isinstance(arg_expr, int): arg_expr = z3_cast(arg_expr, p4_type) # need to work with an independent copy # the purpose is to handle indirect assignments in an action if arg.mode in ("in", "inout") and isinstance( arg_expr, StructInstance): arg_expr = copy.copy(arg_expr) if arg.mode == "out": # outs are left-values so the arg must be a string # infer the type value at runtime, param does not work yet # outs reset the input # In the case that the instance is a complex type make sure # to propagate the variable through all its members log.debug("Resetting %s to %s", arg_expr, param_name) arg_expr = gen_instance(p4_state, arg_name, p4_type) log.debug("Copy-in: %s to %s", arg_expr, param_name) # buffer the value, do NOT set it yet param_buffer[param_name] = arg_expr return param_buffer
def eval(self, ctx): if self.expr is None: expr = None else: # resolve the expr before restoring the state expr = ctx.resolve_expr(self.expr) if isinstance(ctx.return_type, z3.BitVecSortRef): expr = z3_cast(expr, ctx.return_type) # we return a complex typed expression list, instantiate if isinstance(expr, list): # name is meaningless here so keep it empty instance = ctx.gen_instance("", ctx.return_type) instance.set_list(expr) expr = instance cond = z3.simplify(z3.And(z3.Not(z3.Or(*ctx.forward_conds)), ctx.tmp_forward_cond)) if not z3.is_false(cond): ctx.return_states.append((cond, ctx.copy_attrs())) if expr is not None: ctx.return_exprs.append((cond, expr)) ctx.has_returned = True ctx.forward_conds.append(ctx.tmp_forward_cond)
def operator(x, y): # for some reason, z3 borks if you use an int as x? if isinstance(x, int) and isinstance(y, z3.BitVecRef): x = z3_cast(x, y) return op.sub(x, y)