def summarize_execution( fn: Callable, args: Sequence[object] = (), kwargs: Mapping[str, object] = None, detach_path: bool = True, ) -> ExecutionResult: if not kwargs: kwargs = {} ret = None exc = None try: symbolic_ret = fn(*args, **kwargs) if detach_path: context_statespace().detach_path() _ret = realize(symbolic_ret) # TODO, this covers up potential issues with return types. Handle differently? # summarize iterators as the values they produce: if hasattr(_ret, "__next__"): ret = list(_ret) else: ret = _ret except BaseException as e: if isinstance(e, (UnexploredPath, IgnoreAttempt)): raise if in_debug(): debug("hit exception:", type(e), e, test_stack(e.__traceback__)) exc = e if detach_path: context_statespace().detach_path() args = tuple(realize(a) for a in args) kwargs = {k: realize(v) for (k, v) in kwargs.items()} return ExecutionResult(ret, exc, args, kwargs)
def proxy_for_type(typ: Type, varname: str, meet_class_invariants=True, allow_subtypes=False) -> object: space = context_statespace() typ = normalize_pytype(typ) origin = origin_of(typ) type_args = type_args_of(typ) # special cases if isinstance(typ, type) and issubclass(typ, enum.Enum): enum_values = list(typ) # type:ignore for enum_value in enum_values[:-1]: if space.smt_fork(): return enum_value return enum_values[-1] proxy_factory = _SIMPLE_PROXIES.get(origin) if proxy_factory: def recursive_proxy_factory(t: Type): return proxy_for_type(t, varname + space.uniq(), allow_subtypes=allow_subtypes) recursive_proxy_factory.space = space # type: ignore recursive_proxy_factory.pytype = typ # type: ignore recursive_proxy_factory.varname = varname # type: ignore return proxy_factory(recursive_proxy_factory, *type_args) if allow_subtypes and typ is not object: typ = choose_type(space, typ) return proxy_for_class(typ, varname, meet_class_invariants)
def proxy_class_as_concrete(typ: Type, varname: str) -> object: ''' Try aggressively to create an instance of a class with symbolic members. ''' data_members = get_type_hints(typ) # Special handling for some magical types: if issubclass(typ, tuple): args = {k: proxy_for_type(t, varname + '.' + k) for (k, t) in data_members.items()} return typ(**args) # type: ignore elif sys.version_info >= (3, 8) and type(typ) is typing._TypedDictMeta: # type: ignore optional_keys = getattr(typ, "__optional_keys__", ()) keys = (k for k in data_members.keys() if k not in optional_keys or context_statespace().smt_fork()) return {k: proxy_for_type(data_members[k], varname + '.' + k) for k in keys} constructor_params = get_constructor_params(typ) if constructor_params is None: debug(f'unable to create concrete instance of {typ} due to bad constructor') return _MISSING EMPTY = inspect.Parameter.empty args = {} for param in constructor_params: name = param.name smtname = varname + '.' + name annotation = param.annotation if annotation is not EMPTY: args[name] = proxy_for_type(annotation, smtname) else: if param.default is EMPTY: debug('unable to create concrete instance of', typ, 'due to lack of type annotation on', name) return _MISSING else: # TODO: consider whether we should fall back to a proxy # instead of letting this slide. Or try both paths? pass try: obj = typ(**args) except BaseException as e: debug('unable to create concrete proxy with init:', e) return _MISSING # Additionally, for any typed members, ensure that they are also # symbolic. (classes sometimes have valid states that are not directly # constructable) for (key, typ) in data_members.items(): if isinstance(getattr(obj, key, None), CrossHairValue): continue symbolic_value = proxy_for_type(typ, varname + '.' + key) try: setattr(obj, key, symbolic_value) except Exception as e: debug('Unable to assign symbolic value to concrete class:', e) # TODO: consider whether we should fall back to a proxy # instead of letting this slide. Or try both paths? return obj
def make_fake_object(cls: type, varname: str) -> object: constructor = get_smt_proxy_type(cls) try: proxy = constructor() except TypeError as e: # likely the type has a __new__ that expects arguments raise CrosshairUnsupported(f'Unable to proxy {name_of_type(cls)}: {e}') for name, typ in get_type_hints(cls).items(): origin = getattr(typ, '__origin__', None) if origin is Callable: continue value = proxy_for_type( typ, varname + '.' + name + context_statespace().uniq()) object.__setattr__(proxy, name, value) return proxy
def gen_args(sig: inspect.Signature) -> inspect.BoundArguments: args = sig.bind_partial() space = context_statespace() for param in sig.parameters.values(): smt_name = param.name + space.uniq() proxy_maker = lambda typ, **kw: proxy_for_type( typ, smt_name, allow_subtypes=True, **kw) has_annotation = (param.annotation != inspect.Parameter.empty) value: object if param.kind == inspect.Parameter.VAR_POSITIONAL: if has_annotation: varargs_type = List[param.annotation] # type: ignore value = proxy_maker(varargs_type) else: value = proxy_maker(List[Any]) elif param.kind == inspect.Parameter.VAR_KEYWORD: if has_annotation: varargs_type = Dict[str, param.annotation] # type: ignore value = cast(dict, proxy_maker(varargs_type)) # Using ** on a dict requires concrete string keys. Force # instiantiation of keys here: value = {k.__str__(): v for (k, v) in value.items()} else: value = proxy_maker(Dict[str, Any]) else: is_self = param.name == 'self' # Object parameters should meet thier invariants iff they are not the # class under test ("self"). meet_class_invariants = not is_self allow_subtypes = not is_self if has_annotation: value = proxy_for_type(param.annotation, smt_name, meet_class_invariants, allow_subtypes) else: value = proxy_for_type(cast(type, Any), smt_name, meet_class_invariants, allow_subtypes) debug('created proxy for', param.name, 'as type:', type(value)) args.arguments[param.name] = value return args
def __ch_forget_contents__(self): cls = self.__ch_pytype__() clean = proxy_for_type(cls, context_statespace().uniq()) for name, val in self.__dict__.items(): self.__dict__[name] = clean.__dict__[name]
def attempt_call(conditions: Conditions, fn: Callable, short_circuit: ShortCircuitingContext, enforced_conditions: EnforcedConditions) -> CallAnalysis: space = context_statespace() bound_args = gen_args(conditions.sig) msg_gen = MessageGenerator(fn) with space.framework(): # TODO: looks wrong(-ish) to guard this with space.framework(). # Copy on custom objects may require patched builtins. (datetime.timedelta is one such case) original_args = copy.deepcopy(bound_args) space.checkpoint() lcls: Mapping[str, object] = bound_args.arguments # In preconditions, __old__ exists but is just bound to the same args. # This lets people write class invariants using `__old__` to, for example, # demonstrate immutability. lcls = {'__old__': AttributeHolder(lcls), **lcls} expected_exceptions = conditions.raises for precondition in conditions.pre: with ExceptionFilter(expected_exceptions) as efilter: with enforced_conditions.enabled_enforcement(), short_circuit: precondition_ok = precondition.evaluate(lcls) if not precondition_ok: debug('Failed to meet precondition', precondition.expr_source) return CallAnalysis(failing_precondition=precondition) if efilter.ignore: debug('Ignored exception in precondition.', efilter.analysis) return efilter.analysis elif efilter.user_exc is not None: (user_exc, tb) = efilter.user_exc debug('Exception attempting to meet precondition', precondition.expr_source, ':', user_exc, tb.format()) return CallAnalysis( failing_precondition=precondition, failing_precondition_reason= f'it raised "{repr(user_exc)} at {tb.format()[-1]}"') with ExceptionFilter(expected_exceptions) as efilter: with enforced_conditions.enabled_enforcement(), short_circuit: assert not space.running_framework_code __return__ = fn(*bound_args.args, **bound_args.kwargs) lcls = { **bound_args.arguments, '__return__': __return__, '_': __return__, '__old__': AttributeHolder(original_args.arguments), fn.__name__: fn } if efilter.ignore: debug('Ignored exception in function.', efilter.analysis) return efilter.analysis elif efilter.user_exc is not None: space.check_deferred_assumptions() (e, tb) = efilter.user_exc detail = name_of_type(type(e)) + ': ' + str(e) frame_filename, frame_lineno = frame_summary_for_fn(tb, fn) debug('exception while evaluating function body:', detail, frame_filename, 'line', frame_lineno) detail += ' ' + get_input_description(fn.__name__, original_args, _MISSING) return CallAnalysis(VerificationStatus.REFUTED, [ msg_gen.make(MessageType.EXEC_ERR, detail, frame_filename, frame_lineno, ''.join(tb.format())) ]) for argname, argval in bound_args.arguments.items(): if (conditions.mutable_args is not None and argname not in conditions.mutable_args): old_val, new_val = original_args.arguments[argname], argval if not deep_eq(old_val, new_val, set()): space.check_deferred_assumptions() detail = 'Argument "{}" is not marked as mutable, but changed from {} to {}'.format( argname, old_val, new_val) debug('Mutablity problem:', detail) return CallAnalysis( VerificationStatus.REFUTED, [msg_gen.make(MessageType.POST_ERR, detail, None, 0, '')]) (post_condition, ) = conditions.post with ExceptionFilter(expected_exceptions) as efilter: # TODO: re-enable post-condition short circuiting. This will require refactoring how # enforced conditions and short curcuiting interact, so that post-conditions are # selectively run when, and only when, performing a short circuit. #with enforced_conditions.enabled_enforcement(), short_circuit: isok = bool(post_condition.evaluate(lcls)) if efilter.ignore: debug('Ignored exception in postcondition.', efilter.analysis) return efilter.analysis elif efilter.user_exc is not None: space.check_deferred_assumptions() (e, tb) = efilter.user_exc detail = repr(e) + ' ' + get_input_description( fn.__name__, original_args, __return__, post_condition.addl_context) debug('exception while calling postcondition:', detail) failures = [ msg_gen.make(MessageType.POST_ERR, detail, post_condition.filename, post_condition.line, ''.join(tb.format())) ] return CallAnalysis(VerificationStatus.REFUTED, failures) if isok: debug('Postcondition confirmed.') return CallAnalysis(VerificationStatus.CONFIRMED) else: space.check_deferred_assumptions() detail = 'false ' + \ get_input_description( fn.__name__, original_args, __return__, post_condition.addl_context) debug(detail) failures = [ msg_gen.make(MessageType.POST_FAIL, detail, post_condition.filename, post_condition.line, '') ] return CallAnalysis(VerificationStatus.REFUTED, failures)
def eval_regex(re_string, flags, test_string, offset, endpos=None): py_patt = re.compile(re_string, flags) space = context_statespace() s = SymbolicStr("symstr" + space.uniq()) space.add(s.var == z3.StringVal(test_string)) return _match_pattern(py_patt, re_string, s, offset, endpos)
def eval_regex(re_string, flags, test_string, offset): py_patt = re.compile(re_string, flags) space = context_statespace() s = SmtStr('symstr' + space.uniq()) space.add(s.var == z3.StringVal(test_string)) return _match_pattern(py_patt, re_string, s, offset)