Ejemplo n.º 1
0
    def get_first_body_line(fn: Callable) -> Optional[int]:
        """
        Retrieve the first line of the body of the function ``fn``.

        :return:
            the line number of the first non-assert statement in the given function.

        :return:
            None if the function does not start with at least one assert statement.
        """
        _filename, first_fn_lineno, lines = sourcelines(fn)
        if not lines:
            return None
        ast_module = ast.parse(textwrap.dedent("".join(lines)))
        ast_fn = ast_module.body[0]
        if not isinstance(ast_fn, ast.FunctionDef):
            return None
        found_any_preconditions = False
        for statement in ast_fn.body:
            if isinstance(statement, ast.Assert):
                found_any_preconditions = True
                continue
            elif AssertsParser.is_string_literal(statement):
                # A docstring, keep looking:
                continue
            break
        if found_any_preconditions:
            return first_fn_lineno + (statement.lineno - 1)
        else:
            return None
Ejemplo n.º 2
0
def collect_options(thing: Any) -> AnalysisOptionSet:
    parent_opts = AnalysisOptionSet()
    is_package = thing.__name__ == getattr(thing, "__package__", None)
    if getattr(thing, "__module__", None):
        parent_opts = collect_options(sys.modules[thing.__module__])
    elif getattr(thing, "__package__", None):
        if is_package:
            parent_pkg, _, _ = thing.__package__.rpartition(".")
        else:
            parent_pkg = thing.__package__
        if parent_pkg:
            parent_opts = collect_options(sys.modules[parent_pkg])

    lines: Iterable[str]
    if is_package:
        try:
            lines = importlib.resources.read_text(thing,
                                                  "__init__.py").splitlines()
        except FileNotFoundError:
            lines = []
    else:
        _file, _start, lines = sourcelines(thing)
    directives = get_directives(lines)
    if inspect.ismodule(thing):
        # Only look at toplevel comments in modules
        # (we don't want to catch directives for functions inside it)
        # TODO: detect directives at other levels like classes etc and warn that they
        # will be ignored.
        directives = [(l, c, t) for (l, c, t) in directives if c == 0]
    my_opts = parse_directives(directives)
    return parent_opts.overlay(my_opts)
Ejemplo n.º 3
0
def add_completion_conditions(conditions: Conditions):
    """Assume a trivial postcondition of "True" when preconditions exist."""
    post = conditions.post
    if not post and conditions.pre:
        filename, line, _lines = sourcelines(conditions.src_fn)
        post.append(
            ConditionExpr(POSTCONDIITON, lambda vars: True, filename, line,
                          ""))
Ejemplo n.º 4
0
 def get_fn_conditions(self, ctxfn: FunctionInfo) -> Optional[Conditions]:
     fn_and_sig = ctxfn.get_callable()
     if fn_and_sig is None:
         return None
     (fn, sig) = fn_and_sig
     filename, first_line, _lines = sourcelines(fn)
     if isinstance(fn, types.BuiltinFunctionType):
         return Conditions(fn, fn, [], [], frozenset(), sig, frozenset(),
                           [])
     lines = list(get_doc_lines(fn))
     parse = parse_sections(lines, ("pre", "post", "raises"), filename)
     pre: List[ConditionExpr] = []
     raises: Set[Type[BaseException]] = set()
     post_conditions: List[ConditionExpr] = []
     mutable_args: Optional[FrozenSet[str]] = None
     if parse.mutable_expr is not None:
         mutable_args = frozenset(expr.strip().split(".")[0]
                                  for expr in parse.mutable_expr.split(",")
                                  if expr != "")
     for line_num, expr in parse.sections["pre"]:
         pre.append(
             condition_from_source_text(filename, line_num, expr,
                                        fn_globals(fn)))
     for line_num, expr in parse.sections["raises"]:
         if "#" in expr:
             expr = expr.split("#")[0]
         for exc_source in expr.split(","):
             try:
                 exc_type = eval(exc_source)
             except:
                 e = sys.exc_info()[1]
                 parse.syntax_messages.append(
                     ConditionSyntaxMessage(filename, line_num, str(e)))
                 continue
             if not issubclass(exc_type, BaseException):
                 parse.syntax_messages.append(
                     ConditionSyntaxMessage(
                         filename,
                         line_num,
                         f'"{exc_type}" is not an exception class',
                     ))
                 continue
             raises.add(exc_type)
     for line_num, expr in parse.sections["post"]:
         post_conditions.append(
             condition_from_source_text(filename, line_num, expr,
                                        fn_globals(fn)))
     return Conditions(
         fn,
         fn,
         pre,
         post_conditions,
         frozenset(raises),
         sig,
         mutable_args,
         parse.syntax_messages,
     )
Ejemplo n.º 5
0
    def get_fn_conditions(self, ctxfn: FunctionInfo) -> Optional[Conditions]:
        fn_and_sig = ctxfn.get_callable()
        if fn_and_sig is None:
            return None
        (fn, sig) = fn_and_sig
        # TODO replace this guard with package-level configuration?
        if (getattr(fn, "__module__", False)
                and fn.__module__.startswith("crosshair.")
                and not fn.__module__.endswith("_test")):
            return None
        try:
            first_body_line = AssertsParser.get_first_body_line(fn)
        except OSError:
            return None
        if first_body_line is None:
            return None

        filename, first_line, _lines = sourcelines(fn)

        @wraps(fn)
        def wrappedfn(*a, **kw):
            try:
                return NoEnforce(fn)(*a, **kw)
            except AssertionError as e:
                # TODO: check that this isn't failing at an early line in a different
                # file?
                _, lineno = frame_summary_for_fn(
                    fn, traceback.extract_tb(e.__traceback__))
                if lineno >= first_body_line:
                    raise

        post = [
            ConditionExpr(
                POSTCONDIITON,
                lambda _: True,
                filename,
                first_line,
                "",
            )
        ]
        return Conditions(
            wrappedfn,
            fn,
            [],  # (pre)
            post,
            raises=frozenset(parse_sphinx_raises(fn)),
            sig=sig,
            mutable_args=None,
            fn_syntax_messages=[],
        )
Ejemplo n.º 6
0
    def get_class_invariants(self, cls: type) -> List[ConditionExpr]:
        invariants = getattr(cls, "__invariants__", ())  # type: ignore
        ret = []

        def inv_eval(contract, kwargs):
            return contract.condition(self=kwargs["self"])

        for contract in invariants:
            filename, line_num, _lines = sourcelines(contract.condition)
            ret.append(
                ConditionExpr(
                    functools.partial(inv_eval, contract),
                    filename,
                    line_num,
                    self.contract_text(contract),
                ))
        return ret
Ejemplo n.º 7
0
def get_doc_lines(thing: object) -> Iterable[Tuple[int, str]]:
    doc = inspect.getdoc(thing)
    if doc is None:
        return
    _filename, line_num, lines = sourcelines(thing)  # type:ignore
    line_num += len(lines) - 1
    line_numbers = {}
    for line in reversed(lines):
        line_numbers[strip_comment_line(line)] = line_num
        line_num -= 1
    for line in doc.split("\n"):
        l = strip_comment_line(line)
        try:
            lineno = line_numbers[l]
        except KeyError:
            continue
        yield (lineno, line)
Ejemplo n.º 8
0
def collect_options(thing: Any) -> AnalysisOptionSet:
    parent_opts = AnalysisOptionSet()
    if getattr(thing, "__module__", None):
        parent_opts = collect_options(sys.modules[thing.__module__])
    elif hasattr(thing, "__package__"):
        if thing.__package__ and thing.__package__ != thing.__name__:
            parent_opts = collect_options(sys.modules[thing.__package__])

    _file, start, lines = sourcelines(thing)
    directives = get_directives(lines)
    if inspect.ismodule(thing):
        # Only look at toplevel comments in modules
        # (we don't want to catch directives for functions inside it)
        # TODO: detect directives at other levels like classes etc and warn that they
        # will be ignored.
        directives = [(l, c, t) for (l, c, t) in directives if c == 0]
    my_opts = parse_directives(directives)
    return parent_opts.overlay(my_opts)
Ejemplo n.º 9
0
    def get_fn_conditions(self, ctxfn: FunctionInfo) -> Optional[Conditions]:
        fn_and_sig = ctxfn.get_callable()
        if fn_and_sig is None:
            return None
        (fn, sig) = fn_and_sig
        if not getattr(fn, "is_hypothesis_test", False):
            return None
        fuzz_one = getattr(getattr(fn, "hypothesis", None), "fuzz_one_input",
                           None)
        if fuzz_one is None:
            return None

        filename, first_line, _lines = sourcelines(fn)
        post = [
            ConditionExpr(
                POSTCONDIITON,
                lambda _: True,
                filename,
                first_line,
                "",
            )
        ]
        sig = inspect.Signature(parameters=[
            inspect.Parameter(
                "payload", inspect.Parameter.POSITIONAL_ONLY, annotation=bytes)
        ])

        return Conditions(
            fuzz_one,
            fn,
            [],  # (pre)
            post,
            raises=frozenset(),
            sig=sig,
            mutable_args=None,
            fn_syntax_messages=[],
            counterexample_description_maker=partial(
                self._format_counterexample, fn),
        )
Ejemplo n.º 10
0
    def get_fn_conditions(self, ctxfn: FunctionInfo) -> Optional[Conditions]:
        if icontract is None:
            return None
        fn_and_sig = ctxfn.get_callable()
        if fn_and_sig is None:
            return None
        (fn, sig) = fn_and_sig

        checker = icontract._checkers.find_checker(func=fn)  # type: ignore
        contractless_fn = fn  # type: ignore
        while (hasattr(contractless_fn, "__is_invariant_check__")
               or hasattr(contractless_fn, "__preconditions__")
               or hasattr(contractless_fn, "__postconditions__")):
            contractless_fn = contractless_fn.__wrapped__  # type: ignore
        if checker is None:
            return Conditions(contractless_fn, contractless_fn, [], [],
                              frozenset(), sig, None, [])

        pre: List[ConditionExpr] = []
        post: List[ConditionExpr] = []

        def eval_contract(contract, kwargs):
            condition_kwargs = icontract._checkers.select_condition_kwargs(
                contract=contract, resolved_kwargs=kwargs)
            return contract.condition(**condition_kwargs)

        disjunction = checker.__preconditions__  # type: ignore
        if len(disjunction) == 0:
            pass
        elif len(disjunction) == 1:
            for contract in disjunction[0]:
                evalfn = functools.partial(eval_contract, contract)
                filename, line_num, _lines = sourcelines(contract.condition)
                pre.append(
                    ConditionExpr(evalfn, filename, line_num,
                                  self.contract_text(contract)))
        else:

            def eval_disjunction(disjunction, kwargs) -> bool:
                for conjunction in disjunction:
                    ok = True
                    for contract in conjunction:
                        if not eval_contract(contract, kwargs):
                            ok = False
                            break
                    if ok:
                        return True
                return False

            evalfn = functools.partial(eval_disjunction, disjunction)
            filename, line_num, _lines = sourcelines(contractless_fn)
            source = ("(" + ") or (".join([
                " and ".join([self.contract_text(c) for c in conj])
                for conj in disjunction
            ]) + ")")
            pre.append(ConditionExpr(evalfn, filename, line_num, source))

        snapshots = checker.__postcondition_snapshots__  # type: ignore

        def take_snapshots(**kwargs):
            old_as_mapping: MutableMapping[str, Any] = {}
            for snap in snapshots:
                snap_kwargs = icontract._checkers.select_capture_kwargs(
                    a_snapshot=snap, resolved_kwargs=kwargs)
                old_as_mapping[snap.name] = snap.capture(**snap_kwargs)
            return icontract._checkers.Old(mapping=old_as_mapping)

        def post_eval(contract, kwargs):
            _old = kwargs.pop("__old__")
            kwargs["OLD"] = take_snapshots(**_old.__dict__)
            kwargs["result"] = kwargs.pop("__return__")
            del kwargs["_"]
            condition_kwargs = icontract._checkers.select_condition_kwargs(
                contract=contract, resolved_kwargs=kwargs)
            return contract.condition(**condition_kwargs)

        for postcondition in checker.__postconditions__:  # type: ignore
            evalfn = functools.partial(post_eval, postcondition)
            filename, line_num, _lines = sourcelines(postcondition.condition)
            post.append(
                ConditionExpr(evalfn, filename, line_num,
                              self.contract_text(postcondition)))
        return Conditions(
            contractless_fn,
            contractless_fn,
            pre,
            post,
            raises=frozenset(parse_sphinx_raises(fn)),
            sig=sig,
            mutable_args=None,
            fn_syntax_messages=[],
        )
Ejemplo n.º 11
0
def add_completion_conditions(conditions: Conditions):
    post = conditions.post
    if not post and conditions.pre:
        filename, line, _lines = sourcelines(conditions.src_fn)
        post.append(ConditionExpr(lambda vars: True, filename, line, ""))
Ejemplo n.º 12
0
def _contains_line(entity: object, filename: str, linenum: int):
    (cur_filename, start, lines) = sourcelines(entity)
    end = start + len(lines)
    return samefile(filename, cur_filename) and start <= linenum <= end