Exemple #1
0
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)
Exemple #2
0
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)
Exemple #3
0
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
Exemple #4
0
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
Exemple #5
0
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
Exemple #6
0
 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]
Exemple #7
0
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)
Exemple #9
0
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)