[Decompiler] Static Data Decomp (#280)

* update all-types

* begin work on static data decompiler

* working for vif disasm array

* mostly working

* finish static data decompilation
This commit is contained in:
water111
2021-02-25 09:51:28 -05:00
committed by GitHub
parent 9d84ba8ca4
commit 791c4abfc0
31 changed files with 2064 additions and 69 deletions
+2
View File
@@ -44,6 +44,8 @@ add_library(
ObjectFile/ObjectFileDB.cpp
ObjectFile/ObjectFileDB_IR2.cpp
util/data_decompile.cpp
util/DataParser.cpp
util/DecompilerTypeSystem.cpp
util/TP_Type.cpp
+1 -1
View File
@@ -9,7 +9,7 @@ namespace decompiler {
*/
struct DecompilerLabel {
std::string name;
int target_segment;
int target_segment = 0;
int offset; // in bytes
};
} // namespace decompiler
+35 -8
View File
@@ -658,14 +658,6 @@ TP_Type LoadVarOp::get_src_type(const TypeState& input,
rd_in.offset = ro.offset;
auto rd = dts.ts.reverse_field_lookup(rd_in);
// only error on failure if "pair" is disabled. otherwise it might be a pair.
if (!rd.success && !dts.type_prop_settings.allow_pair) {
printf("input type is %s, offset is %d, sign %d size %d\n", rd_in.base_type.print().c_str(),
rd_in.offset, rd_in.deref.value().sign_extend, rd_in.deref.value().size);
throw std::runtime_error(fmt::format("Could not get type of load: {}. Reverse Deref Failed.",
to_form(env.file->labels, env).print()));
}
if (rd.success) {
// load_path_set = true;
// load_path_addr_of = rd.addr_of;
@@ -676,6 +668,41 @@ TP_Type LoadVarOp::get_src_type(const TypeState& input,
return TP_Type::make_from_ts(coerce_to_reg_type(rd.result_type));
}
if (input_type.typespec() == TypeSpec("pointer")) {
// we got a plain pointer. let's just assume we're loading an integer.
// perhaps we should disable this feature by default on 4-byte loads if we're getting
// lots of false positives for loading pointers from plain pointers.
switch (m_kind) {
case Kind::UNSIGNED:
switch (m_size) {
case 1:
case 2:
case 4:
case 8:
return TP_Type::make_from_ts(TypeSpec("uint"));
default:
break;
}
break;
case Kind::SIGNED:
switch (m_size) {
case 1:
case 2:
case 4:
case 8:
return TP_Type::make_from_ts(TypeSpec("int"));
default:
break;
}
break;
case Kind::FLOAT:
return TP_Type::make_from_ts(TypeSpec("float"));
default:
assert(false);
}
}
// rd failed, try as pair.
if (dts.type_prop_settings.allow_pair) {
// we are strict here - only permit pair-type loads from object or pair.
+18 -11
View File
@@ -1664,17 +1664,7 @@ void ConstantFloatElement::collect_vars(VariableSet&) const {}
void ConstantFloatElement::get_modified_regs(RegSet&) const {}
goos::Object ConstantFloatElement::to_form_internal(const Env&) const {
// return goos::Object::make_float(m_value);
int rounded = m_value;
bool exact_int = ((float)rounded) == m_value;
if (m_value == 0.5 || m_value == -0.5 || m_value == 0.0 || m_value == 1.0 || m_value == -1.0 ||
exact_int) {
return goos::Object::make_float(m_value);
} else {
u32 value;
memcpy(&value, &m_value, 4);
return pretty_print::build_list("the-as", "float", fmt::format("#x{:x}", value));
}
return pretty_print::float_representation(m_value);
}
StorePlainDeref::StorePlainDeref(DerefElement* dst,
@@ -1742,4 +1732,21 @@ void StoreArrayAccess::get_modified_regs(RegSet& regs) const {
m_dst->get_modified_regs(regs);
}
DecompiledDataElement::DecompiledDataElement(goos::Object description)
: m_description(std::move(description)) {}
goos::Object DecompiledDataElement::to_form_internal(const Env&) const {
return m_description;
}
void DecompiledDataElement::apply(const std::function<void(FormElement*)>& f) {
f(this);
}
void DecompiledDataElement::apply_form(const std::function<void(Form*)>&) {}
void DecompiledDataElement::collect_vars(VariableSet&) const {}
void DecompiledDataElement::get_modified_regs(RegSet&) const {}
} // namespace decompiler
+13
View File
@@ -1107,6 +1107,19 @@ class StoreArrayAccess : public FormElement {
Variable m_base_var;
};
class DecompiledDataElement : public FormElement {
public:
DecompiledDataElement(goos::Object description);
goos::Object to_form_internal(const Env& env) const override;
void apply(const std::function<void(FormElement*)>& f) override;
void apply_form(const std::function<void(Form*)>& f) override;
void collect_vars(VariableSet& vars) const override;
void get_modified_regs(RegSet& regs) const override;
private:
goos::Object m_description;
};
/*!
* A Form is a wrapper around one or more FormElements.
* This is done for two reasons:
+23 -2
View File
@@ -4,6 +4,7 @@
#include "common/goos/PrettyPrinter.h"
#include "decompiler/util/DecompilerTypeSystem.h"
#include "decompiler/ObjectFile/LinkedObjectFile.h"
#include "decompiler/util/data_decompile.h"
/*
* TODO
@@ -334,7 +335,23 @@ void SimpleExpressionElement::update_from_stack_identity(const Env& env,
auto str = env.file->get_goal_string(lab.target_segment, lab.offset / 4 - 1, false);
result->push_back(pool.alloc_element<StringConstantElement>(str));
} else {
result->push_back(this);
// look for a label hint:
auto kv = env.label_types().find(lab.name);
if (kv != env.label_types().end()) {
auto type_name = kv->second.type_name;
if (type_name == "_auto_") {
auto decompiled_data = decompile_at_label_guess_type(lab, env.file->labels,
env.file->words_by_seg, env.dts->ts);
result->push_back(pool.alloc_element<DecompiledDataElement>(decompiled_data));
} else {
auto type = env.dts->parse_type_spec(kv->second.type_name);
auto decompiled_data =
decompile_at_label(type, lab, env.file->labels, env.file->words_by_seg, env.dts->ts);
result->push_back(pool.alloc_element<DecompiledDataElement>(decompiled_data));
}
} else {
result->push_back(this);
}
}
} else if (arg.is_sym_ptr() || arg.is_sym_val() || arg.is_int() || arg.is_empty_list()) {
@@ -1194,7 +1211,11 @@ void FunctionCallElement::update_from_stack(const Env& env,
function_type = tp_type.typespec();
}
assert(is_method == m_op->is_method());
// assert(is_method == m_op->is_method());
if (is_method != m_op->is_method()) {
lg::error("Disagreement on method!");
throw std::runtime_error("Disagreement on method");
}
// if method, don't pop the obj arg.
// Variable method_obj_var;
+1 -1
View File
@@ -340,7 +340,7 @@ void rewrite_to_get_var(std::vector<FormElement*>& default_result,
std::vector<FormElement*> result;
bool first = true;
while (keep_going) {
while (keep_going && !default_result.empty()) {
keep_going = false;
auto last_op_as_set = dynamic_cast<SetVarElement*>(default_result.back());
if (last_op_as_set && last_op_as_set->dst().reg() == var_to_get.reg() &&
+19 -5
View File
@@ -5,11 +5,11 @@
* A word (4 bytes), possibly with some linking info.
*/
#ifndef JAK2_DISASSEMBLER_LINKEDWORD_H
#define JAK2_DISASSEMBLER_LINKEDWORD_H
#include <cstdint>
#include <string>
#include <cassert>
#include "common/common_types.h"
namespace decompiler {
class LinkedWord {
@@ -31,7 +31,21 @@ class LinkedWord {
int label_id = -1;
std::string symbol_name;
u8 get_byte(int idx) const {
assert(kind == PLAIN_DATA);
switch (idx) {
case 0:
return data & 0xff;
case 1:
return (data >> 8) & 0xff;
case 2:
return (data >> 16) & 0xff;
case 3:
return (data >> 24) & 0xff;
default:
assert(false);
}
}
};
} // namespace decompiler
#endif // JAK2_DISASSEMBLER_LINKEDWORD_H
+8 -1
View File
@@ -456,7 +456,14 @@ std::string ObjectFileDB::ir2_to_file(ObjectFileData& data) {
// functions
for (auto& func : data.linked_data.functions_by_seg.at(seg)) {
result += ir2_function_to_string(data, func, seg);
try {
result += ir2_function_to_string(data, func, seg);
} catch (std::exception& e) {
result += "Failed to write: ";
result += e.what();
result += "\n";
}
if (func.ir2.top_form && func.ir2.env.has_local_vars()) {
result += '\n';
if (func.ir2.env.has_local_vars()) {
+1
View File
@@ -97,6 +97,7 @@ bool convert_to_expressions(Form* top_level_form,
} catch (std::exception& e) {
f.warnings.expression_build_warning("In {}: {}", f.guessed_name.to_string(), e.what());
lg::warn("In {}: {}", f.guessed_name.to_string(), e.what());
return false;
}
+5 -1
View File
@@ -26,7 +26,11 @@ std::string final_defun_out(const Function& func,
const DecompilerTypeSystem& dts,
FunctionDefSpecials special_mode) {
std::vector<goos::Object> inline_body;
func.ir2.top_form->inline_forms(inline_body, env);
try {
func.ir2.top_form->inline_forms(inline_body, env);
} catch (std::exception& e) {
return e.what();
}
int var_count = 0;
auto var_dec = env.local_var_type_list(func.ir2.top_form, func.type.arg_count() - 1, &var_count);
+42 -26
View File
@@ -983,7 +983,7 @@
(deftype vector (structure)
(
(data float 4 :offset-assert 0)
(data float 4 :do-not-decompile :offset-assert 0)
(x float :offset 0)
(y float :offset 4)
(z float :offset 8)
@@ -3315,14 +3315,14 @@
;; texture-h
;;;;;;;;;;;;;;
; ;; texture-h
; (deftype texture-id (uint32)
; ()
; :method-count-assert 9
; :size-assert #x4
; :flag-assert #x900000004
; ;; likely a bitfield type
; )
;; texture-h
(deftype texture-id (uint32)
()
:method-count-assert 9
:size-assert #x4
:flag-assert #x900000004
;; likely a bitfield type
)
(deftype texture-pool-segment (structure)
((dest uint32 :offset-assert 0)
@@ -3402,6 +3402,8 @@
:flag-assert #x90000000c
)
(define-extern texture-mip->segment (function int int int))
;; texture-h
(deftype texture-page (basic)
((info basic :offset-assert 4)
@@ -3490,6 +3492,9 @@
:size-assert #x10
:flag-assert #x900000010
)
(define-extern *texture-relocate-later* texture-relocate-later)
;;;;;;;;;;;;;;
;; level-h
;;;;;;;;;;;;;;
@@ -3517,30 +3522,30 @@
;; level-h
(deftype level-load-info (basic)
((name-list basic 3 :offset-assert 4)
((name-list basic 3 :offset-assert 4)
(index int32 :offset-assert 16)
(name basic :offset 4)
(visname basic :offset 8)
(nickname basic :offset 12)
(packages basic :offset-assert 20)
(sound-banks basic :offset-assert 24)
(packages pair :offset-assert 20)
(sound-banks pair :offset-assert 24)
(music-bank basic :offset-assert 28)
(ambient-sounds basic :offset-assert 32)
(ambient-sounds pair :offset-assert 32)
(mood basic :offset-assert 36)
(mood-func basic :offset-assert 40)
(ocean basic :offset-assert 44)
(sky basic :offset-assert 48)
(sun-fade float :offset-assert 52)
(continues basic :offset-assert 56)
(tasks basic :offset-assert 60)
(continues pair :offset-assert 56)
(tasks pair :offset-assert 60)
(priority int32 :offset-assert 64)
(load-commands basic :offset-assert 68)
(alt-load-commands basic :offset-assert 72)
(load-commands pair :offset-assert 68)
(alt-load-commands pair :offset-assert 72)
(bsp-mask uint64 :offset-assert 80)
(bsphere sphere :offset-assert 88)
(buzzer int32 :offset-assert 92)
(bottom-height float :offset-assert 96) ;; meters
(run-packages basic :offset-assert 100)
(bottom-height float :offset-assert 96) ;; meters
(run-packages pair :offset-assert 100)
(prev-level basic :offset-assert 104)
(next-level basic :offset-assert 108)
(wait-for-load basic :offset-assert 112)
@@ -3576,6 +3581,10 @@
(texture-page basic 9 :offset-assert 60)
(loaded-texture-page basic 16 :offset-assert 96)
(loaded-texture-page-count int32 :offset-assert 160)
; (foreground-sink-group-0 dma-foreground-sink-group :inline :offset-assert 176)
; (foreground-sink-group-1 dma-foreground-sink-group :inline :offset-assert 208)
; (foreground-sink-group-2 dma-foreground-sink-group :inline :offset-assert 240)
; (array-pad uint8 12)
(foreground-sink-group dma-foreground-sink-group 3 :inline :offset-assert 176) ;; inline basic
(foreground-draw-engine basic 3 :offset-assert 272)
(entity basic :offset-assert 284)
@@ -3639,18 +3648,25 @@
;; level-h
(deftype level-group (basic)
((length int32 :offset-assert 4)
(unknown-field-1 basic :offset-assert 8)
(unknown-field-2 basic :offset-assert 12)
(entity-link entity-links :offset 16) ;; not sure what's going on here
(border? basic :offset-assert 20)
(vis? basic :offset-assert 24)
(want-level basic :offset-assert 28)
(receiving-level basic :offset-assert 32)
(load-commands basic :offset-assert 36)
(load-commands pair :offset-assert 36)
(play? basic :offset-assert 40)
; (level UNKNOWN 3 :offset-assert 100)
; (data UNKNOWN 3 :offset-assert 100)
(level0 level :inline :offset 96) ;; inline basic
;; there's something? from 40 -> 96.
(hack-pad uint8 :offset 90)
;(level level 3 :inline :offset-assert 96)
;(data level 3 :inline :offset-assert 100)
(level0 level :inline :offset-assert 96) ;; inline basic
(level1 level :inline :offset-assert 2704) ;; inline basic
(level-default level :inline :offset-assert 5312) ;; inline basic
;; this actually went earlier,
(level level 3 :inline :offset 96)
(pad uint32)
)
:method-count-assert 27
@@ -6984,7 +7000,7 @@
(quat vector :inline :offset-assert 32)
(camera-trans vector :inline :offset-assert 48)
(camera-rot float 9 :offset-assert 64)
(load-commands basic :offset-assert 100)
(load-commands pair :offset-assert 100)
(vis-nick basic :offset-assert 104)
(lev0 basic :offset-assert 108)
(disp0 basic :offset-assert 112)
@@ -32489,14 +32505,14 @@
(define-extern texture-page type)
;;(define-extern *depth-cue-base-page* object) ;; unknown type
;;(define-extern texture-pool-segment object) ;; unknown type
;;(define-extern *texture-relocate-later* object) ;; unknown type
;; ;; unknown type
;;(define-extern *sky-base-vram-word* object) ;; unknown type
;;(define-extern texture-id object) ;; unknown type
;;(define-extern *sky-base-page* object) ;; unknown type
(define-extern texture-page-dir type)
;;(define-extern texture-relocate-later object) ;; unknown type
;;(define-extern texture-link object) ;; unknown type
(define-extern texture-mip->segment function)
;;(define-extern shader-ptr object) ;; unknown type
;;(define-extern *eyes-base-vram-word* object) ;; unknown type
;;(define-extern *eyes-base-page* object) ;; unknown type
@@ -39,5 +39,47 @@
"loader-h":[
["L10", "float", true]
]
],
"dma-disasm":[
["L148", "(array vif-disasm-element)", true]
],
"level-h":[
["L3", "level-group", true]
],
"level-info":[
["L964", "level-load-info", true],
["L867", "level-load-info", true],
["L851", "level-load-info", true],
["L822", "level-load-info", true],
["L812", "level-load-info", true],
["L531", "level-load-info", true],
["L512", "level-load-info", true],
["L495", "level-load-info", true],
["L479", "level-load-info", true],
["L271", "level-load-info", true],
["L255", "level-load-info", true],
["L237", "level-load-info", true],
["L215", "level-load-info", true],
["L175", "level-load-info", true],
["L153", "level-load-info", true],
["L143", "level-load-info", true],
["L131", "level-load-info", true],
["L112", "level-load-info", true],
["L72", "level-load-info", true],
["L52", "level-load-info", true],
["L48", "level-load-info", true],
["L41", "level-load-info", true],
["L35", "level-load-info", true],
["L30", "level-load-info", true],
["L28", "level-load-info", true],
["L544", "level-load-info", true],
["L2", "pair", true]
],
"level-h":[
["L3", "_auto_", true]]
}
+232
View File
@@ -0,0 +1,232 @@
#include <stdexcept>
#include <cassert>
#include "DataParser.h"
#include "third-party/fmt/core.h"
/*
* Allowable lines:
* L123: - label
* L123: (offset 2) - label with an offset (only 2 allowed)
* .word 0xbeef - a hex word
* .word L123 - a label word
* .symbol sym - a symbol
* .empty-list - the empty list
* .type typ - a type
*/
namespace decompiler {
namespace {
std::vector<std::string> string_to_lines(const std::string& str) {
std::vector<std::string> result;
std::string::size_type i;
std::string::size_type start = 0;
while (true) {
i = str.find('\n', start);
if (i == std::string::npos) {
if (start < str.length()) {
result.push_back(str.substr(start));
}
return result;
} else {
result.push_back(str.substr(start, i - start));
start = i + 1;
}
}
}
std::string get_until_space(std::string& instr) {
assert(!instr.empty());
size_t i;
for (i = 0; i < instr.length(); i++) {
if (instr[i] == ' ') {
break;
}
}
auto name = instr.substr(0, i);
if (i == instr.length()) {
instr.clear();
} else {
instr = instr.substr(i + 1);
}
return name;
}
} // namespace
ParsedData parse_data(const std::string& str) {
ParsedData result;
struct LabelInfo {
int idx = -1;
bool defined = false;
};
std::unordered_map<std::string, LabelInfo> label_map;
const std::string offset_2 = ": (offset 2)";
auto lines = string_to_lines(str);
int byte_offset = 0;
for (auto& line : lines) {
// strip off leading white space
size_t i;
for (i = 0; i < line.length(); i++) {
if (line[i] != ' ') {
line = line.substr(i);
break;
}
}
if (line.empty()) {
continue;
}
// try as label definition.
if (line.front() == 'L') {
int offset = 0;
if (line.back() == ':') {
line.pop_back();
} else {
if (line.length() >= (2 + offset_2.length()) &&
line.substr(line.length() - offset_2.length()) == offset_2) {
line = line.substr(0, line.length() - offset_2.length());
offset = 2;
} else {
throw std::runtime_error(fmt::format("Invalid label line: {}", line));
}
}
auto& l = label_map[line];
if (l.defined) {
throw std::runtime_error(fmt::format("Label {} is multiply defined.", line));
}
l.defined = true;
if (l.idx == -1) {
l.idx = result.labels.size();
result.labels.emplace_back();
}
auto& label = result.labels.at(l.idx);
label.target_segment = 0;
label.offset = byte_offset + offset;
label.name = line;
continue;
}
auto first_thing = get_until_space(line);
// try as .type
if (first_thing == ".type") {
LinkedWord word(0);
word.kind = LinkedWord::TYPE_PTR;
word.symbol_name = line;
result.words.push_back(word);
byte_offset += 4;
continue;
}
if (first_thing == ".symbol") {
LinkedWord word(0);
word.kind = LinkedWord::SYM_PTR;
word.symbol_name = line;
result.words.push_back(word);
byte_offset += 4;
continue;
}
if (first_thing == ".empty-list") {
if (!line.empty()) {
throw std::runtime_error("Got something after .empty-list, this is not allowed");
}
LinkedWord word(0);
word.kind = LinkedWord::EMPTY_PTR;
result.words.push_back(word);
byte_offset += 4;
continue;
}
if (first_thing == ".word") {
if (!line.empty() && line.at(0) == 'L') {
auto& l = label_map[line];
if (l.idx == -1) {
l.idx = result.labels.size();
result.labels.emplace_back();
}
LinkedWord word(0);
word.kind = LinkedWord::PTR;
word.label_id = l.idx;
result.words.push_back(word);
byte_offset += 4;
continue;
} else {
auto val = std::stoull(line, nullptr, 16);
assert(val <= UINT32_MAX);
LinkedWord word(val);
word.kind = LinkedWord::PLAIN_DATA;
result.words.push_back(word);
byte_offset += 4;
continue;
}
}
}
for (auto& kv : label_map) {
if (!kv.second.defined) {
throw std::runtime_error(fmt::format("Label {} was used but not defined.", kv.first));
}
}
return result;
}
std::string ParsedData::print() const {
std::string result;
std::unordered_map<int, const DecompilerLabel*> label_map;
for (const auto& x : labels) {
label_map[x.offset] = &x;
}
for (size_t idx = 0; idx < words.size(); idx++) {
// print label
auto kv = label_map.find(idx * 4);
if (kv != label_map.end()) {
result += fmt::format("{}:\n", kv->second->name);
}
auto kv_offset = label_map.find(idx * 4 + 2);
if (kv_offset != label_map.end()) {
result += fmt::format("{}: (offset 2)\n", kv_offset->second->name);
}
// print word
auto& word = words.at(idx);
switch (word.kind) {
case LinkedWord::PLAIN_DATA:
result += fmt::format(" .word 0x{:x}\n", word.data);
break;
case LinkedWord::PTR:
result += fmt::format(" .word {}\n", labels.at(word.label_id).name);
break;
case LinkedWord::SYM_PTR:
result += fmt::format(" .symbol {}\n", word.symbol_name);
break;
case LinkedWord::TYPE_PTR:
result += fmt::format(" .type {}\n", word.symbol_name);
break;
case LinkedWord::EMPTY_PTR:
result += " .empty-list\n";
break;
default:
assert(false);
}
}
return result;
}
const DecompilerLabel& ParsedData::label(const std::string& name) const {
for (auto& x : labels) {
if (x.name == name) {
return x;
}
}
throw std::runtime_error("Could not find label " + name);
}
} // namespace decompiler
+23
View File
@@ -0,0 +1,23 @@
#pragma once
/*!
* @file DataParser.h
* A parser for the decompiled GOAL data format.
*/
#include <vector>
#include <string>
#include "decompiler/ObjectFile/LinkedWord.h"
#include "decompiler/Disasm/DecompilerLabel.h"
namespace decompiler {
struct ParsedData {
std::vector<LinkedWord> words;
std::vector<DecompilerLabel> labels;
std::string print() const;
const DecompilerLabel& label(const std::string& name) const;
};
ParsedData parse_data(const std::string& str);
} // namespace decompiler
+655
View File
@@ -0,0 +1,655 @@
#include "data_decompile.h"
#include "third-party/fmt/core.h"
#include "common/goos/PrettyPrinter.h"
#include "common/util/math_util.h"
#include "common/log/log.h"
namespace decompiler {
/*!
* Attempt to determine the type of this label. This does not make sure that the type system
* actually knows about the type. If the thing is not a basic or pair, it will fail.
*/
std::optional<TypeSpec> get_type_of_label(const DecompilerLabel& label,
const std::vector<std::vector<LinkedWord>>& words) {
if ((label.offset % 8) == 2) {
return TypeSpec("pair");
}
// try to guess the type by looking for a type pointer.
if (label.offset < 4) {
return {};
}
if ((label.offset % 8) == 4) {
auto type_ptr_word_idx = (label.offset / 4) - 1;
auto& type_ptr = words.at(label.target_segment).at(type_ptr_word_idx);
if (type_ptr.kind != LinkedWord::TYPE_PTR) {
return {};
}
if (type_ptr.symbol_name == "array") {
auto content_type_ptr_word_idx = type_ptr_word_idx + 3;
auto& content_type_ptr = words.at(label.target_segment).at(content_type_ptr_word_idx);
if (content_type_ptr.kind != LinkedWord::TYPE_PTR) {
return {};
}
return TypeSpec("array", {TypeSpec(content_type_ptr.symbol_name)});
}
return TypeSpec(type_ptr.symbol_name);
} else {
return {};
}
}
/*!
* Attempt to decompile data at the given label, without knowing the type. This can only succeed
* if the object is a basic or pair, and is intended to save the user time in these cases,
* or even be run automatically.
*/
goos::Object decompile_at_label_guess_type(const DecompilerLabel& label,
const std::vector<DecompilerLabel>& labels,
const std::vector<std::vector<LinkedWord>>& words,
const TypeSystem& ts) {
auto guessed_type = get_type_of_label(label, words);
if (!guessed_type.has_value()) {
throw std::runtime_error("Couldn't guess the type of " + label.name);
}
return decompile_at_label(*guessed_type, label, labels, words, ts);
}
/*!
* Attempt to decompile data of the given type at the given label. If the decompiler thinks the
* types do not line up, it will fail.
*/
goos::Object decompile_at_label(const TypeSpec& type,
const DecompilerLabel& label,
const std::vector<DecompilerLabel>& labels,
const std::vector<std::vector<LinkedWord>>& words,
const TypeSystem& ts) {
if (type == TypeSpec("string")) {
return decompile_string_at_label(label, words);
}
if (ts.typecheck(TypeSpec("array"), type, "", false, false)) {
return decompile_boxed_array(label, labels, words, ts);
}
if (ts.typecheck(TypeSpec("structure"), type, "", false, false)) {
return decompile_structure(type, label, labels, words, ts);
}
if (type == TypeSpec("pair")) {
return decompile_pair(label, labels, words, ts);
}
throw std::runtime_error("Unimplemented decompile_at_label for " + type.print());
}
/*!
* Special case to decompile a string into a string constant.
*/
goos::Object decompile_string_at_label(const DecompilerLabel& label,
const std::vector<std::vector<LinkedWord>>& words) {
// first, check that it's actually a string.
if (label.offset % 4) {
throw std::runtime_error(fmt::format("Cannot get string at label {}, alignment of label is {}",
label.name, label.offset));
}
assert(label.offset >= 4);
const auto& type_ptr = words.at(label.target_segment).at((label.offset - 4) / 4);
if (type_ptr.kind != LinkedWord::TYPE_PTR) {
throw std::runtime_error(fmt::format(
"Cannot get string at label {}, word before is not a type pointer.", label.name));
}
if (type_ptr.symbol_name != "string") {
throw std::runtime_error(fmt::format("Cannot get string at label {}, type pointer is for a {}.",
label.name, type_ptr.symbol_name));
}
std::string result;
auto word_idx = (label.offset / 4) - 1;
// next should be the size
if (word_idx + 1 >= int(words.at(label.target_segment).size())) {
throw std::runtime_error(
fmt::format("Cannot get string at label {}, not enough room", label.name));
}
const LinkedWord& size_word = words.at(label.target_segment).at(word_idx + 1);
if (size_word.kind != LinkedWord::PLAIN_DATA) {
// sometimes an array of string pointer triggers this!
throw std::runtime_error(
fmt::format("Cannot get string at label {}, size is not plain data.", label.name));
}
// now characters...
for (size_t i = 0; i < size_word.data; i++) {
int word_offset = word_idx + 2 + (i / 4);
int byte_offset = i % 4;
auto& word = words.at(label.target_segment).at(word_offset);
if (word.kind != LinkedWord::PLAIN_DATA) {
throw std::runtime_error(
fmt::format("Cannot get string at label {}, character is not plain data.", label.name));
}
char cword[4];
memcpy(cword, &word.data, 4);
result += cword[byte_offset];
assert(result.back() != 0);
}
return goos::StringObject::make_new(result);
}
goos::Object decompile_structure(const TypeSpec& type,
const DecompilerLabel& label,
const std::vector<DecompilerLabel>& labels,
const std::vector<std::vector<LinkedWord>>& words,
const TypeSystem& ts) {
// first step, get type info and words
TypeSpec actual_type = type;
auto uncast_type_info = ts.lookup_type(actual_type);
auto type_info = dynamic_cast<StructureType*>(uncast_type_info);
if (!type_info) {
throw std::runtime_error(fmt::format("Type {} wasn't a structure type.", actual_type.print()));
}
bool is_basic = dynamic_cast<BasicType*>(uncast_type_info);
int word_count = (type_info->get_size_in_memory() + 3) / 4;
// check alignment
auto offset_location = label.offset - type_info->get_offset();
if ((offset_location % 8) == 2) {
// TEMP HACK
lg::error("Data decompile was looking for a structure, but it looks like a pair instead.");
return decompile_pair(label, labels, words, ts);
}
if (offset_location % 8) {
throw std::runtime_error(
fmt::format("Structure type {} (type offset {}) has alignment {}, which is not valid.",
type_info->get_name(), type_info->get_offset(), (offset_location % 8)));
}
// check enough room
if (int(words.at(label.target_segment).size()) < word_count + offset_location / 4) {
throw std::runtime_error(fmt::format("Structure type {} takes up {} bytes and doesn't fit.",
type_info->get_name(), type_info->get_size_in_memory()));
}
// get words for real
std::vector<LinkedWord> obj_words;
obj_words.insert(obj_words.begin(),
words.at(label.target_segment).begin() + (offset_location / 4),
words.at(label.target_segment).begin() + (offset_location / 4) + word_count);
// status of each byte.
enum ByteStatus : u8 { ZERO_UNREAD, HAS_DATA_UNREAD, ZERO_READ, HAS_DATA_READ };
std::vector<int> field_status_per_byte;
for (int i = 0; i < word_count; i++) {
auto& w = obj_words.at(i);
switch (w.kind) {
case LinkedWord::TYPE_PTR:
case LinkedWord::PTR:
case LinkedWord::SYM_PTR:
case LinkedWord::EMPTY_PTR:
field_status_per_byte.push_back(HAS_DATA_UNREAD);
field_status_per_byte.push_back(HAS_DATA_UNREAD);
field_status_per_byte.push_back(HAS_DATA_UNREAD);
field_status_per_byte.push_back(HAS_DATA_UNREAD);
break;
case LinkedWord::PLAIN_DATA: {
u8 bytes[4];
memcpy(bytes, &w.data, 4);
for (auto b : bytes) {
field_status_per_byte.push_back(b ? HAS_DATA_UNREAD : ZERO_UNREAD);
}
} break;
default:
throw std::runtime_error("Unsupported word in static data");
}
}
std::vector<std::pair<std::string, goos::Object>> field_defs_out;
// now iterate over fields:
int idx = 0;
for (auto& field : type_info->fields()) {
if (field.skip_in_decomp()) {
idx++;
continue;
}
if (is_basic && idx == 0) {
assert(field.name() == "type" && field.offset() == 0);
auto& word = obj_words.at(0);
if (word.kind != LinkedWord::TYPE_PTR) {
throw std::runtime_error("Basic doesn't start with type pointer");
}
if (word.symbol_name != actual_type.base_type()) {
// we can specify a more specific type.
auto got_type = TypeSpec(word.symbol_name);
if (ts.typecheck(actual_type, got_type, "", false, false)) {
lg::info("For type {}, got more specific type {}\n", actual_type.print(),
got_type.print());
actual_type = got_type;
if (actual_type == TypeSpec("string")) {
return decompile_string_at_label(label, words);
}
} else {
throw std::runtime_error(
fmt::format("Basic has the wrong type pointer, got {} expected {}", word.symbol_name,
actual_type.base_type()));
}
}
for (int k = 0; k < 4; k++) {
field_status_per_byte.at(k) = HAS_DATA_READ;
}
idx++;
continue;
}
idx++;
// first, let's see if this overlaps with anything:
auto field_start = field.offset();
auto field_end = field_start + ts.get_size_in_type(field);
bool all_zero = true;
bool any_overlap = false;
for (int i = field_start; i < field_end; i++) {
auto status = field_status_per_byte.at(i);
if (status != ZERO_UNREAD && status != ZERO_READ) {
all_zero = false;
}
if (status == HAS_DATA_READ || status == ZERO_READ) {
any_overlap = true;
}
}
if (all_zero) {
// field has nothing in it, just skip it.
continue;
}
if (any_overlap) {
// for now, let's just skip fields that overlapped with the previous.
// eventually we should do something smarter here...
continue;
}
// OK - READ THE FIELD:
for (int i = field_start; i < field_end; i++) {
// even if our field was partially zero, we mark those zero bytes as "has data".
field_status_per_byte.at(i) = HAS_DATA_READ;
}
// first, let's see if it's a value or reference
auto field_type_info = ts.lookup_type(field.type());
if (!field_type_info->is_reference()) {
// value type. need to get bytes.
assert(!field.is_inline());
if (field.is_array()) {
// array of values.
auto len = field.array_size();
auto stride = ts.get_size_in_type(field) / len;
assert(stride == field_type_info->get_size_in_memory());
std::vector<goos::Object> array_def = {pretty_print::to_symbol(
fmt::format("new 'static 'array '{} {}", field.type().print(), field.array_size()))};
for (int i = 0; i < len; i++) {
auto start = field_start + stride * i;
auto end = start + field_type_info->get_size_in_memory();
std::vector<u8> elt_bytes;
for (int j = start; j < end; j++) {
auto& word = obj_words.at(j / 4);
if (word.kind != LinkedWord::PLAIN_DATA) {
throw std::runtime_error("Got bad word in kind in array of values");
}
elt_bytes.push_back(word.get_byte(j % 4));
}
array_def.push_back(decompile_value(field.type(), elt_bytes, ts));
}
field_defs_out.emplace_back(field.name(), pretty_print::build_list(array_def));
} else if (field.is_dynamic()) {
throw std::runtime_error(
fmt::format("Dynamic value field {} in static data type {} not yet implemented",
field.name(), actual_type.print()));
} else {
std::vector<u8> bytes_out;
for (int byte_idx = field_start; byte_idx < field_end; byte_idx++) {
bytes_out.push_back(obj_words.at(byte_idx / 4).get_byte(byte_idx % 4));
}
field_defs_out.emplace_back(field.name(), decompile_value(field.type(), bytes_out, ts));
}
} else {
if (!field.is_dynamic() && !field.is_array() && field.is_inline()) {
// inline structure!
DecompilerLabel fake_label;
fake_label.target_segment = label.target_segment;
// offset from real start of outer + field offset + tag, we want to fake that.
fake_label.offset = offset_location + field.offset() + field_type_info->get_offset();
fake_label.name = fmt::format("fake-label-{}-{}", actual_type.print(), field.name());
field_defs_out.emplace_back(
field.name(), decompile_at_label(field.type(), fake_label, labels, words, ts));
} else if (!field.is_dynamic() && field.is_array() && field.is_inline()) {
// it's an inline array. let's figure out the len and stride
auto len = field.array_size();
auto total_size = ts.get_size_in_type(field);
auto stride = total_size / len;
assert(stride * len == total_size);
assert(stride == align(field_type_info->get_size_in_memory(),
field_type_info->get_inline_array_stride_alignment()));
std::vector<goos::Object> array_def = {pretty_print::to_symbol(fmt::format(
"new 'static 'inline-array '{} {}", field.type().print(), field.array_size()))};
for (int elt = 0; elt < len; elt++) {
DecompilerLabel fake_label;
fake_label.target_segment = label.target_segment;
// offset from real start of outer + field offset + tag, we want to fake that.
fake_label.offset =
offset_location + field.offset() + field_type_info->get_offset() + stride * elt;
fake_label.name =
fmt::format("fake-label-{}-{}-elt-{}", actual_type.print(), field.name(), elt);
array_def.push_back(decompile_at_label(field.type(), fake_label, labels, words, ts));
}
field_defs_out.emplace_back(field.name(), pretty_print::build_list(array_def));
} else if (!field.is_dynamic() && field.is_array() && !field.is_inline()) {
auto len = field.array_size();
auto total_size = ts.get_size_in_type(field);
auto stride = total_size / len;
assert(stride * len == total_size);
assert(stride == 4);
std::vector<goos::Object> array_def = {pretty_print::to_symbol(
fmt::format("new 'static 'array '{} {}", field.type().print(), field.array_size()))};
for (int elt = 0; elt < len; elt++) {
auto& word = obj_words.at((field_start / 4) + elt);
if (word.kind == LinkedWord::PTR) {
array_def.push_back(
decompile_at_label(field.type(), labels.at(word.label_id), labels, words, ts));
} else if (word.kind == LinkedWord::PLAIN_DATA && word.data == 0) {
// do nothing, the default is zero?
array_def.push_back(pretty_print::to_symbol("0"));
} else if (word.kind == LinkedWord::SYM_PTR) {
if (word.symbol_name == "#f" || word.symbol_name == "#t") {
array_def.push_back(pretty_print::to_symbol(fmt::format("{}", word.symbol_name)));
} else {
array_def.push_back(pretty_print::to_symbol(fmt::format("'{}", word.symbol_name)));
}
} else if (word.kind == LinkedWord::EMPTY_PTR) {
array_def.push_back(pretty_print::to_symbol("'()"));
} else {
throw std::runtime_error(
fmt::format("Field {} in type {} offset {} did not have a proper reference for "
"array element {}",
field.name(), actual_type.print(), field.offset(), elt));
}
}
field_defs_out.emplace_back(field.name(), pretty_print::build_list(array_def));
} else if (field.is_dynamic() || field.is_array() || field.is_inline()) {
throw std::runtime_error(fmt::format(
"Dynamic/array/inline reference field {} type {} in static data not yet implemented",
field.name(), actual_type.print()));
} else {
// then we expect a label.
assert(field_end - field_start == 4);
auto& word = obj_words.at(field_start / 4);
if (word.kind == LinkedWord::PTR) {
field_defs_out.emplace_back(
field.name(),
decompile_at_label(field.type(), labels.at(word.label_id), labels, words, ts));
} else if (word.kind == LinkedWord::PLAIN_DATA && word.data == 0) {
// do nothing, the default is zero?
field_defs_out.emplace_back(field.name(), pretty_print::to_symbol("0"));
} else if (word.kind == LinkedWord::SYM_PTR) {
if (word.symbol_name == "#f" || word.symbol_name == "#t") {
field_defs_out.emplace_back(
field.name(), pretty_print::to_symbol(fmt::format("{}", word.symbol_name)));
} else {
field_defs_out.emplace_back(
field.name(), pretty_print::to_symbol(fmt::format("'{}", word.symbol_name)));
}
} else if (word.kind == LinkedWord::EMPTY_PTR) {
field_defs_out.emplace_back(field.name(), pretty_print::to_symbol("'()"));
} else {
throw std::runtime_error(
fmt::format("Field {} in type {} offset {} did not have a proper reference",
field.name(), actual_type.print(), field.offset()));
}
}
}
}
for (size_t i = 0; i < field_status_per_byte.size(); i++) {
if (field_status_per_byte.at(i) == HAS_DATA_UNREAD) {
throw std::runtime_error(
fmt::format("In structure of type {} at label {} offset {}, there was unknown data.",
actual_type.print(), label.name, i));
}
}
std::vector<goos::Object> result_def = {
pretty_print::to_symbol("new"), pretty_print::to_symbol("'static"),
pretty_print::to_symbol(fmt::format("'{}", actual_type.print()))};
for (auto& f : field_defs_out) {
result_def.push_back(pretty_print::to_symbol(fmt::format(":{}", f.first)));
result_def.push_back(f.second);
}
return pretty_print::build_list(result_def);
}
goos::Object decompile_value(const TypeSpec& type,
const std::vector<u8>& bytes,
const TypeSystem& ts) {
// try as common integer types:
if (ts.typecheck(TypeSpec("uint32"), type, "", false, false)) {
assert(bytes.size() == 4);
u32 value;
memcpy(&value, bytes.data(), 4);
return pretty_print::to_symbol(fmt::format("#x{:x}", u64(value)));
} else if (ts.typecheck(TypeSpec("int32"), type, "", false, false)) {
assert(bytes.size() == 4);
s32 value;
memcpy(&value, bytes.data(), 4);
if (value > 100 && value <= INT32_MAX) {
return pretty_print::to_symbol(fmt::format("#x{:x}", value));
} else {
return pretty_print::to_symbol(fmt::format("{}", value));
}
} else if (ts.typecheck(TypeSpec("int8"), type, "", false, false)) {
assert(bytes.size() == 1);
s8 value;
memcpy(&value, bytes.data(), 1);
if (value > 5) {
return pretty_print::to_symbol(fmt::format("#x{:x}", value));
} else {
return pretty_print::to_symbol(fmt::format("{}", value));
}
} else if (ts.typecheck(TypeSpec("uint64"), type, "", false, false)) {
assert(bytes.size() == 8);
u64 value;
memcpy(&value, bytes.data(), 8);
return pretty_print::to_symbol(fmt::format("#x{:x}", value));
} else if (ts.typecheck(TypeSpec("float"), type, "", false, false)) {
assert(bytes.size() == 4);
float value;
memcpy(&value, bytes.data(), 4);
return pretty_print::float_representation(value);
}
else {
throw std::runtime_error(fmt::format("decompile_value failed on a {}", type.print()));
}
}
goos::Object decompile_boxed_array(const DecompilerLabel& label,
const std::vector<DecompilerLabel>& labels,
const std::vector<std::vector<LinkedWord>>& words,
const TypeSystem& ts) {
TypeSpec content_type;
auto type_ptr_word_idx = (label.offset / 4) - 1;
if ((label.offset % 8) == 4) {
auto& type_ptr = words.at(label.target_segment).at(type_ptr_word_idx);
if (type_ptr.kind != LinkedWord::TYPE_PTR) {
throw std::runtime_error("Invalid basic in decompile_boxed_array");
}
if (type_ptr.symbol_name == "array") {
auto content_type_ptr_word_idx = type_ptr_word_idx + 3;
auto& content_type_ptr = words.at(label.target_segment).at(content_type_ptr_word_idx);
if (content_type_ptr.kind != LinkedWord::TYPE_PTR) {
throw std::runtime_error("Invalid content in decompile_boxed_array");
}
content_type = TypeSpec(content_type_ptr.symbol_name);
} else {
throw std::runtime_error("Wrong basic type in decompile_boxed_array");
}
} else {
throw std::runtime_error("Invalid alignment in decompile_boxed_array");
}
// now get the size
auto& size_word_1 = words.at(label.target_segment).at(type_ptr_word_idx + 1);
auto& size_word_2 = words.at(label.target_segment).at(type_ptr_word_idx + 2);
auto first_elt_word_idx = type_ptr_word_idx + 4;
if (size_word_1.kind != LinkedWord::PLAIN_DATA || size_word_2.kind != LinkedWord::PLAIN_DATA) {
throw std::runtime_error("Invalid size in decompile_boxed_array");
}
if (size_word_1.data != size_word_2.data) {
throw std::runtime_error("Inconsistent size in decompile_boxed_array");
}
int array_length = size_word_1.data;
auto content_type_info = ts.lookup_type(content_type);
if (content_type_info->is_reference()) {
// easy, stride of 4.
std::vector<goos::Object> result = {
pretty_print::to_symbol("new"), pretty_print::to_symbol("'static"),
pretty_print::to_symbol("'boxed-array"), pretty_print::to_symbol(content_type.print()),
pretty_print::to_symbol(fmt::format("{}", array_length))};
for (int elt = 0; elt < array_length; elt++) {
auto& word = words.at(label.target_segment).at(first_elt_word_idx + elt);
if (word.kind == LinkedWord::PLAIN_DATA && word.data == 0) {
result.push_back(pretty_print::to_symbol("0"));
} else if (word.kind == LinkedWord::PTR) {
result.push_back(
decompile_at_label(content_type, labels.at(word.label_id), labels, words, ts));
} else {
throw std::runtime_error(
fmt::format("Unknown content type in boxed array of references, word idx {}",
first_elt_word_idx + elt));
}
}
return pretty_print::build_list(result);
} else {
// value type.
throw std::runtime_error("boxed value type array decompile not yet implemented.");
}
}
namespace {
goos::Object decompile_pair_elt(const LinkedWord& word,
const std::vector<DecompilerLabel>& labels,
const std::vector<std::vector<LinkedWord>>& words,
const TypeSystem& ts) {
if (word.kind == LinkedWord::PTR) {
return decompile_at_label_guess_type(labels.at(word.label_id), labels, words, ts);
} else if (word.kind == LinkedWord::PLAIN_DATA && word.data == 0) {
// do nothing, the default is zero?
return pretty_print::to_symbol("0");
} else if (word.kind == LinkedWord::SYM_PTR) {
if (word.symbol_name == "#f" || word.symbol_name == "#t") {
return pretty_print::to_symbol(fmt::format("{}", word.symbol_name));
} else {
return pretty_print::to_symbol(fmt::format("'{}", word.symbol_name));
}
} else if (word.kind == LinkedWord::EMPTY_PTR) {
return pretty_print::to_symbol("'()");
} else if (word.kind == LinkedWord::PLAIN_DATA && (word.data & 0b111) == 0) {
return pretty_print::to_symbol(fmt::format("(the binteger {})", word.data >> 3));
} else {
throw std::runtime_error(fmt::format("Pair elt did not have a good word kind"));
}
}
} // namespace
goos::Object decompile_pair(const DecompilerLabel& label,
const std::vector<DecompilerLabel>& labels,
const std::vector<std::vector<LinkedWord>>& words,
const TypeSystem& ts) {
if ((label.offset % 8) != 2) {
if ((label.offset % 4) != 0) {
throw std::runtime_error(fmt::format("Invalid alignment for pair {}\n", label.offset % 16));
} else {
auto& word = words.at(label.target_segment).at(label.offset / 4);
if (word.kind != LinkedWord::EMPTY_PTR) {
throw std::runtime_error(
fmt::format("Based on alignment, expected to get empty list for pair, but didn't"));
}
return pretty_print::to_symbol("'()");
}
}
std::vector<goos::Object> list_tokens;
auto to_print = label;
for (int iter = 0;; iter++) {
if (iter > 10000) {
throw std::runtime_error(
"Exceeded 10,000 look ups while trying to follow a linked list. Giving up, the list is "
"possibly circular. Increase the limit in data_decompile.cpp if you really need more.");
}
if ((to_print.offset % 8) == 2) {
// continue
auto car_word = words.at(to_print.target_segment).at((to_print.offset - 2) / 4);
list_tokens.push_back(decompile_pair_elt(car_word, labels, words, ts));
auto cdr_word = words.at(to_print.target_segment).at((to_print.offset + 2) / 4);
// if empty
if (cdr_word.kind == LinkedWord::EMPTY_PTR) {
return pretty_print::build_list(list_tokens);
}
// if pointer
if (cdr_word.kind == LinkedWord::PTR) {
to_print = labels.at(cdr_word.label_id);
continue;
}
// invalid.
lg::error(
"There is an improper list. This is probably okay, but should be checked manually "
"because we "
"could not find a test case yet.");
list_tokens.push_back(pretty_print::to_symbol("."));
list_tokens.push_back(decompile_pair_elt(cdr_word, labels, words, ts));
return pretty_print::build_list(list_tokens);
} else {
if ((to_print.offset % 4) != 0) {
throw std::runtime_error(
fmt::format("Invalid alignment for pair {}\n", to_print.offset % 16));
} else {
auto& word = words.at(to_print.target_segment).at(to_print.offset / 4);
if (word.kind != LinkedWord::EMPTY_PTR) {
throw std::runtime_error(
fmt::format("Based on alignment, expected to get empty list for pair, but didn't"));
}
// improper list
lg::error(
"There is an improper list. This is probably okay, but should be checked manually "
"because we "
"could not find a test case yet.");
list_tokens.push_back(pretty_print::to_symbol("."));
list_tokens.push_back(decompile_pair_elt(
words.at(to_print.target_segment).at(to_print.offset / 4), labels, words, ts));
return pretty_print::build_list(list_tokens);
}
}
}
}
} // namespace decompiler
+41
View File
@@ -0,0 +1,41 @@
#pragma once
#include <optional>
#include "common/goos/Object.h"
#include "decompiler/Disasm/DecompilerLabel.h"
#include "decompiler/ObjectFile/LinkedWord.h"
#include "common/type_system/TypeSpec.h"
#include "common/type_system/TypeSystem.h"
namespace decompiler {
std::optional<TypeSpec> get_type_of_label(const DecompilerLabel& label,
const std::vector<std::vector<LinkedWord>>& words);
goos::Object decompile_string_at_label(const DecompilerLabel& label,
const std::vector<std::vector<LinkedWord>>& words);
goos::Object decompile_at_label(const TypeSpec& type,
const DecompilerLabel& label,
const std::vector<DecompilerLabel>& labels,
const std::vector<std::vector<LinkedWord>>& words,
const TypeSystem& ts);
goos::Object decompile_at_label_guess_type(const DecompilerLabel& label,
const std::vector<DecompilerLabel>& labels,
const std::vector<std::vector<LinkedWord>>& words,
const TypeSystem& ts);
goos::Object decompile_structure(const TypeSpec& actual_type,
const DecompilerLabel& label,
const std::vector<DecompilerLabel>& labels,
const std::vector<std::vector<LinkedWord>>& words,
const TypeSystem& ts);
goos::Object decompile_pair(const DecompilerLabel& label,
const std::vector<DecompilerLabel>& labels,
const std::vector<std::vector<LinkedWord>>& words,
const TypeSystem& ts);
goos::Object decompile_boxed_array(const DecompilerLabel& label,
const std::vector<DecompilerLabel>& labels,
const std::vector<std::vector<LinkedWord>>& words,
const TypeSystem& ts);
goos::Object decompile_value(const TypeSpec& type,
const std::vector<u8>& bytes,
const TypeSystem& ts);
} // namespace decompiler