Ejemplo n.º 1
0
def reduce_array_ext(expr):
    """Find a subset of expr that still produces array-ext in models.

    This is for producing minimal test cases for a specific class of
    bugs where Z3 exposes the internal array-ext function (e.g., Z3
    issue #125).
    """

    subs = expr.children()
    i = 0
    while i < len(subs):
        nsubs = subs[:i] + subs[i+1:]
        e2 = z3.And(nsubs)
        print subs[i].sexpr(),
        for rep in range(100):
            check = simsym.check(e2)
            if check.is_unknown:
                continue
            if 'array-ext' in check.z3_model.sexpr():
                # Got an array-ext, so subs[i] must not be necessary
                print "=> dropping"
                subs = nsubs
                break
        else:
            # Never got an array-ext, so subs[i] is probably necessary
            print "=> keeping"
            i += 1
    return z3.And(subs)
Ejemplo n.º 2
0
    def xcheck(cond):
        check = simsym.check(simsym.symand([pc, iso_constraint, cond]))
        if check.is_unknown:
            if unknown_count[0] == 0:
                print '  Idempotence unknown:', check.reason
#                print '    ' + str(cond)
            unknown_count[0] += 1
        return check
Ejemplo n.º 3
0
    def xcheck(cond):
        check = simsym.check(simsym.symand([pc, iso_constraint, cond]))
        if check.is_unknown:
            if unknown_count[0] == 0:
                print '  Idempotence unknown:', check.reason


#                print '    ' + str(cond)
            unknown_count[0] += 1
        return check
Ejemplo n.º 4
0
def print_cond(msg, cond):
    if args.check_conds and simsym.check(cond).is_unsat:
        return

    ## If the assumptions (i.e., calls to simsym.assume) imply the condition
    ## is true, we say that condition always holds, and we can print "always".
    ## It would be nice to print a clean condition that excludes assumptions,
    ## even if the assumptions don't directly imply the condition, but that
    ## would require finding the simplest expression for x such that
    ##
    ##   x AND simsym.assume_list = cond
    ##
    ## which seems hard to do using Z3.  In principle, this should be the
    ## same as simplifying the 'c' expression below, but Z3 isn't good at
    ## simplifying it.  We could keep the two kinds of constraints (i.e.,
    ## explicit assumptions vs. symbolic execution control flow constraints)
    ## separate in simsym, which will make them easier to disentangle..

    #c = simsym.implies(simsym.symand(simsym.assume_list), cond)
    ## XXX the above doesn't work well -- it causes open*open to say "always".
    ## One hypothesis is that we should be pairing the assume_list with each
    ## path condition, instead of taking the assume_list across all paths.
    c = cond

    if args.check_conds and simsym.check(simsym.symnot(c)).is_unsat:
        s = 'always'
    else:
        if args.print_conds:
            scond = simsym.simplify(cond, args.simplify_more)
            s = '\n    ' + str(scond).replace('\n', '\n    ')
        else:
            if args.check_conds:
                s = 'sometimes'
            else:
                s = 'maybe'
    print '  %s: %s' % (msg, s)
Ejemplo n.º 5
0
def print_cond(msg, cond, check_conds, print_conds):
    if check_conds and simsym.check(cond).is_unsat:
        return

    ## If the assumptions (i.e., calls to simsym.assume) imply the condition
    ## is true, we say that condition always holds, and we can print "always".
    ## It would be nice to print a clean condition that excludes assumptions,
    ## even if the assumptions don't directly imply the condition, but that
    ## would require finding the simplest expression for x such that
    ##
    ##   x AND simsym.assume_list = cond
    ##
    ## which seems hard to do using Z3.  In principle, this should be the
    ## same as simplifying the 'c' expression below, but Z3 isn't good at
    ## simplifying it.  We could keep the two kinds of constraints (i.e.,
    ## explicit assumptions vs. symbolic execution control flow constraints)
    ## separate in simsym, which will make them easier to disentangle..

    # c = simsym.implies(simsym.symand(simsym.assume_list), cond)
    ## XXX the above doesn't work well -- it causes open*open to say "always".
    ## One hypothesis is that we should be pairing the assume_list with each
    ## path condition, instead of taking the assume_list across all paths.
    c = cond

    if check_conds and simsym.check(simsym.symnot(c)).is_unsat:
        s = "always"
    else:
        if print_conds:
            scond = simsym.simplify(cond, print_conds == "simplify")
            s = "\n    " + str(scond).replace("\n", "\n    ")
        else:
            if check_conds:
                s = "sometimes"
            else:
                s = "maybe"
    print "  %s: %s" % (msg, s)
Ejemplo n.º 6
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()
Ejemplo n.º 7
0
  def setup_proc(self, pid, fdmap, vamap, pipe_end_fds):
    emit = self.emit
    emit('int fd __attribute__((unused));',
         'int r __attribute__((unused));')
    for fd, (symfd, inode) in fdmap.items():
      if symfd.ispipe:
        pipe_setup_fd = self.pipes[symfd.pipeid]
        if symfd.pipewriter: pipe_setup_fd += 1
        emit('r = dup2(%d, %d);' % (pipe_setup_fd, fd),
             'if (r < 0) setup_error("dup2");')
      else:
        emit('fd = open("%s", O_RDWR);' % self.inums[symfd.inum].fname,
             'if (fd < 0) setup_error("open");',
             'r = lseek(fd, %d, SEEK_SET);' % (inode.offsets[symfd.off]),
             'if (fd >= 0 && r < 0) setup_error("lseek");',
             'r = dup2(fd, %d);' % fd,
             'if (fd >= 0 && r < 0) setup_error("dup2");',
             'close(fd);')

    # There may be other FDs open to pipes that never came up in the
    # test, but that matter to keep the pipe open.  Check for this
    # possibility for every pipe end.
    for (_, pipewriter), (pipeid, pidcounts) in pipe_end_fds.items():
      # How many of this end are already set up in this process?
      knowncount = pidcounts[pid]
      # Try to find > knowncount distinct instances of this end in
      # this process.
      conds = []
      distinct = []
      sym_fd_map = self.fs.getproc(pid).fd_map
      for i in range(knowncount + 1):
        ofdnum = fs_module.SFdNum.var()
        ofd = sym_fd_map._map[ofdnum]
        conds.extend([sym_fd_map._valid[ofdnum], ofd.ispipe,
                      ofd.pipeid == pipeid, ofd.pipewriter == pipewriter])
        distinct.append(ofdnum)
      if len(distinct) > 1:
        conds.append(simsym.distinct(*distinct))

      # XXX I've tried to *prove* that the path condition implies
      # there *must* be more FDs for this pipe end, so the decision
      # will stand regardless of choices made by model completion, but
      # I just can't get it to work:
      #  prove(simsym.implies(self.constraint,
      #                       simsym.exists(ofdnums, simsym.symand(conds))))

      result = simsym.check(simsym.symand([self.constraint] + conds))
      if result.is_unknown:
        print 'Warning: Unable to check pipe FD existence:', result.reason
      elif result.is_sat:
        # Open up another pipe end
        pipe_setup_fd = self.pipes[pipeid]
        if pipewriter: pipe_setup_fd += 1
        emit('r = dup(%d);' % pipe_setup_fd,
             'if (r < 0) setup_error("dup");')

    for va, vainfo in vamap.items():
      if vainfo.anon:
        emit('init_map_anon(%#x, %d, %d);' %
             (va, vainfo.writable.val,
              self.datavals[vainfo.anondata].first_byte))
      else:
        inode = self.inums[vainfo.inum]
        emit('init_map_file(%#x, %d, "%s", %#x);' %
             (va, vainfo.writable.val, inode.fname, inode.offsets[vainfo.off]))
Ejemplo n.º 8
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()
Ejemplo n.º 9
0
        print "nex_pc", nex_pc
        ex_pr = simsym.exists(simsym.internals(), pr)
        nex_pr = simsym.symnot(ex_pr)
        ps2 = simsym.symand([ps, nex_pc, nex_pr])

        ps_ex_pr = simsym.symand([ps, ex_pr])
        pr2 = simsym.symand([simsym.symor([pr, ps_ex_pr]), nex_pc])

        ps_ex_pc = simsym.symand([ps, ex_pc])
        pr_ex_pc = simsym.symand([pr, ex_pc])
        pc2 = simsym.symor([pc, ps_ex_pc, pr_ex_pc])

        for msg, cond in (('commute', pc2),
                          ('results diverge', pr2),
                          ('states diverge', ps2)):
            if simsym.check(cond)[0] == z3.unsat:
                continue
            if simsym.check(simsym.symnot(cond))[0] == z3.unsat:
                s = 'any state'
            else:
                if print_cond:
                    scond = simsym.simplify(cond)
                    s = '\n    ' + str(scond).replace('\n', '\n    ')
                else:
                    s = 'sometimes'
            print '  %s: %s' % (msg, s)

        if testfile is not None:
            for cond, ores in rvs:
		#print "cond: ", cond
		#print "pc2: ", pc2
Ejemplo n.º 10
0
    def setup_proc(self, pid, fdmap, vamap, pipe_end_fds):
        emit = self.emit
        emit("int fd __attribute__((unused));", "int r __attribute__((unused));")
        for fd, (symfd, inode) in fdmap.items():
            if symfd.ispipe:
                pipe_setup_fd = self.pipes[symfd.pipeid]
                if symfd.pipewriter:
                    pipe_setup_fd += 1
                emit("r = dup2(%d, %d);" % (pipe_setup_fd, fd), 'if (r < 0) setup_error("dup2");')
            else:
                emit(
                    'fd = open("%s", O_RDWR);' % self.inums[symfd.inum].fname,
                    'if (fd < 0) setup_error("open");',
                    "r = lseek(fd, %d, SEEK_SET);" % (inode.offsets[symfd.off]),
                    'if (fd >= 0 && r < 0) setup_error("lseek");',
                    "r = dup2(fd, %d);" % fd,
                    'if (fd >= 0 && r < 0) setup_error("dup2");',
                    "close(fd);",
                )

        # There may be other FDs open to pipes that never came up in the
        # test, but that matter to keep the pipe open.  Check for this
        # possibility for every pipe end.
        for (_, pipewriter), (pipeid, pidcounts) in pipe_end_fds.items():
            # How many of this end are already set up in this process?
            knowncount = pidcounts[pid]
            # Try to find > knowncount distinct instances of this end in
            # this process.
            conds = []
            distinct = []
            sym_fd_map = self.fs.getproc(pid).fd_map
            for i in range(knowncount + 1):
                ofdnum = fs_module.SFdNum.var()
                ofd = sym_fd_map._map[ofdnum]
                conds.extend(
                    [sym_fd_map._valid[ofdnum], ofd.ispipe, ofd.pipeid == pipeid, ofd.pipewriter == pipewriter]
                )
                distinct.append(ofdnum)
            if len(distinct) > 1:
                conds.append(simsym.distinct(*distinct))

            # XXX I've tried to *prove* that the path condition implies
            # there *must* be more FDs for this pipe end, so the decision
            # will stand regardless of choices made by model completion, but
            # I just can't get it to work:
            #  prove(simsym.implies(self.constraint,
            #                       simsym.exists(ofdnums, simsym.symand(conds))))

            result = simsym.check(simsym.symand([self.constraint] + conds))
            if result.is_unknown:
                print "Warning: Unable to check pipe FD existence:", result.reason
            elif result.is_sat:
                # Open up another pipe end
                pipe_setup_fd = self.pipes[pipeid]
                if pipewriter:
                    pipe_setup_fd += 1
                emit("r = dup(%d);" % pipe_setup_fd, 'if (r < 0) setup_error("dup");')

        for va, vainfo in vamap.items():
            if vainfo.anon:
                emit(
                    "init_map_anon(%#x, %d, %d);" % (va, vainfo.writable.val, self.datavals[vainfo.anondata].first_byte)
                )
            else:
                inode = self.inums[vainfo.inum]
                emit(
                    'init_map_file(%#x, %d, "%s", %#x);'
                    % (va, vainfo.writable.val, inode.fname, inode.offsets[vainfo.off])
                )