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]
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
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)
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]
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()