Decomp linter warning for offset reuse (#342)

* Linter warning for offset reuse in codebase

* Fix repeated offset problems
This commit is contained in:
MS 2023-12-17 17:45:33 -05:00 committed by GitHub
parent 59ca9b6155
commit 994d17a85e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 68 additions and 17 deletions

View file

@ -176,7 +176,7 @@ void Isle::VTable0x68(MxBool p_add)
// TODO // TODO
} }
// STUB: LEGO1 0x10031820 // STUB: LEGO1 0x100327a0
MxLong Isle::HandleTransitionEnd() MxLong Isle::HandleTransitionEnd()
{ {
return 0; return 0;

View file

@ -43,7 +43,6 @@ ColorStringStruct g_colorSaveData[43] = {
// NOTE: This offset = the end of the variables table, the last entry // NOTE: This offset = the end of the variables table, the last entry
// in that table is a special entry, the string "END_OF_VARIABLES" // in that table is a special entry, the string "END_OF_VARIABLES"
// GLOBAL: LEGO1 0x100f3e50
extern const char* g_endOfVariables; extern const char* g_endOfVariables;
// FUNCTION: LEGO1 0x10039550 // FUNCTION: LEGO1 0x10039550

View file

@ -112,8 +112,8 @@ const char* g_current = "current";
// GLOBAL: LEGO1 0x101020e8 // GLOBAL: LEGO1 0x101020e8
void (*g_omniUserMessage)(const char*, int); void (*g_omniUserMessage)(const char*, int);
// GLOBAL: LEGO1 0x100f4c54 // GLOBAL: LEGO1 0x100f4c58
MxBool g_isWorldActive; MxBool g_isWorldActive = TRUE;
// FUNCTION: LEGO1 0x10015700 // FUNCTION: LEGO1 0x10015700
LegoOmni* Lego() LegoOmni* Lego()

View file

@ -122,7 +122,7 @@ void LegoWorld::FUN_10073400()
{ {
} }
// STUB: LEGO1 0x10073400 // STUB: LEGO1 0x10073430
void LegoWorld::FUN_10073430() void LegoWorld::FUN_10073430()
{ {
} }

View file

@ -13,7 +13,7 @@ class Motorcycle : public IslePathActor {
// FUNCTION: LEGO1 0x10035840 // FUNCTION: LEGO1 0x10035840
inline virtual const char* ClassName() const override // vtable+0x0c inline virtual const char* ClassName() const override // vtable+0x0c
{ {
// GLOBAL: LEGO1 0x10035840 // GLOBAL: LEGO1 0x100f38e8
return "Motorcycle"; return "Motorcycle";
} }

View file

@ -27,7 +27,7 @@ class MxDSBuffer : public MxCore {
// FUNCTION: LEGO1 0x100c6500 // FUNCTION: LEGO1 0x100c6500
inline virtual const char* ClassName() const override // vtable+0x0c inline virtual const char* ClassName() const override // vtable+0x0c
{ {
// GLOBAL: LEGO1 0x100f0568 // GLOBAL: LEGO1 0x101025b8
return "MxDSBuffer"; return "MxDSBuffer";
} }

View file

@ -49,7 +49,4 @@ class MxDSSelectAction : public MxDSParallelAction {
// FUNCTION: LEGO1 0x100cbd00 // FUNCTION: LEGO1 0x100cbd00
// MxStringListCursor::~MxStringListCursor // MxStringListCursor::~MxStringListCursor
// TEMPLATE: LEGO1 0x100cc450
// MxListEntry<MxString>::GetValue
#endif // MXDSSELECTACTION_H #endif // MXDSSELECTACTION_H

View file

@ -14,7 +14,7 @@ class MxRAMStreamController : public MxStreamController {
// FUNCTION: LEGO1 0x100b9430 // FUNCTION: LEGO1 0x100b9430
inline virtual const char* ClassName() const override // vtable+0xc inline virtual const char* ClassName() const override // vtable+0xc
{ {
// GLOBAL: LEGO1 0x10102130 // GLOBAL: LEGO1 0x10102118
return "MxRAMStreamController"; return "MxRAMStreamController";
} }

View file

@ -18,7 +18,7 @@ class Score : public LegoWorld {
// FUNCTION: LEGO1 0x100010c0 // FUNCTION: LEGO1 0x100010c0
inline virtual const char* ClassName() const override // vtable+0x0c inline virtual const char* ClassName() const override // vtable+0x0c
{ {
// GLOBAL: LEGO1 0x100f0058 // GLOBAL: LEGO1 0x100f0050
return "Score"; return "Score";
} }

View file

@ -1,6 +1,7 @@
from typing import List, Optional from typing import List, Optional
from .parser import DecompParser from .parser import DecompParser
from .error import ParserAlert, ParserError from .error import ParserAlert, ParserError
from .node import ParserSymbol
def get_checkorder_filter(module): def get_checkorder_filter(module):
@ -14,16 +15,39 @@ def __init__(self) -> None:
self._parser = DecompParser() self._parser = DecompParser()
self._filename: str = "" self._filename: str = ""
self._module: Optional[str] = None self._module: Optional[str] = None
# Set of (str, int) tuples for each module/offset pair seen while scanning.
# This is _not_ reset between files and is intended to report offset reuse
# when scanning the entire directory.
self._offsets_used = set()
def reset(self): def reset(self, full_reset: bool = False):
self.alerts = [] self.alerts = []
self._parser.reset() self._parser.reset()
self._filename = "" self._filename = ""
self._module = None self._module = None
if full_reset:
self._offsets_used.clear()
def file_is_header(self): def file_is_header(self):
return self._filename.lower().endswith(".h") return self._filename.lower().endswith(".h")
def _load_offsets_from_list(self, marker_list: List[ParserSymbol]):
"""Helper for loading (module, offset) tuples while the DecompParser
has them broken up into three different lists."""
for marker in marker_list:
value = (marker.module, marker.offset)
if value in self._offsets_used:
self.alerts.append(
ParserAlert(
code=ParserError.DUPLICATE_OFFSET,
line_number=marker.line_number,
line=f"0x{marker.offset:08x}",
)
)
else:
self._offsets_used.add(value)
def _check_function_order(self): def _check_function_order(self):
"""Rules: """Rules:
1. Only markers that are implemented in the file are considered. This means we 1. Only markers that are implemented in the file are considered. This means we
@ -55,8 +79,9 @@ def _check_function_order(self):
last_offset = fun.offset last_offset = fun.offset
def _check_offset_uniqueness(self): def _check_offset_uniqueness(self):
# TODO self._load_offsets_from_list(self._parser.functions)
pass self._load_offsets_from_list(self._parser.vtables)
self._load_offsets_from_list(self._parser.variables)
def _check_byname_allowed(self): def _check_byname_allowed(self):
if self.file_is_header(): if self.file_is_header():
@ -75,7 +100,7 @@ def check_lines(self, lines, filename, module=None):
"""`lines` is a generic iterable to allow for testing with a list of strings. """`lines` is a generic iterable to allow for testing with a list of strings.
We assume lines has the entire contents of the compilation unit.""" We assume lines has the entire contents of the compilation unit."""
self.reset() self.reset(False)
self._filename = filename self._filename = filename
self._module = module self._module = module
@ -84,9 +109,10 @@ def check_lines(self, lines, filename, module=None):
self._parser.finish() self._parser.finish()
self.alerts = self._parser.alerts[::] self.alerts = self._parser.alerts[::]
self._check_offset_uniqueness()
if self._module is not None: if self._module is not None:
self._check_byname_allowed() self._check_byname_allowed()
self._check_offset_uniqueness()
if not self.file_is_header(): if not self.file_is_header():
self._check_function_order() self._check_function_order()

View file

@ -70,6 +70,7 @@ def test_module_isolation(linter):
] ]
assert linter.check_lines(lines, "test.cpp", "TEST") is True assert linter.check_lines(lines, "test.cpp", "TEST") is True
linter.reset(True)
assert linter.check_lines(lines, "test.cpp", "ALPHA") is True assert linter.check_lines(lines, "test.cpp", "ALPHA") is True
@ -81,5 +82,33 @@ def test_byname_headers_only(linter):
] ]
assert linter.check_lines(lines, "test.h", "TEST") is True assert linter.check_lines(lines, "test.h", "TEST") is True
linter.reset(True)
assert linter.check_lines(lines, "test.cpp", "TEST") is False assert linter.check_lines(lines, "test.cpp", "TEST") is False
assert linter.alerts[0].code == ParserError.BYNAME_FUNCTION_IN_CPP assert linter.alerts[0].code == ParserError.BYNAME_FUNCTION_IN_CPP
def test_duplicate_offsets(linter):
"""The linter will retain module/offset pairs found until we do a full reset."""
lines = [
"// FUNCTION: TEST 0x1000",
"// FUNCTION: HELLO 0x1000",
"// MyClass::~MyClass",
]
# Should not fail for duplicate offset 0x1000 because the modules are unique.
assert linter.check_lines(lines, "test.h", "TEST") is True
# Simulate a failure by reading the same file twice.
assert linter.check_lines(lines, "test.h", "TEST") is False
# Two errors because offsets from both modules are duplicated
assert len(linter.alerts) == 2
assert all(a.code == ParserError.DUPLICATE_OFFSET for a in linter.alerts)
# Partial reset will retain the list of seen offsets.
linter.reset(False)
assert linter.check_lines(lines, "test.h", "TEST") is False
# Full reset will forget seen offsets.
linter.reset(True)
assert linter.check_lines(lines, "test.h", "TEST") is True