mirror of
https://github.com/isledecomp/isle.git
synced 2025-10-23 00:14:22 +00:00
Decomp parser: indirect globals and string markers (#446)
* Enable string annotations and indirect globals * Adding some STRING annotations * Library functions
This commit is contained in:
@@ -14,6 +14,7 @@ string_demangle_cases = [
|
||||
14,
|
||||
True,
|
||||
),
|
||||
("??_C@_00A@?$AA@", 0, False),
|
||||
]
|
||||
|
||||
|
||||
|
@@ -112,3 +112,33 @@ def test_duplicate_offsets(linter):
|
||||
# Full reset will forget seen offsets.
|
||||
linter.reset(True)
|
||||
assert linter.check_lines(lines, "test.h", "TEST") is True
|
||||
|
||||
|
||||
def test_duplicate_strings(linter):
|
||||
"""Duplicate string markers are okay if the string value is the same."""
|
||||
string_lines = [
|
||||
"// STRING: TEST 0x1000",
|
||||
'return "hello world";',
|
||||
]
|
||||
|
||||
# No problem to use this marker twice.
|
||||
assert linter.check_lines(string_lines, "test.h", "TEST") is True
|
||||
assert linter.check_lines(string_lines, "test.h", "TEST") is True
|
||||
|
||||
different_string = [
|
||||
"// STRING: TEST 0x1000",
|
||||
'return "hi there";',
|
||||
]
|
||||
|
||||
# Same address but the string is different
|
||||
assert linter.check_lines(different_string, "greeting.h", "TEST") is False
|
||||
assert len(linter.alerts) == 1
|
||||
assert linter.alerts[0].code == ParserError.WRONG_STRING
|
||||
|
||||
same_addr_reused = [
|
||||
"// GLOBAL:TEXT 0x1000",
|
||||
"int g_test = 123;",
|
||||
]
|
||||
|
||||
# This will fail like any other offset reuse.
|
||||
assert linter.check_lines(same_addr_reused, "other.h", "TEST") is False
|
||||
|
@@ -442,3 +442,82 @@ def test_static_variable(parser):
|
||||
)
|
||||
assert len(parser.variables) == 2
|
||||
assert parser.variables[1].is_static is True
|
||||
|
||||
|
||||
def test_reject_global_return(parser):
|
||||
"""Previously we had annotated strings with the GLOBAL marker.
|
||||
For example: if a function returned a string. We now want these to be
|
||||
annotated with the STRING marker."""
|
||||
|
||||
parser.read_lines(
|
||||
[
|
||||
"// FUNCTION: TEST 0x5555",
|
||||
"void test_function() {",
|
||||
" // GLOBAL: TEST 0x8888",
|
||||
' return "test";',
|
||||
"}",
|
||||
]
|
||||
)
|
||||
assert len(parser.variables) == 0
|
||||
assert len(parser.alerts) == 1
|
||||
assert parser.alerts[0].code == ParserError.GLOBAL_NOT_VARIABLE
|
||||
|
||||
|
||||
def test_global_string(parser):
|
||||
"""We now allow GLOBAL and STRING markers for the same item."""
|
||||
|
||||
parser.read_lines(
|
||||
[
|
||||
"// GLOBAL: TEST 0x1234",
|
||||
"// STRING: TEXT 0x5555",
|
||||
'char* g_test = "hello";',
|
||||
]
|
||||
)
|
||||
assert len(parser.variables) == 1
|
||||
assert len(parser.strings) == 1
|
||||
assert len(parser.alerts) == 0
|
||||
|
||||
assert parser.variables[0].name == "g_test"
|
||||
assert parser.strings[0].name == "hello"
|
||||
|
||||
|
||||
def test_comment_variables(parser):
|
||||
"""Match on hidden variables from libraries."""
|
||||
|
||||
parser.read_lines(
|
||||
[
|
||||
"// GLOBAL: TEST 0x1234",
|
||||
"// g_test",
|
||||
]
|
||||
)
|
||||
assert len(parser.variables) == 1
|
||||
assert parser.variables[0].name == "g_test"
|
||||
|
||||
|
||||
def test_flexible_variable_prefix(parser):
|
||||
"""Don't alert to library variables that lack the g_ prefix.
|
||||
This is out of our control."""
|
||||
|
||||
parser.read_lines(
|
||||
[
|
||||
"// GLOBAL: TEST 0x1234",
|
||||
"// some_other_variable",
|
||||
]
|
||||
)
|
||||
assert len(parser.variables) == 1
|
||||
assert len(parser.alerts) == 0
|
||||
assert parser.variables[0].name == "some_other_variable"
|
||||
|
||||
|
||||
def test_string_ignore_g_prefix(parser):
|
||||
"""String annotations above a regular variable should not alert to
|
||||
the missing g_ prefix. This is only required for GLOBAL markers."""
|
||||
|
||||
parser.read_lines(
|
||||
[
|
||||
"// STRING: TEST 0x1234",
|
||||
'const char* value = "";',
|
||||
]
|
||||
)
|
||||
assert len(parser.strings) == 1
|
||||
assert len(parser.alerts) == 0
|
||||
|
@@ -15,7 +15,7 @@ state_change_marker_cases = [
|
||||
(_rs.SEARCH, "TEMPLATE", _rs.IN_TEMPLATE, None),
|
||||
(_rs.SEARCH, "VTABLE", _rs.IN_VTABLE, None),
|
||||
(_rs.SEARCH, "LIBRARY", _rs.IN_LIBRARY, None),
|
||||
(_rs.SEARCH, "STRING", _rs.SEARCH, None),
|
||||
(_rs.SEARCH, "STRING", _rs.IN_GLOBAL, None),
|
||||
|
||||
(_rs.WANT_SIG, "FUNCTION", _rs.WANT_SIG, None),
|
||||
(_rs.WANT_SIG, "GLOBAL", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER),
|
||||
@@ -33,7 +33,7 @@ state_change_marker_cases = [
|
||||
(_rs.IN_FUNC, "TEMPLATE", _rs.IN_TEMPLATE, _pe.MISSED_END_OF_FUNCTION),
|
||||
(_rs.IN_FUNC, "VTABLE", _rs.IN_VTABLE, _pe.MISSED_END_OF_FUNCTION),
|
||||
(_rs.IN_FUNC, "LIBRARY", _rs.IN_LIBRARY, _pe.MISSED_END_OF_FUNCTION),
|
||||
(_rs.IN_FUNC, "STRING", _rs.IN_FUNC, None),
|
||||
(_rs.IN_FUNC, "STRING", _rs.IN_FUNC_GLOBAL, None),
|
||||
|
||||
(_rs.IN_TEMPLATE, "FUNCTION", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER),
|
||||
(_rs.IN_TEMPLATE, "GLOBAL", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER),
|
||||
@@ -60,7 +60,7 @@ state_change_marker_cases = [
|
||||
(_rs.IN_GLOBAL, "TEMPLATE", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER),
|
||||
(_rs.IN_GLOBAL, "VTABLE", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER),
|
||||
(_rs.IN_GLOBAL, "LIBRARY", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER),
|
||||
(_rs.IN_GLOBAL, "STRING", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER),
|
||||
(_rs.IN_GLOBAL, "STRING", _rs.IN_GLOBAL, None),
|
||||
|
||||
(_rs.IN_FUNC_GLOBAL, "FUNCTION", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER),
|
||||
(_rs.IN_FUNC_GLOBAL, "GLOBAL", _rs.IN_FUNC_GLOBAL, None),
|
||||
@@ -69,7 +69,7 @@ state_change_marker_cases = [
|
||||
(_rs.IN_FUNC_GLOBAL, "TEMPLATE", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER),
|
||||
(_rs.IN_FUNC_GLOBAL, "VTABLE", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER),
|
||||
(_rs.IN_FUNC_GLOBAL, "LIBRARY", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER),
|
||||
(_rs.IN_FUNC_GLOBAL, "STRING", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER),
|
||||
(_rs.IN_FUNC_GLOBAL, "STRING", _rs.IN_FUNC_GLOBAL, None),
|
||||
|
||||
(_rs.IN_VTABLE, "FUNCTION", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER),
|
||||
(_rs.IN_VTABLE, "GLOBAL", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER),
|
||||
|
@@ -10,6 +10,7 @@ from isledecomp.parser.util import (
|
||||
is_blank_or_comment,
|
||||
get_class_name,
|
||||
get_variable_name,
|
||||
get_string_contents,
|
||||
)
|
||||
|
||||
|
||||
@@ -158,3 +159,18 @@ variable_name_cases = [
|
||||
@pytest.mark.parametrize("line,name", variable_name_cases)
|
||||
def test_get_variable_name(line: str, name: str):
|
||||
assert get_variable_name(line) == name
|
||||
|
||||
|
||||
string_match_cases = [
|
||||
('return "hello world";', "hello world"),
|
||||
('"hello\\\\"', "hello\\"),
|
||||
('"hello \\"world\\""', 'hello "world"'),
|
||||
('"hello\\nworld"', "hello\nworld"),
|
||||
# Only match first string if there are multiple options
|
||||
('Method("hello", "world");', "hello"),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("line, string", string_match_cases)
|
||||
def test_get_string_contents(line: str, string: str):
|
||||
assert get_string_contents(line) == string
|
||||
|
Reference in New Issue
Block a user