Example #1
0
    def __getitem__(self, key):
        """Return the value associated with key.

       If the dictionary does not currently contain a value for key,
       one will be drawn from self's iterable.  key may be a Z3
       literal.
       """

        hkey = z3util.HashableAst(key)

        if hkey not in self.__map:
            if isinstance(key, simsym.Symbolic) and \
               not _is_literal(simsym.unwrap(key)):
                # There's nothing wrong with supporting arbitrary ASTs,
                # but in every place we use DynamicDict, this indicates
                # an easy-to-miss bug.
                raise ValueError("Key is not a literal: %r" % key)

            if self.__fn is None:
                raise ValueError(
                    "DynamicDict has been read; cannot be extended")
            try:
                self.__map[hkey] = self.__fn(key)
            except StopIteration:
                raise ValueError("Ran out of values for %r" % key)
        return self.__map[hkey]
Example #2
0
  def build_dir(self):
    # Reads filenames; extends inums
    fn_to_ino = {}
    for symfn, fn in self.filenames.items():
      if not self.fs.root_dir.contains(symfn):
        continue
      syminum = self.fs.root_dir[symfn]
      fn_to_ino[fn] = self.inums[syminum]

    # Reads procs[pid].fds and procs[pid].vas; extends nothing
    (fdmap0, vamap0) = self.build_proc(False)
    (fdmap1, vamap1) = self.build_proc(True)

    # Compute known pipe end FDs.  This map is system-wide because,
    # for example, process 1 may be holding open a pipe that's only
    # used by testing in process 0.
    pipe_end_fds = collections.defaultdict(list)
    for pid, fdmap in [(False, fdmap0), (True, fdmap1)]:
      for fd, (symfd, inode) in fdmap.items():
        if symfd.ispipe:
          key = (z3util.HashableAst(symfd.pipeid.someval), symfd.pipewriter.val)
          if key not in pipe_end_fds:
            # Make sure both ends are in the map, even if we never
            # come across an example of the other end
            pipe_end_fds[key] = (symfd.pipeid, {True: 0, False: 0})
            okey = (key[0], not key[1])
            pipe_end_fds[okey] = (symfd.pipeid, {True: 0, False: 0})
          pipe_end_fds[key][1][pid] += 1

    setup = {'common': testgen.CodeWriter(),
             'proc0': testgen.CodeWriter(),
             'proc1': testgen.CodeWriter(),
             'procfinal': testgen.CodeWriter(),
             'final': testgen.CodeWriter()}

    try:
      # setup_proc reads nothing; extends inums, datavals, pipes
      self.emit = setup['proc0']
      self.setup_proc(False, fdmap0, vamap0, pipe_end_fds)
      self.emit = setup['proc1']
      self.setup_proc(True, fdmap1, vamap1, pipe_end_fds),
      # setup_inodes reads inums, pipes; extends datavals
      self.emit = setup['common']; self.setup_inodes()
      # setup_filenames reads nothing; extends nothing
      self.emit = setup['common']; self.setup_filenames(fn_to_ino)
      # setup_proc_finalize reads pipes; extends nothing
      self.emit = setup['procfinal']; self.setup_proc_finalize()
      # setup_inodes_finalize reads inums, pipes; extends nothing
      self.emit = setup['final']; self.setup_inodes_finalize()
    finally:
      self.emit = None
    return setup
Example #3
0
    def add(self, realm, expr, val, env):
        """Add a condition on the value of expr.

        expr and val must be instances of simsym.Symbolic.  env must
        be a simsym.SymbolicApplyResult that provides the context for
        interpreting constants in expr and val.

        For most expr's, this will require that an isomorphic
        assignment assign val to expr.

        If realm is an Interpreter, then this will require that all
        keys in the Interpreter be distinct, but it will not place
        constraints on their specific values.  This makes it possible
        to match isomorphic "equality patterns" between values of
        uninterpreted sorts, or values we wish to treat as
        uninterpreted.
        """

        if not isinstance(expr, simsym.Symbolic):
            raise TypeError("Expected instance of simsym.Symbolic, got %r" %
                            expr)
        if not isinstance(val, simsym.Symbolic):
            raise TypeError("Expected instance of simsym.Symbolic, got %r" %
                            val)

        if realm is None:
            # Default realm.  Use value equality.
            if not isinstance(expr, simsym.SBool):
                print 'WARNING: Interpreted sort assignment:', \
                    type(expr), expr, val
            self.__conds.add(expr == val)
        elif isinstance(realm, testgen.Interpreter):
            # Use equality isomorphism within this realm
            hval = z3util.HashableAst(val)
            self.__repmaps[realm][z3util.HashableAst(val)].add(expr)
        else:
            raise ValueError("Unknown realm type %r" % realm)
Example #4
0
    def __getitem__(self, key):
        if not isinstance(key, self.__key_type):
            raise TypeError("key must be %r instance, not %r" %
                            (self.__key_type.__name__, simsym.strtype(key)))
        if _is_literal(simsym.unwrap(key)):
            raise ValueError("key must be non-literal, not %r" % key)

        lit = key.eval(self.__realm)
        hlit = z3util.HashableAst(lit)

        if hlit not in self.__map:
            if self.__fn is None:
                raise ValueError(
                    "Interpreter has been read; cannot be extended")
            try:
                self.__map[hlit] = (key, self.__fn(lit))
            except StopIteration:
                raise ValueError("Ran out of values for %r" % key)
        return self.__map[hlit][1]
Example #5
0
    def on_path(self, result):
        super(TestWriter, self).on_path(result)

        pathinfo = collections.OrderedDict([
            ('id', '_'.join(self.callset_names) + '_' + result.pathid)
        ])
        self.model_data_callset[result.pathid] = pathinfo

        if result.type == 'exception':
            pathinfo['exception'] = '\n'.join(
                traceback.format_exception_only(*result.exc_info[:2]))
            self.nerror += 1
            return

        pathinfo['diverge'] = ', '.join(map(str, result.value.diverge))

        # Filter out non-commutative results
        if len(result.value.diverge):
            return

        if not self.trace_file and not self.testgen:
            return

        if self.trace_file:
            print >> self.trace_file, "=== Path %s ===" % result.pathid
            print >> self.trace_file

        e = result.path_condition

        ## This can potentially reduce the number of test cases
        ## by, e.g., eliminating irrelevant variables from e.
        ## The effect doesn't seem significant: one version of Fs
        ## produces 3204 test cases without simplify, and 3182 with.
        e = simsym.simplify(e)

        if args.verbose_testgen:
            print "Simplified path condition:"
            print e

        if self.trace_file:
            print >> self.trace_file, e
            print >> self.trace_file

        # Find the uninterpreted constants in the path condition.  We
        # omit assumptions because uninterpreted constants that appear
        # only in assumptions generally don't represent that the model
        # actually "touched".  We use the simplified expression
        # because the final state comparison in original expression
        # contains a lot of trivial expressions like x==x for all
        # state variables x, and we don't care about these
        # uninterpreted constants.
        e_vars = expr_vars(
            simsym.simplify(
                simsym.symand(
                    result.get_path_condition_list(with_assume=False,
                                                   with_det=True))))

        if self.testgen:
            self.testgen.begin_path(result)

        self.model_data_testinfo_list = []
        pathinfo['tests'] = self.model_data_testinfo_list

        self.npathmodel = 0
        self.last_assignments = None
        while not self.stop_call_set() and \
              self.npathmodel < args.max_tests_per_path:
            # XXX Would it be faster to reuse the solver?
            check = simsym.check(e)
            if check.is_sat and 'array-ext' in check.z3_model.sexpr():
                # Work around some non-deterministic bug that causes
                # Z3 to occasionally produce models containing
                # 'array-ext' applications that break evaluation.
                print 'Warning: Working around array-ext bug'
                for i in range(10):
                    check = simsym.check(e)
                    if not check.is_sat:
                        continue
                    if 'array-ext' not in check.z3_model.sexpr():
                        break
                else:
                    self._testerror('array-ext workaround failed', pathinfo)
                    break

            if check.is_unsat: break
            if check.is_unknown:
                # raise Exception('Cannot enumerate: %s' % str(e))
                self._testerror(check.reason, pathinfo)
                break

            if args.verbose_testgen:
                print "Model:"
                print check.model

            testid = ('_'.join(self.callset_names) + '_' + result.pathid +
                      '_' + str(self.npathmodel))
            testinfo = collections.OrderedDict(id=testid)
            self.model_data_testinfo_list.append(testinfo)

            assignments = self.__on_model(result, check.z3_model, e, testid)
            if assignments is None:
                break
            if args.verbose_testgen:
                print 'Assignments:'
                pprint.pprint(assignments)
            if args.diff_testgen:
                new_assignments = {}
                if self.last_assignments is not None:
                    for aexpr, val in assignments:
                        hexpr = z3util.HashableAst(aexpr)
                        sval = str(val)
                        last_sval = self.last_assignments.get(hexpr)
                        if last_sval is not None and last_sval != sval:
                            print '%s: %s -> %s' % (aexpr, last_sval, sval)
                        new_assignments[hexpr] = sval
                self.last_assignments = new_assignments

            testinfo['assignments'] = {}
            for aexpr, val in assignments[None]:
                testinfo['assignments'][str(aexpr)] = str(val)

            # Construct the isomorphism condition for the assignments
            # used by testgen.  This tells us exactly what values
            # actually mattered to test case generation.  However,
            # this set isn't perfect: testgen may have queried
            # assignments that didn't actually matter to the
            # function's behavior (e.g., flags that didn't matter
            # because the function will return an error anyway, etc).
            # To filter out such uninterpreted constants, we only
            # consider those that were *both* used in an assignment by
            # testgen and appeared in the path condition expression.
            # XXX We should revisit this and see how much difference
            # this makes.
            same = IsomorphicMatch()
            for realm, rassigns in assignments.iteritems():
                for aexpr, val in rassigns:
                    aexpr_vars = expr_vars(aexpr)
                    if not aexpr_vars.isdisjoint(e_vars):
                        same.add(realm, aexpr, val, result)
                    elif args.verbose_testgen:
                        print 'Ignoring assignment:', (aexpr, val)
            isocond = same.condition()

            # Compute idempotent projections for this test
            if args.idempotent_projs:
                projs, proj_errors = idempotent_projs(result, isocond)
                testinfo['idempotent_projs'] = projs
                if proj_errors:
                    testinfo['idempotence_unknown'] = proj_errors

            # Construct constraint for next test
            notsame = simsym.symnot(isocond)
            if args.verbose_testgen:
                print 'Negation', self.nmodel, ':', notsame
            e = simsym.symand([e, notsame])

        if self.npathmodel == args.max_tests_per_path:
            print '  Max tests reached for path %s' % result.pathid

        if self.testgen:
            self.testgen.end_path()