#include "cfg/builder.hpp"
#include <queue>
#include <algorithm>

static bool isMemOp(const string& op)
{
    return op.find('[') != string::npos || op.find('+') != string::npos;
}

static bool regMatch(const string& op, const string& target)
{
    auto norm = [](const string& s) -> string {
        if (s.size() >= 3 && s[0] == 'r' && isdigit(s[1])) return s.substr(1);
        if (s.size() >= 3 && s[0] == 'e' && isdigit(s[1])) return s.substr(1);
        return s;
    };
    return norm(op) == norm(target) || op == target;
}

CFGBuilder::CFGBuilder(Binary* binary, Disassembler* disasm, bool subsOnly)
    : m_bin(binary), m_dis(disasm), m_subs_only(subsOnly) {}

addr_t CFGBuilder::skipEndbr64(addr_t addr)
{
    auto bytes = m_bin->readBytes(addr, 4);
    if (bytes.size() >= 4)
    {
        // endbr64: F3 0F 1E FA
        // endbr32: F3 0F 1E FB
        if (bytes[0] == 0xF3 && bytes[1] == 0x0F && bytes[2] == 0x1E &&
            (bytes[3] == 0xFA || bytes[3] == 0xFB))
            return addr + 4;
    }
    return addr;
}

bool CFGBuilder::isVisited(addr_t addr)
{
    return m_blocks.count(addr) > 0;
}

void CFGBuilder::markVisited(addr_t addr)
{
    m_blocks.insert(addr);
}

void CFGBuilder::buildGotMap()
{
    auto imports = m_bin->getImportedFunctions();
    for (const auto& imp : imports)
    {
        if (imp.address)
            m_got[imp.address] = {imp.name, imp.library};
    }
}

addr_t CFGBuilder::resolveGOT(const Instruction& inst)
{
    if (!m_dis->isIndirectBranch(inst)) return 0;
    int64_t disp = m_dis->getRIPDisp(inst);
    if (disp == 0) return 0;

    addr_t got = inst.address + inst.size + disp;
    auto it = m_got.find(got);
    if (it != m_got.end())
    {
        if (!it->second.first.empty()) return got;
    }

    auto bytes = m_bin->readBytes(got, 8);
    if (bytes.size() < 4) return 0;

    uint64_t target = 0;
    if (bytes.size() >= 8)
    {
        target = bytes[0] | ((uint64_t)bytes[1] << 8) | ((uint64_t)bytes[2] << 16) |
                 ((uint64_t)bytes[3] << 24) | ((uint64_t)bytes[4] << 32) |
                 ((uint64_t)bytes[5] << 40) | ((uint64_t)bytes[6] << 48) |
                 ((uint64_t)bytes[7] << 56);
    }
    else
    {
        target = bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24);
    }

    return target;
}

addr_t CFGBuilder::resolveJumpTable(const vector<Instruction>& block, const Instruction& inst)
{
    size_t n = block.size();
    if (n < 3) return 0;

    int idx = -1;
    for (int i = (int)n - 1; i >= 0; i--)
    {
        if (block[i].address == inst.address) { idx = i; break; }
    }
    if (idx < 2) return 0;

    for (int i = idx - 1; i >= max(0, idx - 8); i--)
    {
        if (block[i].mnemonic == "lea")
        {
            int64_t disp = m_dis->getRIPDisp(block[i]);
            if (disp == 0) continue;
            addr_t table = block[i].address + block[i].size + disp;

            int64_t bound = -1;
            for (int j = i - 1; j >= max(0, i - 4); j--)
            {
                if (block[j].mnemonic == "cmp")
                {
                    size_t pos = block[j].operands.find("0x");
                    if (pos != string::npos)
                    {
                        try { bound = stoll(block[j].operands.substr(pos), nullptr, 16); }
                        catch (...) {}
                    }
                    break;
                }
            }
            if (bound <= 0) bound = 32;

            for (int64_t e = 0; e < bound && e < 256; e++)
            {
                auto ent = m_bin->readBytes(table + e * 8, 8);
                if (ent.size() < 4) break;
                uint64_t entry = 0;
                if (ent.size() >= 8)
                    entry = ent[0] | ((uint64_t)ent[1] << 8) | ((uint64_t)ent[2] << 16) |
                            ((uint64_t)ent[3] << 24) | ((uint64_t)ent[4] << 32) |
                            ((uint64_t)ent[5] << 40) | ((uint64_t)ent[6] << 48) | ((uint64_t)ent[7] << 56);
                else
                    entry = ent[0] | (ent[1] << 8) | (ent[2] << 16) | (ent[3] << 24);

                if (entry == 0 || entry > 0x100000000ULL) break;
                auto ranges = m_bin->getExecutableRanges();
                bool ok = false;
                for (const auto& r : ranges)
                    if (entry >= r.first && entry < r.second) { ok = true; break; }
                if (ok) return table;
                else break;
            }
        }
    }
    return 0;
}

addr_t CFGBuilder::traceReg(const string& reg, const vector<Instruction>& block, size_t idx)
{
    if (idx >= block.size() || idx == 0) return 0;
    string cur = reg;

    for (int i = (int)idx - 1; i >= 0; i--)
    {
        const auto& inst = block[i];
        if (inst.mnemonic == "nop" || inst.mnemonic == "cmp" || inst.mnemonic == "test")
            continue;

        size_t comma = inst.operands.find(',');
        string dst = comma != string::npos ? inst.operands.substr(0, comma) : inst.operands;
        while (!dst.empty() && dst[0] == ' ') dst = dst.substr(1);
        while (!dst.empty() && dst.back() == ' ') dst.pop_back();

        if (inst.mnemonic == "mov" || inst.mnemonic == "lea")
        {
            if (dst == cur || dst == "r" + cur.substr(1) || regMatch(dst, cur))
            {
                if (inst.mnemonic == "mov" && !inst.operands.empty())
                {
                    size_t pos = inst.operands.rfind("0x");
                    if (pos != string::npos)
                    {
                        try { return stoull(inst.operands.substr(pos), nullptr, 16); }
                        catch (...) {}
                    }
                }
                if (inst.mnemonic == "lea")
                {
                    int64_t disp = m_dis->getRIPDisp(inst);
                    if (disp != 0) return inst.address + inst.size + disp;
                }
                if (comma != string::npos)
                {
                    string src = inst.operands.substr(comma + 1);
                    while (!src.empty() && src[0] == ' ') src = src.substr(1);
                    while (!src.empty() && src.back() == ' ') src.pop_back();
                    if (!src.empty() && src[0] != '0' && !isMemOp(src))
                    {
                        cur = src;
                        continue;
                    }
                }
                return 0;
            }
        }
        if (dst == cur || regMatch(dst, cur)) return 0;
    }
    return 0;
}

void CFGBuilder::recordIndirect(const Instruction& inst, addr_t target, bool ok, const string& name, const string& lib)
{
    IndirectTarget it;
    it.instruction_addr = inst.address;
    it.resolved_target = ok ? target : 0;
    it.resolved = ok;
    it.name = name;
    it.library = lib;
    m_targets.push_back(it);
}

BasicBlock CFGBuilder::disassembleBlock(addr_t start, addr_t& next)
{
    BasicBlock block;
    block.address = start;
    block.is_prolog = false;
    block.is_epilog = false;

    addr_t cur = start;
    auto ranges = m_bin->getExecutableRanges();

    auto inExec = [&](addr_t a) -> bool {
        for (const auto& r : ranges)
            if (a >= r.first && a < r.second) return true;
        return false;
    };

    for (size_t i = 0; i < 5000; ++i)
    {
        if (!inExec(cur)) { next = 0; break; }

        auto bytes = m_bin->readBytes(cur, 15);
        if (bytes.empty()) { next = 0; break; }

        auto insts = m_dis->disassemble(bytes, cur, 1);
        if (insts.empty()) { next = 0; break; }

        auto& inst = insts[0];
        block.instructions.push_back(inst);

        if (i <= 3 && !block.is_prolog && m_dis->isProlog(block.instructions))
            block.is_prolog = true;

        if (m_dis->isEpilog(inst))
            block.is_epilog = true;

        if (m_dis->isReturn(inst)) { next = 0; break; }
        if (m_dis->isTrap(inst)) { next = 0; break; }

        if (m_dis->isUnconditionalBranch(inst))
        {
            addr_t t = m_dis->getBranchTarget(inst);
            if (t && !m_dis->isIndirectBranch(inst))
            {
                t = skipEndbr64(t);
                block.successors.push_back(t);
                if (m_prologs.count(t))
                    m_funcs.insert(t);
            }
            else if (m_dis->isIndirectBranch(inst))
            {
                addr_t resolved = resolveGOT(inst);
                if (!resolved)
                {
                    resolved = resolveJumpTable(block.instructions, inst);
                    if (resolved)
                    {
                        for (int j = (int)block.instructions.size() - 2; j >= 0; j--)
                        {
                            if (block.instructions[j].mnemonic == "lea")
                            {
                                int64_t ld = m_dis->getRIPDisp(block.instructions[j]);
                                if (ld != 0)
                                {
                                    addr_t tbl = block.instructions[j].address + block.instructions[j].size + ld;
                                    for (int64_t e = 0; e < 64; e++)
                                    {
                                        auto eb = m_bin->readBytes(tbl + e * 8, 8);
                                        if (eb.size() < 4) break;
                                        uint64_t entry = 0;
                                        if (eb.size() >= 8)
                                            entry = eb[0] | ((uint64_t)eb[1] << 8) | ((uint64_t)eb[2] << 16) |
                                                    ((uint64_t)eb[3] << 24) | ((uint64_t)eb[4] << 32) |
                                                    ((uint64_t)eb[5] << 40) | ((uint64_t)eb[6] << 48) | ((uint64_t)eb[7] << 56);
                                        else
                                            entry = eb[0] | (eb[1] << 8) | (eb[2] << 16) | (eb[3] << 24);
                                        if (entry == 0 || entry > 0x100000000ULL) break;
                                        bool ok = false;
                                        for (const auto& r : ranges)
                                            if (entry >= r.first && entry < r.second) { ok = true; break; }
                                        if (ok) block.successors.push_back(entry);
                                        else break;
                                    }
                                    resolved = tbl;
                                    break;
                                }
                            }
                        }
                    }
                }
                if (!resolved)
                {
                    string reg = inst.operands;
                    while (!reg.empty() && reg[0] == ' ') reg = reg.substr(1);
                    resolved = traceReg(reg, block.instructions, block.instructions.size() - 1);
                    if (resolved)
                    {
                        block.successors.push_back(resolved);
                        recordIndirect(inst, resolved, true, "traced", "");
                    }
                    else
                    {
                        recordIndirect(inst, 0, false, "unresolved", "");
                    }
                }
                else
                {
                    auto git = m_got.find(resolved);
                    string nm = git != m_got.end() ? git->second.first : "";
                    string lb = git != m_got.end() ? git->second.second : "";
                    block.successors.push_back(resolved);
                    recordIndirect(inst, resolved, true, nm, lb);
                }
            }
            next = 0; break;
        }

        if (m_dis->isConditionalBranch(inst))
        {
            addr_t t = m_dis->getBranchTarget(inst);
            if (t && !m_dis->isIndirectBranch(inst))
                block.successors.push_back(t);
            block.successors.push_back(cur + inst.size);
            next = cur + inst.size; break;
        }

        if (m_dis->isCall(inst))
        {
            addr_t t = m_dis->getBranchTarget(inst);
            if (t && !m_dis->isIndirectBranch(inst))
            {
                t = skipEndbr64(t);
                m_funcs.insert(t);
                recordIndirect(inst, t, true, "", "");
            }
            else if (m_dis->isIndirectBranch(inst))
            {
                addr_t resolved = resolveGOT(inst);
                if (resolved)
                {
                    auto git = m_got.find(resolved);
                    string nm = git != m_got.end() ? git->second.first : "";
                    string lb = git != m_got.end() ? git->second.second : "";
                    auto gb = m_bin->readBytes(resolved, 8);
                    if (gb.size() >= 8)
                    {
                        uint64_t target = gb[0] | ((uint64_t)gb[1] << 8) |
                                          ((uint64_t)gb[2] << 16) | ((uint64_t)gb[3] << 24) |
                                          ((uint64_t)gb[4] << 32) | ((uint64_t)gb[5] << 40) |
                                          ((uint64_t)gb[6] << 48) | ((uint64_t)gb[7] << 56);
                        if (target && target < 0x100000000ULL) resolved = target;
                    }
                    resolved = skipEndbr64(resolved);
                    m_funcs.insert(resolved);
                    recordIndirect(inst, resolved, true, nm, lb);
                }
                else
                {
                    string reg = inst.operands;
                    while (!reg.empty() && reg[0] == ' ') reg = reg.substr(1);
                    resolved = traceReg(reg, block.instructions, block.instructions.size() - 1);
                    if (resolved)
                    {
                        resolved = skipEndbr64(resolved);
                        m_funcs.insert(resolved);
                        recordIndirect(inst, resolved, true, "traced", "");
                    }
                    else
                    {
                        recordIndirect(inst, 0, false, "unresolved", "");
                    }
                }
            }
        }

        cur += inst.size;
        next = cur;
    }

    return block;
}

set<addr_t> CFGBuilder::findPrologs()
{
    set<addr_t> cand;
    auto ranges = m_bin->getExecutableRanges();

    for (const auto& r : ranges)
    {
        addr_t a = r.first;
        while (a < r.second)
        {
            auto bytes = m_bin->readBytes(a, 15);
            if (bytes.empty()) { a += 1; continue; }
            auto insts = m_dis->disassemble(bytes, a, 1);
            if (insts.empty()) { a += 1; continue; }
            auto& inst = insts[0];

            if (inst.mnemonic == "endbr64")
            {
                auto nb = m_bin->readBytes(a + inst.size, 15);
                auto ni = m_dis->disassemble(nb, a + inst.size, 2);
                if (!ni.empty() && m_dis->isProlog(ni)) cand.insert(a);
            }
            else
            {
                vector<Instruction> tmp{inst};
                if (m_dis->isProlog(tmp)) cand.insert(a);
            }

            a += inst.size;
        }
    }

    return cand;
}

Function CFGBuilder::buildFunction(addr_t start, const string& name)
{
    Function func;
    func.address = start;
    func.name = name;

    addr_t actual = skipEndbr64(start);

    queue<addr_t> q;
    q.push(actual);

    while (!q.empty())
    {
        addr_t a = q.front(); q.pop();
        if (isVisited(a)) continue;
        markVisited(a);

        addr_t nxt = a;
        auto block = disassembleBlock(a, nxt);
        for (auto s : block.successors)
            if (!isVisited(s)) q.push(s);

        func.blocks.push_back(block);
    }

    // Compute end_address as max instruction address across all blocks
    for (const auto& b : func.blocks)
        for (const auto& inst : b.instructions)
            if (inst.address + inst.size > func.end_address)
                func.end_address = inst.address + inst.size;

    // Tag PLT stubs
    if (isPLTStub(func)) func.is_thunk = true;

    return func;
}

bool CFGBuilder::isPLTStub(const Function& func) const
{
    if (func.blocks.size() != 1) return false;
    const auto& block = func.blocks[0];
    if (block.instructions.empty()) return false;
    const auto& first = block.instructions[0];
    if (first.mnemonic == "jmp" && first.operands.find('[') != string::npos)
    {
        int64_t disp = m_dis->getRIPDisp(first);
        if (disp != 0)
        {
            addr_t target = first.address + first.size + disp;
            if (m_got.count(target)) return true;
        }
    }
    return false;
}

void CFGBuilder::scanDataPointers()
{
    auto ranges = m_bin->getDataRanges();
    auto exec = m_bin->getExecutableRanges();

    auto isInExec = [&](addr_t addr) -> bool {
        for (const auto& r : exec)
            if (addr >= r.first && addr < r.second) return true;
        return false;
    };

    for (const auto& r : ranges)
    {
        addr_t a = r.first;
        // Align to 8 bytes
        if (a & 7) a = (a + 7) & ~7ULL;
        for (; a + 8 <= r.second; a += 8)
        {
            auto bytes = m_bin->readBytes(a, 8);
            if (bytes.size() < 8) continue;
            uint64_t val = bytes[0] | ((uint64_t)bytes[1] << 8) |
                           ((uint64_t)bytes[2] << 16) | ((uint64_t)bytes[3] << 24) |
                           ((uint64_t)bytes[4] << 32) | ((uint64_t)bytes[5] << 40) |
                           ((uint64_t)bytes[6] << 48) | ((uint64_t)bytes[7] << 56);
            if (val && isInExec(val))
                m_funcs.insert(val);
        }
    }
}

CFG CFGBuilder::build()
{
    CFG cfg;
    cfg.binary_path = m_bin->getPath();
    cfg.entry_point = m_bin->getEntryPoint();

    switch (m_bin->getArch())
    {
        case Arch::X86: cfg.arch_str = "x86"; break;
        case Arch::X64: cfg.arch_str = "x86-64"; break;
        default: cfg.arch_str = "unknown";
    }
    switch (m_bin->getFormat())
    {
        case BinaryFormat::PE: cfg.format_str = "PE"; break;
        case BinaryFormat::ELF: cfg.format_str = "ELF"; break;
        default: cfg.format_str = "unknown";
    }

    cfg.imports = m_bin->getImportedFunctions();
    buildGotMap();

    m_prologs = findPrologs();

    set<addr_t> built;
    queue<addr_t> pending;

    // Apply .pdata end_address to functions
    auto pdata = m_bin->getRuntimeFunctions();
    auto setEndAddress = [&](Function& func) {
        for (const auto& rf : pdata)
            if (rf.first == func.address)
            {
                func.end_address = rf.second;
                break;
            }
    };

    auto isEmpty = [](const Function& f) -> bool {
        for (const auto& b : f.blocks)
            if (!b.instructions.empty()) return false;
        return true;
    };

    auto scanCallAndJmpTargets = [&](const Function& func) {
        for (const auto& b : func.blocks)
            for (const auto& inst : b.instructions)
            {
                if (m_dis->isCall(inst))
                {
                    addr_t t = m_dis->getBranchTarget(inst);
                    if (t && !m_dis->isIndirectBranch(inst))
                    {
                        t = skipEndbr64(t);
                        m_funcs.insert(t);
                    }
                }
                if (m_dis->isUnconditionalBranch(inst))
                {
                    addr_t t = m_dis->getBranchTarget(inst);
                    if (t && !m_dis->isIndirectBranch(inst))
                    {
                        t = skipEndbr64(t);
                        if (m_prologs.count(t))
                            m_funcs.insert(t);
                    }
                }
            }
    };

    auto process = [&](addr_t addr, const string& name)
    {
        addr = skipEndbr64(addr);
        if (built.count(addr)) return;
        built.insert(addr);

        auto func = buildFunction(addr, name);
        if (isEmpty(func)) return;
        setEndAddress(func);
        cfg.functions.push_back(func);
        scanCallAndJmpTargets(func);

        for (auto d : m_funcs)
            if (!built.count(d)) pending.push(d);
    };

    {
        addr_t ep = skipEndbr64(m_bin->getEntryPoint());
        string ep_name = "entry";
        for (const auto& exp : m_bin->getExportedFunctions())
            if (exp.first == ep) { ep_name = exp.second; break; }
        process(ep, ep_name);
    }

    if (!m_subs_only)
    {
        for (const auto& exp : m_bin->getExportedFunctions())
            pending.push(skipEndbr64(exp.first));
        for (const auto& rf : pdata)
            if (rf.first != m_bin->getEntryPoint())
                pending.push(skipEndbr64(rf.first));
    }

    map<addr_t, string> imp_map;
    for (const auto& imp : m_bin->getImportedFunctions())
        imp_map[imp.address] = imp.name;

    while (!pending.empty())
    {
        addr_t a = pending.front(); pending.pop();
        a = skipEndbr64(a);
        if (built.count(a)) continue;

        string name;
        for (const auto& exp : m_bin->getExportedFunctions())
            if (exp.first == a) { name = exp.second; break; }
        if (name.empty())
        {
            auto it = imp_map.find(a);
            if (it != imp_map.end()) name = it->second;
        }

        auto func = buildFunction(a, name);
        if (isEmpty(func)) continue;
        setEndAddress(func);
        cfg.functions.push_back(func);
        scanCallAndJmpTargets(func);

        if (!m_subs_only)
        {
            for (auto p : m_prologs)
                if (!built.count(p)) pending.push(p);
        }
    }

    // Add data-section function pointers as seeds (if not subs-only)
    if (!m_subs_only)
    {
        scanDataPointers();
        for (auto d : m_funcs)
            if (!built.count(d)) pending.push(d);
        while (!pending.empty())
        {
            addr_t a = pending.front(); pending.pop();
            a = skipEndbr64(a);
            if (built.count(a)) continue;

            auto func = buildFunction(a, "");
            if (isEmpty(func)) continue;
            setEndAddress(func);
            cfg.functions.push_back(func);
            scanCallAndJmpTargets(func);
        }
    }

    cfg.indirect_targets = m_targets;
    return cfg;
}

static int64_t stackDelta(const Instruction& inst)
{
    if (inst.mnemonic == "push") return -8;
    if (inst.mnemonic == "pop") return 8;
    if (inst.mnemonic == "call") return 0;
    if (inst.mnemonic == "ret" || inst.mnemonic == "retf") return 8;
    if (inst.mnemonic == "sub")
    {
        size_t p = inst.operands.find("rsp");
        if (p == string::npos) p = inst.operands.find("esp");
        if (p != string::npos && (p == 0 || inst.operands[p-1] == ' ' || inst.operands[p-1] == ','))
        {
            size_t x = inst.operands.rfind("0x");
            if (x != string::npos && x > p)
            {
                try { return -(int64_t)stoull(inst.operands.substr(x), nullptr, 16); }
                catch (...) {}
            }
        }
    }
    if (inst.mnemonic == "add")
    {
        size_t p = inst.operands.find("rsp");
        if (p == string::npos) p = inst.operands.find("esp");
        if (p != string::npos && (p == 0 || inst.operands[p-1] == ' ' || inst.operands[p-1] == ','))
        {
            size_t x = inst.operands.rfind("0x");
            if (x != string::npos && x > p)
            {
                try { return (int64_t)stoull(inst.operands.substr(x), nullptr, 16); }
                catch (...) {}
            }
        }
    }
    return 0;
}

static void computeStackDeltas(CFG& cfg)
{
    for (auto& func : cfg.functions)
    {
        for (auto& block : func.blocks)
        {
            int64_t delta = 0;
            for (auto& inst : block.instructions)
            {
                inst.stack_offset = delta;
                delta += stackDelta(inst);
            }
        }
    }
}

static void jumpThread(CFG& cfg)
{
    map<addr_t, BasicBlock*> addr_to_block;
    map<addr_t, Function*> addr_to_func;
    for (auto& func : cfg.functions)
    {
        for (auto& block : func.blocks)
        {
            addr_to_block[block.address] = &block;
            addr_to_func[block.address] = &func;
        }
    }

    for (auto& func : cfg.functions)
    {
        bool changed = true;
        while (changed)
        {
            changed = false;
            for (auto& block : func.blocks)
            {
                if (block.successors.size() != 1) continue;
                addr_t t = block.successors[0];
                auto it = addr_to_block.find(t);
                if (it == addr_to_block.end()) continue;
                if (addr_to_func[t] != &func) continue;

                BasicBlock* target = it->second;
                if (!target->instructions.empty() &&
                    target->instructions.back().mnemonic == "jmp" &&
                    target->successors.size() == 1)
                {
                    addr_t redirect = target->successors[0];
                    if (redirect != block.address && redirect != t)
                    {
                        block.successors[0] = redirect;
                        changed = true;
                    }
                }
            }
        }
    }
}

static void pruneDeadBlocks(CFG& cfg)
{
    for (auto& func : cfg.functions)
    {
        if (func.blocks.empty()) continue;
        set<addr_t> reachable;
        queue<addr_t> q;
        reachable.insert(func.blocks[0].address);
        q.push(func.blocks[0].address);

        map<addr_t, BasicBlock*> addr_map;
        for (auto& b : func.blocks) addr_map[b.address] = &b;

        while (!q.empty())
        {
            addr_t cur = q.front(); q.pop();
            auto it = addr_map.find(cur);
            if (it == addr_map.end()) continue;
            for (auto s : it->second->successors)
                if (reachable.insert(s).second) q.push(s);
        }

        vector<BasicBlock> alive;
        for (auto& b : func.blocks)
            if (reachable.count(b.address))
                alive.push_back(move(b));
        func.blocks = move(alive);
    }
}

static void computeXrefs(CFG& cfg)
{
    map<addr_t, vector<pair<addr_t, string>>> xmap;
    for (const auto& func : cfg.functions)
    {
        for (const auto& block : func.blocks)
        {
            for (const auto& inst : block.instructions)
            {
                if (inst.mnemonic == "call" || inst.mnemonic == "jmp" ||
                    (inst.mnemonic.size() == 2 && inst.mnemonic[0] == 'j' && inst.mnemonic != "jn"))
                {
                    size_t pos = inst.operands.find("0x");
                    if (pos != string::npos)
                    {
                        try
                        {
                            addr_t target = stoull(inst.operands.substr(pos), nullptr, 16);
                            string type = inst.mnemonic;
                            xmap[target].push_back({inst.address, type});
                        }
                        catch (...) {}
                    }
                }
            }
        }
    }

    for (auto& kv : xmap)
    {
        XrefEntry xe;
        xe.target = kv.first;
        xe.callers = move(kv.second);
        cfg.xrefs.push_back(move(xe));
    }
}

void CFGBuilder::clean(CFG& cfg)
{
    jumpThread(cfg);
    pruneDeadBlocks(cfg);
    computeStackDeltas(cfg);
    cfg.has_stack_offsets = true;
    computeXrefs(cfg);
}
