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)
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)
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()
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()
z3printer._PP.max_lines = float('inf') for (base, ncomb, projections, calls) in tests: module_testcases = [] projected_calls = list(calls) for p in projections: for c in calls: projected_calls.append(projected_call(p, projections[p], c)) for callset in itertools.combinations_with_replacement(projected_calls, ncomb): print ' '.join([c.__name__ for c in callset]) rvs = simsym.symbolic_apply(test, base, *callset) conds = collections.defaultdict(lambda: simsym.wrap(z3.BoolVal(False))) for cond, res in simsym.combine(rvs): conds[res] = cond pc = simsym.simplify(conds['']) pr = simsym.simplify(simsym.symor([conds['r'], conds['rs']])) ps = simsym.simplify(conds['s']) ex_pc = simsym.exists(simsym.internals(), pc) nex_pc = simsym.symnot(ex_pc) 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])