#include "addressparser.h" #include using rcx::AddressParser; using rcx::AddressParserCallbacks; using rcx::AddressParseResult; class TestAddressParser : public QObject { Q_OBJECT private slots: // -- Hex literals -- void bareHex() { auto r = AddressParser::evaluate("AB"); QVERIFY(r.ok); QCOMPARE(r.value, 0xABULL); } void prefixedHex() { auto r = AddressParser::evaluate("0x1F4"); QVERIFY(r.ok); QCOMPARE(r.value, 0x1F4ULL); } void zeroLiteral() { auto r = AddressParser::evaluate("0"); QVERIFY(r.ok); QCOMPARE(r.value, 0ULL); } void large64bit() { auto r = AddressParser::evaluate("7FF66CCE0000");QVERIFY(r.ok); QCOMPARE(r.value, 0x7FF66CCE0000ULL); } // -- Arithmetic -- void addition() { auto r = AddressParser::evaluate("0x100 + 0x200"); QVERIFY(r.ok); QCOMPARE(r.value, 0x300ULL); } void subtraction() { auto r = AddressParser::evaluate("0x300 - 0x100"); QVERIFY(r.ok); QCOMPARE(r.value, 0x200ULL); } void multiplication() { auto r = AddressParser::evaluate("0x10 * 4"); QVERIFY(r.ok); QCOMPARE(r.value, 0x40ULL); } void division() { auto r = AddressParser::evaluate("0x100 / 2"); QVERIFY(r.ok); QCOMPARE(r.value, 0x80ULL); } void precedence() { // 0x10 + 2*3 = 0x10 + 6 = 0x16 auto r = AddressParser::evaluate("0x10 + 2 * 3"); QVERIFY(r.ok); QCOMPARE(r.value, 0x16ULL); } void parentheses() { // (0x10 + 2) * 3 = 0x12 * 3 = 0x36 auto r = AddressParser::evaluate("(0x10 + 2) * 3"); QVERIFY(r.ok); QCOMPARE(r.value, 0x36ULL); } // -- Unary minus -- void unaryMinus() { auto r = AddressParser::evaluate("-0x10 + 0x20"); QVERIFY(r.ok); QCOMPARE(r.value, 0x10ULL); } // -- Module resolution -- void moduleResolve() { AddressParserCallbacks cbs; cbs.resolveModule = [](const QString& name, bool* ok) -> uint64_t { *ok = (name == "Program.exe"); return *ok ? 0x140000000ULL : 0; }; auto r = AddressParser::evaluate(" + 0x123", 8, &cbs); QVERIFY(r.ok); QCOMPARE(r.value, 0x140000123ULL); } void moduleNotFound() { AddressParserCallbacks cbs; cbs.resolveModule = [](const QString&, bool* ok) -> uint64_t { *ok = false; return 0; }; auto r = AddressParser::evaluate("", 8, &cbs); QVERIFY(!r.ok); QVERIFY(r.error.contains("not found")); } // -- Dereference -- void derefSimple() { AddressParserCallbacks cbs; cbs.readPointer = [](uint64_t addr, bool* ok) -> uint64_t { *ok = (addr == 0x1000); return *ok ? 0xDEADBEEFULL : 0; }; auto r = AddressParser::evaluate("[0x1000]", 8, &cbs); QVERIFY(r.ok); QCOMPARE(r.value, 0xDEADBEEFULL); } void derefNested() { AddressParserCallbacks cbs; cbs.resolveModule = [](const QString& name, bool* ok) -> uint64_t { *ok = (name == "mod"); return *ok ? 0x400000ULL : 0; }; cbs.readPointer = [](uint64_t addr, bool* ok) -> uint64_t { *ok = true; if (addr == 0x400100) return 0x500000; if (addr == 0x900000) return 0xABCDEF; return 0; }; // [ + [ + 0x100]] = [0x400000 + [0x400000+0x100]] // inner deref: [0x400100] = 0x500000 // outer: [0x400000 + 0x500000] = [0x900000] = 0xABCDEF auto r = AddressParser::evaluate("[ + [ + 0x100]]", 8, &cbs); QVERIFY(r.ok); QCOMPARE(r.value, 0xABCDEFULL); } void derefReadFailure() { AddressParserCallbacks cbs; cbs.readPointer = [](uint64_t, bool* ok) -> uint64_t { *ok = false; return 0; }; auto r = AddressParser::evaluate("[0x1000]", 8, &cbs); QVERIFY(!r.ok); QVERIFY(r.error.contains("failed to read")); } // -- Complex expression from plan -- void complexExpr() { AddressParserCallbacks cbs; cbs.resolveModule = [](const QString& name, bool* ok) -> uint64_t { *ok = (name == "Program.exe"); return *ok ? 0x140000000ULL : 0; }; cbs.readPointer = [](uint64_t addr, bool* ok) -> uint64_t { *ok = true; if (addr == 0x1400000DEULL) return 0x500000; return 0; }; // [ + 0xDE] - AB = [0x1400000DE] - 0xAB = 0x500000 - 0xAB = 0x4FFF55 auto r = AddressParser::evaluate("[ + 0xDE] - AB", 8, &cbs); QVERIFY(r.ok); QCOMPARE(r.value, 0x4FFF55ULL); } // -- Errors -- void emptyInput() { auto r = AddressParser::evaluate(""); QVERIFY(!r.ok); } void unmatchedBracket() { auto r = AddressParser::evaluate("[0x1000"); QVERIFY(!r.ok); QVERIFY(r.error.contains("']'")); } void unmatchedAngle() { auto r = AddressParser::evaluate("'")); } void divisionByZero() { auto r = AddressParser::evaluate("0x100 / 0"); QVERIFY(!r.ok); QVERIFY(r.error.contains("division by zero")); } void trailingGarbage() { auto r = AddressParser::evaluate("0x100 xyz"); QVERIFY(!r.ok); QVERIFY(r.error.contains("unexpected")); } void trailingOperator() { auto r = AddressParser::evaluate("0x100 +"); QVERIFY(!r.ok); } // -- Validation -- void validateValid() { QCOMPARE(AddressParser::validate("0x100 + 0x200"), QString()); QCOMPARE(AddressParser::validate(" + [0x100]"), QString()); } void validateInvalid() { QVERIFY(!AddressParser::validate("").isEmpty()); QVERIFY(!AddressParser::validate("[0x100").isEmpty()); QVERIFY(!AddressParser::validate("0x100 xyz").isEmpty()); } // -- Backtick stripping -- void backtickStripping() { auto r = AddressParser::evaluate("7ff6`6cce0000"); QVERIFY(r.ok); QCOMPARE(r.value, 0x7FF66CCE0000ULL); } // -- Whitespace tolerance -- void whitespace() { auto r = AddressParser::evaluate(" 0x100 + 0x200 "); QVERIFY(r.ok); QCOMPARE(r.value, 0x300ULL); } // -- Legacy compat: simple hex -- void simpleHexAddress() { auto r = AddressParser::evaluate("140000000"); QVERIFY(r.ok); QCOMPARE(r.value, 0x140000000ULL); } // -- Multiple additions -- void multipleAdditions() { auto r = AddressParser::evaluate("0x100 + 0x200 + 0x300"); QVERIFY(r.ok); QCOMPARE(r.value, 0x600ULL); } // -- Identifier resolution -- void identBase() { AddressParserCallbacks cbs; cbs.resolveIdentifier = [](const QString& name, bool* ok) -> uint64_t { *ok = (name == "base"); return *ok ? 0x140000000ULL : 0; }; auto r = AddressParser::evaluate("base", 8, &cbs); QVERIFY(r.ok); QCOMPARE(r.value, 0x140000000ULL); } void identFieldName() { AddressParserCallbacks cbs; cbs.resolveIdentifier = [](const QString& name, bool* ok) -> uint64_t { if (name == "base") { *ok = true; return 0x140000000ULL; } if (name == "e_lfanew") { *ok = true; return 0xE8ULL; } *ok = false; return 0; }; auto r = AddressParser::evaluate("base + e_lfanew", 8, &cbs); QVERIFY(r.ok); QCOMPARE(r.value, 0x1400000E8ULL); } void identUnknown() { AddressParserCallbacks cbs; cbs.resolveIdentifier = [](const QString&, bool* ok) -> uint64_t { *ok = false; return 0; }; auto r = AddressParser::evaluate("unknown_var", 8, &cbs); QVERIFY(!r.ok); QVERIFY(r.error.contains("unknown identifier")); } // -- Hex vs identifier disambiguation -- void hexDisambigDEAD() { // "DEAD" is all hex digits → should parse as hex number 0xDEAD auto r = AddressParser::evaluate("DEAD"); QVERIFY(r.ok); QCOMPARE(r.value, 0xDEADULL); } void hexDisambigBase() { // "base" has 's' (non-hex) → identifier AddressParserCallbacks cbs; cbs.resolveIdentifier = [](const QString& name, bool* ok) -> uint64_t { *ok = (name == "base"); return *ok ? 42ULL : 0; }; auto r = AddressParser::evaluate("base", 8, &cbs); QVERIFY(r.ok); QCOMPARE(r.value, 42ULL); } void hexDisambigABCwithUnderscore() { // "ABC_field" has '_' → identifier, not hex AddressParserCallbacks cbs; cbs.resolveIdentifier = [](const QString& name, bool* ok) -> uint64_t { *ok = (name == "ABC_field"); return *ok ? 99ULL : 0; }; auto r = AddressParser::evaluate("ABC_field", 8, &cbs); QVERIFY(r.ok); QCOMPARE(r.value, 99ULL); } // -- Bitwise operators -- void bitwiseAnd() { auto r = AddressParser::evaluate("0xFF & 0x0F"); QVERIFY(r.ok); QCOMPARE(r.value, 0x0FULL); } void bitwiseOr() { auto r = AddressParser::evaluate("0xA0 | 0x0B"); QVERIFY(r.ok); QCOMPARE(r.value, 0xABULL); } void bitwiseXor() { auto r = AddressParser::evaluate("0xA ^ 0x5"); QVERIFY(r.ok); QCOMPARE(r.value, 0xFULL); } void shiftLeft() { auto r = AddressParser::evaluate("1 << 4"); QVERIFY(r.ok); QCOMPARE(r.value, 0x10ULL); } void shiftRight() { auto r = AddressParser::evaluate("0xFF00 >> 8"); QVERIFY(r.ok); QCOMPARE(r.value, 0xFFULL); } // -- Unary bitwise NOT -- void unaryNot() { auto r = AddressParser::evaluate("~0"); QVERIFY(r.ok); QCOMPARE(r.value, 0xFFFFFFFFFFFFFFFFULL); } void unaryNotMask() { // ~0xFFF = 0xFFFFFFFFFFFFF000 auto r = AddressParser::evaluate("~0xFFF"); QVERIFY(r.ok); QCOMPARE(r.value, 0xFFFFFFFFFFFFF000ULL); } // -- Operator precedence -- void shiftPrecedence() { // C precedence: shift binds looser than addition // 1 + 2 << 3 = (1 + 2) << 3 = 3 << 3 = 24 = 0x18 auto r = AddressParser::evaluate("1 + 2 << 3"); QVERIFY(r.ok); QCOMPARE(r.value, 0x18ULL); } void andOrPrecedence() { // & binds tighter than | // 0xFF | 0x100 & 0xF00 = 0xFF | (0x100 & 0xF00) = 0xFF | 0x100 = 0x1FF auto r = AddressParser::evaluate("0xFF | 0x100 & 0xF00"); QVERIFY(r.ok); QCOMPARE(r.value, 0x1FFULL); } void xorPrecedence() { // ^ between & and |: a | b ^ c & d = a | (b ^ (c & d)) // 0xF0 | 0x0F ^ 0xFF & 0x0F = 0xF0 | (0x0F ^ (0xFF & 0x0F)) // = 0xF0 | (0x0F ^ 0x0F) = 0xF0 | 0x00 = 0xF0 auto r = AddressParser::evaluate("0xF0 | 0x0F ^ 0xFF & 0x0F"); QVERIFY(r.ok); QCOMPARE(r.value, 0xF0ULL); } // -- E_lfanew end-to-end -- void elfanewScenario() { AddressParserCallbacks cbs; cbs.resolveIdentifier = [](const QString& name, bool* ok) -> uint64_t { if (name == "base") { *ok = true; return 0x140000000ULL; } if (name == "e_lfanew") { *ok = true; return 0xE8ULL; } *ok = false; return 0; }; // base + e_lfanew = 0x140000000 + 0xE8 = 0x1400000E8 auto r = AddressParser::evaluate("base + e_lfanew", 8, &cbs); QVERIFY(r.ok); QCOMPARE(r.value, 0x1400000E8ULL); } void pageAlignedExpr() { AddressParserCallbacks cbs; cbs.resolveIdentifier = [](const QString& name, bool* ok) -> uint64_t { if (name == "base") { *ok = true; return 0x140000000ULL; } if (name == "e_lfanew") { *ok = true; return 0xE8ULL; } *ok = false; return 0; }; // (base + e_lfanew) & ~0xFFF = 0x1400000E8 & ~0xFFF = 0x140000000 auto r = AddressParser::evaluate("(base + e_lfanew) & ~0xFFF", 8, &cbs); QVERIFY(r.ok); QCOMPARE(r.value, 0x140000000ULL); } // -- Bare module.dll identifier -- void bareModuleDll() { AddressParserCallbacks cbs; cbs.resolveIdentifier = [](const QString& name, bool* ok) -> uint64_t { *ok = (name == "client.dll"); return *ok ? 0x7FF600000000ULL : 0; }; auto r = AddressParser::evaluate("client.dll + 0xFF", 8, &cbs); QVERIFY(r.ok); QCOMPARE(r.value, 0x7FF6000000FFULL); } void bareModuleExe() { AddressParserCallbacks cbs; cbs.resolveIdentifier = [](const QString& name, bool* ok) -> uint64_t { *ok = (name == "cs2.exe"); return *ok ? 0x140000000ULL : 0; }; auto r = AddressParser::evaluate("cs2.exe + 0xDE", 8, &cbs); QVERIFY(r.ok); QCOMPARE(r.value, 0x1400000DEULL); } // -- Validate with new syntax -- void validateIdentifier() { QCOMPARE(AddressParser::validate("base + e_lfanew"), QString()); } void validateBitwiseOps() { QCOMPARE(AddressParser::validate("0xFF & 0x0F"), QString()); QCOMPARE(AddressParser::validate("1 << 4"), QString()); QCOMPARE(AddressParser::validate("~0xFFF"), QString()); } }; QTEST_GUILESS_MAIN(TestAddressParser) #include "test_addressparser.moc"