/*++ Copyright (c) 2011 Microsoft Corporation Module Name: z3_replayer.cpp Abstract: Interpreter for Z3 logs Author: Leonardo de Moura (leonardo) 2011-09-22 Notes: --*/ #include "util/vector.h" #include "util/map.h" #include "api/z3_replayer.h" #include "util/stream_buffer.h" #include "util/symbol.h" #include "util/trace.h" #include #include void register_z3_replayer_cmds(z3_replayer & in); void throw_invalid_reference() { TRACE("z3_replayer", tout << "invalid argument reference\n";); throw z3_replayer_exception("invalid argument reference"); } struct z3_replayer::imp { z3_replayer & m_owner; std::istream & m_stream; int m_curr; // current char; int m_line; // line svector m_string; symbol m_id; int64_t m_int64; uint64_t m_uint64; double m_double; float m_float; size_t m_ptr; size_t_map m_heap; svector m_cmds; std::vector m_cmds_names; enum value_kind { INT64, UINT64, DOUBLE, STRING, SYMBOL, OBJECT, UINT_ARRAY, INT_ARRAY, SYMBOL_ARRAY, OBJECT_ARRAY, FLOAT }; char const* kind2string(value_kind k) const { switch (k) { case INT64: return "int64"; case UINT64: return "uint64"; case DOUBLE: return "double"; case STRING: return "string"; case SYMBOL: return "symbol"; case OBJECT: return "object"; case UINT_ARRAY: return "uint_array"; case INT_ARRAY: return "int_array"; case SYMBOL_ARRAY: return "symbol_array"; case OBJECT_ARRAY: return "object_array"; case FLOAT: return "float"; default: UNREACHABLE(); return "unknown"; } } void check_arg(unsigned pos, value_kind k) const { if (pos >= m_args.size()) { TRACE("z3_replayer", tout << "too few arguments " << m_args.size() << " expecting " << kind2string(k) << "\n";); throw z3_replayer_exception("invalid argument reference"); } if (m_args[pos].m_kind != k) { std::stringstream strm; strm << "expecting " << kind2string(k) << " at position " << pos << " but got " << kind2string(m_args[pos].m_kind); TRACE("z3_replayer", tout << strm.str() << "\n";); throw z3_replayer_exception(strm.str()); } } struct value { value_kind m_kind; union { int64_t m_int; uint64_t m_uint; double m_double; char const * m_str; void const* m_sym; // uint64_t void * m_obj; float m_float; }; value():m_kind(OBJECT), m_int(0) {} value(void * obj):m_kind(OBJECT), m_obj(obj) {} value(value_kind k, char const * str):m_kind(k), m_str(str) {} value(value_kind k, symbol const& s):m_kind(k), m_sym(s.c_api_symbol2ext()) {} value(value_kind k, uint64_t u):m_kind(k), m_uint(u) {} value(value_kind k, int64_t i):m_kind(k), m_int(i) {} value(value_kind k, double d):m_kind(k), m_double(d) {} value(value_kind k, float f):m_kind(k), m_float(f) {} }; svector m_args; void * m_result; vector > m_obj_arrays; vector > m_sym_arrays; vector m_unsigned_arrays; vector > m_int_arrays; imp(z3_replayer & o, std::istream & in): m_owner(o), m_stream(in), m_curr(0), m_line(1) { next(); } void display_arg(std::ostream & out, value const & v) const { switch (v.m_kind) { case INT64: out << v.m_int; break; case UINT64: out << v.m_uint; break; case FLOAT: out << v.m_float; break; case DOUBLE: out << v.m_double; break; case STRING: out << v.m_str; break; case SYMBOL: out << symbol::c_api_ext2symbol(v.m_sym); break; case OBJECT: out << v.m_obj; break; case UINT_ARRAY: case OBJECT_ARRAY: case SYMBOL_ARRAY: out << ""; break; default: out << ""; break; } } void display_args(std::ostream & out) const { for (unsigned i = 0; i < m_args.size(); i++) { if (i > 0) out << " "; display_arg(out, m_args[i]); } } int curr() const { return m_curr; } void new_line() { m_line++; } void next() { m_curr = m_stream.get(); } void read_string_core(char delimiter) { if (curr() != delimiter) throw z3_replayer_exception("invalid string/symbol"); m_string.reset(); next(); while (true) { int c = curr(); if (c == EOF) { throw z3_replayer_exception("unexpected end of file"); } else if (c == '\n') { throw z3_replayer_exception("unexpected end of line"); } else if (c == '\\') { next(); unsigned val = 0; unsigned sz = 0; while (sz < 3) { c = curr(); if ('0' <= c && c <= '9') { val *= 10; val += c - '0'; sz++; } else { throw z3_replayer_exception("invalid escaped character"); } if (val > 255) throw z3_replayer_exception("invalid escaped character"); next(); } TRACE("z3_replayer_escape", tout << "val: " << val << "\n";); m_string.push_back(static_cast(val)); } else if (c == delimiter) { next(); m_string.push_back(0); return; } else { m_string.push_back(c); next(); } } } void read_string() { read_string_core('"'); } void read_quoted_symbol() { read_string_core('|'); m_id = m_string.begin(); } void read_int64() { if (!(curr() == '-' || ('0' <= curr() && curr() <= '9'))) throw z3_replayer_exception("invalid integer"); bool sign = false; if (curr() == '-') { sign = true; next(); if (!('0' <= curr() && curr() <= '9')) throw z3_replayer_exception("invalid integer"); } m_int64 = 0; while (true) { int c = curr(); if ('0' <= c && c <= '9') { m_int64 = 10*m_int64 + (c - '0'); next(); } else { break; } } if (sign) m_int64 = -m_int64; } void read_uint64() { if (!('0' <= curr() && curr() <= '9')) throw z3_replayer_exception("invalid unsigned"); m_uint64 = 0; while (true) { int c = curr(); if ('0' <= c && c <= '9') { m_uint64 = 10*m_uint64 + (c - '0'); next(); } else { break; } } } bool is_double_char() const { return curr() == '-' || curr() == '.' || ('0' <= curr() && curr() <= '9') || curr() == 'e' || curr() == 'E'; } #if (!defined(strtof)) float strtof(const char * str, char ** end_ptr) { // Note: This may introduce a double-rounding problem. return (float)strtod(str, end_ptr); } #endif void read_float() { m_string.reset(); while (is_double_char()) { m_string.push_back(curr()); next(); } if (m_string.empty()) throw z3_replayer_exception("invalid float"); m_string.push_back(0); char * ptr; m_float = strtof(m_string.begin(), &ptr); } void read_double() { m_string.reset(); while (is_double_char()) { m_string.push_back(curr()); next(); } if (m_string.empty()) throw z3_replayer_exception("invalid double"); m_string.push_back(0); char * ptr; m_double = strtod(m_string.begin(), &ptr); } void read_ptr() { if (!(('0' <= curr() && curr() <= '9') || ('A' <= curr() && curr() <= 'F') || ('a' <= curr() && curr() <= 'f'))) { TRACE("invalid_ptr", tout << "curr: " << curr() << "\n";); throw z3_replayer_exception("invalid ptr"); } unsigned pos = 0; m_ptr = 0; while (true) { int c = curr(); if ('0' <= c && c <= '9') { m_ptr = m_ptr * 16 + (c - '0'); } else if ('a' <= c && c <= 'f') { m_ptr = m_ptr * 16 + 10 + (c - 'a'); } else if ('A' <= c && c <= 'F') { m_ptr = m_ptr * 16 + 10 + (c - 'A'); } else if (pos == 1 && (c == 'x' || c == 'X')) { // support for 0x.... notation } else { return; } next(); pos++; } } void skip_blank() { while (true) { int c = curr(); if (c == '\n') { new_line(); next(); } else if (c == ' ' || c == '\t') { next(); } else { break; } } } void push_array(unsigned sz, value_kind k) { unsigned asz = m_args.size(); if (sz > asz) throw z3_replayer_exception("invalid array size"); uint64_t aidx; value_kind nk; for (unsigned i = asz - sz; i < asz; i++) { if (m_args[i].m_kind != k) throw z3_replayer_exception("invalid array: mixed value types"); } if (k == UINT64) { aidx = m_unsigned_arrays.size(); nk = UINT_ARRAY; m_unsigned_arrays.push_back(unsigned_vector()); unsigned_vector & v = m_unsigned_arrays.back(); for (unsigned i = asz - sz; i < asz; i++) { v.push_back(static_cast(m_args[i].m_uint)); } } else if (k == INT64) { aidx = m_int_arrays.size(); nk = INT_ARRAY; m_int_arrays.push_back(svector()); svector & v = m_int_arrays.back(); for (unsigned i = asz - sz; i < asz; i++) { v.push_back(static_cast(m_args[i].m_int)); } } else if (k == SYMBOL) { aidx = m_sym_arrays.size(); nk = SYMBOL_ARRAY; m_sym_arrays.push_back(svector()); svector & v = m_sym_arrays.back(); for (unsigned i = asz - sz; i < asz; i++) { v.push_back(reinterpret_cast(const_cast(m_args[i].m_str))); } } else if (k == OBJECT) { TRACE("z3_replayer_bug", tout << "args: "; display_args(tout); tout << "\n"; tout << "push_back, sz: " << sz << ", m_obj_arrays.size(): " << m_obj_arrays.size() << "\n"; for (unsigned i = asz - sz; i < asz; i++) { tout << "pushing: " << m_args[i].m_obj << "\n"; }); aidx = m_obj_arrays.size(); nk = OBJECT_ARRAY; m_obj_arrays.push_back(ptr_vector()); ptr_vector & v = m_obj_arrays.back(); for (unsigned i = asz - sz; i < asz; i++) { v.push_back(m_args[i].m_obj); } } else { throw z3_replayer_exception("unsupported array type"); } m_args.shrink(asz - sz); m_args.push_back(value(nk, aidx)); } #define TICK_FREQUENCY 100000 void parse() { memory::exit_when_out_of_memory(false, nullptr); uint64_t counter = 0; unsigned tick = 0; while (true) { IF_VERBOSE(1, { counter++; tick++; if (tick >= TICK_FREQUENCY) { std::cout << "[replayer] " << counter << " operations executed" << std::endl; tick = 0; } }); skip_blank(); int c = curr(); if (c == EOF) return; switch (c) { case 'V': // version next(); skip_blank(); read_string(); break; case 'R': // reset next(); TRACE("z3_replayer", tout << "[" << m_line << "] " << "R\n";); reset(); break; case 'P': { // push pointer next(); skip_blank(); read_ptr(); TRACE("z3_replayer", tout << "[" << m_line << "] " << "P " << m_ptr << "\n";); if (m_ptr == 0) { m_args.push_back(nullptr); } else { void * obj = nullptr; if (!m_heap.find(m_ptr, obj)) throw z3_replayer_exception("invalid pointer"); m_args.push_back(value(obj)); TRACE("z3_replayer_bug", tout << "args after 'P':\n"; display_args(tout); tout << "\n";); } break; } case 'S': { // push string next(); skip_blank(); read_string(); TRACE("z3_replayer", tout << "[" << m_line << "] " << "S " << m_string.begin() << "\n";); symbol sym(m_string.begin()); // save string m_args.push_back(value(STRING, sym.bare_str())); break; } case 'N': // push null symbol next(); TRACE("z3_replayer", tout << "[" << m_line << "] " << "N\n";); m_args.push_back(value(SYMBOL, symbol::null)); break; case '$': { // push symbol next(); skip_blank(); read_quoted_symbol(); TRACE("z3_replayer", tout << "[" << m_line << "] " << "$ " << m_id << "\n";); m_args.push_back(value(SYMBOL, m_id)); break; } case '#': { // push numeral symbol next(); skip_blank(); read_uint64(); TRACE("z3_replayer", tout << "[" << m_line << "] " << "# " << m_uint64 << "\n";); symbol sym(static_cast(m_uint64)); m_args.push_back(value(SYMBOL, sym)); break; } case 'I': // push integer; next(); skip_blank(); read_int64(); TRACE("z3_replayer", tout << "[" << m_line << "] " << "I " << m_int64 << "\n";); m_args.push_back(value(INT64, m_int64)); break; case 'U': // push unsigned; next(); skip_blank(); read_uint64(); TRACE("z3_replayer", tout << "[" << m_line << "] " << "U " << m_uint64 << "\n";); m_args.push_back(value(UINT64, m_uint64)); break; case 'F': // push float next(); skip_blank(); read_float(); TRACE("z3_replayer", tout << "[" << m_line << "] " << "F " << m_float << "\n";); m_args.push_back(value(FLOAT, m_float)); break; case 'D': // push double next(); skip_blank(); read_double(); TRACE("z3_replayer", tout << "[" << m_line << "] " << "D " << m_double << "\n";); m_args.push_back(value(DOUBLE, m_double)); break; case 'p': case 's': case 'u': case 'i': // push array next(); skip_blank(); read_uint64(); TRACE("z3_replayer", tout << "[" << m_line << "] " << "A " << m_uint64 << "\n";); if (c == 'p') push_array(static_cast(m_uint64), OBJECT); else if (c == 's') push_array(static_cast(m_uint64), SYMBOL); else if (c == 'i') push_array(static_cast(m_uint64), INT64); else push_array(static_cast(m_uint64), UINT64); break; case 'C': { // call procedure next(); skip_blank(); read_uint64(); TRACE("z3_replayer", tout << "[" << m_line << "] " << "C " << m_uint64 << "\n";); unsigned idx = static_cast(m_uint64); if (idx >= m_cmds.size()) throw z3_replayer_exception("invalid command"); try { TRACE("z3_replayer_cmd", tout << idx << ":" << m_cmds_names[idx] << "\n";); m_cmds[idx](m_owner); } catch (z3_error & ex) { throw ex; } catch (z3_exception & ex) { std::cout << "[z3 exception]: " << ex.msg() << std::endl; } break; } case '=': // save result // = obj_id next(); skip_blank(); read_ptr(); TRACE("z3_replayer", tout << "[" << m_line << "] " << "= " << m_ptr << "\n";); m_heap.insert(m_ptr, m_result); break; case '*': { // save out // @ obj_id pos next(); skip_blank(); read_ptr(); skip_blank(); read_uint64(); unsigned pos = static_cast(m_uint64); TRACE("z3_replayer", tout << "[" << m_line << "] " << "* " << m_ptr << " " << pos << "\n";); check_arg(pos, OBJECT); m_heap.insert(m_ptr, m_args[pos].m_obj); break; } case '@': { // save array out // @ obj_id array_pos idx next(); skip_blank(); read_ptr(); skip_blank(); read_uint64(); unsigned pos = static_cast(m_uint64); check_arg(pos, OBJECT_ARRAY); unsigned aidx = static_cast(m_args[pos].m_uint); ptr_vector & v = m_obj_arrays[aidx]; skip_blank(); read_uint64(); unsigned idx = static_cast(m_uint64); TRACE("z3_replayer", tout << "[" << m_line << "] " << "@ " << m_ptr << " " << pos << " " << idx << "\n";); TRACE("z3_replayer_bug", tout << "v[idx]: " << v[idx] << "\n";); m_heap.insert(m_ptr, v[idx]); break; } case 'M': // user message next(); skip_blank(); read_string(); TRACE("z3_replayer", tout << "[" << m_line << "] " << "M " << m_string.begin() << "\n";); std::cout << m_string.begin() << "\n"; std::cout.flush(); break; default: TRACE("z3_replayer", tout << "unknown command " << c << "\n";); throw z3_replayer_exception("unknown log command"); break; } } } int get_int(unsigned pos) const { check_arg(pos, INT64); return static_cast(m_args[pos].m_int); } int64_t get_int64(unsigned pos) const { check_arg(pos, INT64); return m_args[pos].m_int; } unsigned get_uint(unsigned pos) const { check_arg(pos, UINT64); return static_cast(m_args[pos].m_uint); } uint64_t get_uint64(unsigned pos) const { check_arg(pos, UINT64); return m_args[pos].m_uint; } float get_float(unsigned pos) const { if (pos >= m_args.size() || m_args[pos].m_kind != FLOAT) throw_invalid_reference(); return m_args[pos].m_float; } double get_double(unsigned pos) const { check_arg(pos, DOUBLE); return m_args[pos].m_double; } Z3_string get_str(unsigned pos) const { check_arg(pos, STRING); return m_args[pos].m_str; } Z3_symbol get_symbol(unsigned pos) const { check_arg(pos, SYMBOL); return (Z3_symbol)m_args[pos].m_sym; } void * get_obj(unsigned pos) const { check_arg(pos, OBJECT); return m_args[pos].m_obj; } unsigned * get_uint_array(unsigned pos) const { check_arg(pos, UINT_ARRAY); unsigned idx = static_cast(m_args[pos].m_uint); return m_unsigned_arrays[idx].data(); } int * get_int_array(unsigned pos) const { check_arg(pos, INT_ARRAY); unsigned idx = static_cast(m_args[pos].m_uint); return m_int_arrays[idx].data(); } bool * get_bool_array(unsigned pos) const { check_arg(pos, UINT_ARRAY); unsigned idx = static_cast(m_args[pos].m_uint); return reinterpret_cast(m_unsigned_arrays[idx].data()); } Z3_symbol * get_symbol_array(unsigned pos) const { check_arg(pos, SYMBOL_ARRAY); unsigned idx = static_cast(m_args[pos].m_uint); return m_sym_arrays[idx].data(); } void ** get_obj_array(unsigned pos) const { check_arg(pos, OBJECT_ARRAY); unsigned idx = static_cast(m_args[pos].m_uint); ptr_vector const & v = m_obj_arrays[idx]; TRACE("z3_replayer_bug", tout << "pos: " << pos << ", idx: " << idx << " size(): " << v.size() << "\n"; for (unsigned i = 0; i < v.size(); i++) tout << v[i] << " "; tout << "\n";); return v.data(); } int * get_int_addr(unsigned pos) { check_arg(pos, INT64); return reinterpret_cast(&(m_args[pos].m_int)); } int64_t * get_int64_addr(unsigned pos) { check_arg(pos, INT64); return &(m_args[pos].m_int); } unsigned * get_uint_addr(unsigned pos) { check_arg(pos, UINT64); return reinterpret_cast(&(m_args[pos].m_uint)); } uint64_t * get_uint64_addr(unsigned pos) { check_arg(pos, UINT64); return &(m_args[pos].m_uint); } Z3_string * get_str_addr(unsigned pos) { check_arg(pos, STRING); return &(m_args[pos].m_str); } void ** get_obj_addr(unsigned pos) { check_arg(pos, OBJECT); return &(m_args[pos].m_obj); } void store_result(void * obj) { m_result = obj; } void register_cmd(unsigned id, z3_replayer_cmd cmd, char const* name) { m_cmds.reserve(id+1, 0); while (static_cast(m_cmds_names.size()) <= id+1) { m_cmds_names.push_back(""); } m_cmds[id] = cmd; m_cmds_names[id] = name; } void reset() { m_result = nullptr; m_args.reset(); m_obj_arrays.reset(); m_sym_arrays.reset(); m_unsigned_arrays.reset(); m_int_arrays.reset(); } }; z3_replayer::z3_replayer(std::istream & in) { m_imp = alloc(imp, *this, in); register_z3_replayer_cmds(*this); } z3_replayer::~z3_replayer() { dealloc(m_imp); } unsigned z3_replayer::get_line() const { return m_imp->m_line; } bool z3_replayer::get_bool(unsigned pos) const { return get_int(pos) != 0; } int z3_replayer::get_int(unsigned pos) const { return m_imp->get_int(pos); } unsigned z3_replayer::get_uint(unsigned pos) const { return m_imp->get_uint(pos); } int64_t z3_replayer::get_int64(unsigned pos) const { return m_imp->get_int64(pos); } uint64_t z3_replayer::get_uint64(unsigned pos) const { return m_imp->get_uint64(pos); } float z3_replayer::get_float(unsigned pos) const { return m_imp->get_float(pos); } double z3_replayer::get_double(unsigned pos) const { return m_imp->get_double(pos); } Z3_string z3_replayer::get_str(unsigned pos) const { return m_imp->get_str(pos); } Z3_symbol z3_replayer::get_symbol(unsigned pos) const { return m_imp->get_symbol(pos); } void * z3_replayer::get_obj(unsigned pos) const { return m_imp->get_obj(pos); } unsigned * z3_replayer::get_uint_array(unsigned pos) const { return m_imp->get_uint_array(pos); } int * z3_replayer::get_int_array(unsigned pos) const { return m_imp->get_int_array(pos); } bool * z3_replayer::get_bool_array(unsigned pos) const { return m_imp->get_bool_array(pos); } Z3_symbol * z3_replayer::get_symbol_array(unsigned pos) const { return m_imp->get_symbol_array(pos); } void ** z3_replayer::get_obj_array(unsigned pos) const { return m_imp->get_obj_array(pos); } int * z3_replayer::get_int_addr(unsigned pos) { return m_imp->get_int_addr(pos); } int64_t * z3_replayer::get_int64_addr(unsigned pos) { return m_imp->get_int64_addr(pos); } unsigned * z3_replayer::get_uint_addr(unsigned pos) { return m_imp->get_uint_addr(pos); } uint64_t * z3_replayer::get_uint64_addr(unsigned pos) { return m_imp->get_uint64_addr(pos); } Z3_string * z3_replayer::get_str_addr(unsigned pos) { return m_imp->get_str_addr(pos); } void ** z3_replayer::get_obj_addr(unsigned pos) { return m_imp->get_obj_addr(pos); } void z3_replayer::store_result(void * obj) { return m_imp->store_result(obj); } void z3_replayer::register_cmd(unsigned id, z3_replayer_cmd cmd, char const* name) { return m_imp->register_cmd(id, cmd, name); } void z3_replayer::parse() { return m_imp->parse(); }