bdf0ae866a
Проект начинается с двух вещей: клиент, который мы действительно собрали, и загрузчик, который является его частью, а не чем-то рядом. Форк - prepare-game.ps1 пересобирает клиент целиком: ванильный jar, SRG и MCP, SpecialSource, FernFlower, патчи, автофиксеры, javac. Идемпотентен. - Свой код игры лежит в src-overlay, потому что src пересоздаётся при каждой сборке и всё написанное вручную там погибает. - 116 патчей в patches/decompiler-fixes.txt. Каждый сверялся с оригинальным байткодом через javap: компиляция не ловит ошибки декомпиляции. - struct_diff.py сравнивает исходный байткод с нашей сборкой по классам, интерфейсам, суперклассам, методам и полям, и отдельно проверяет, что пропавшие классы действительно никому не нужны. Загрузчик - Собирается и тестируется без игры: build.ps1 не использует classpath вообще, поэтому SelfTest из 128 проверок проходит без запущенного форка. - Клиентский API не содержит ни одного игрового типа, реализацию даёт игра. - Политика: мод меняет то, что клиент показывает, но никогда то, что истинно в мире. Мир, предметы, игроки и сеть принадлежат серверу. - Capability приведены в соответствие с этой политикой: осталось шесть, и за каждым стоит реальная проверка. Прежние WORLD, PLAYER, NETWORK, TEXT, INPUT и COMMAND выдавались, но ничего не проверяли. - Команды модов имеют область CLIENT, SERVER или BOTH. Серверные не выполняются на клиенте никогда. - Свой jar загрузчика лежит в classpath игры и сборки форка. Инструменты - add_patch.py, чтобы каждое исправление переживало передекомпиляцию - find_self_comparisons.py, find_remove_index_bugs.py, find_callers.py - stage_run.py и fetch_missing.py для запуска клиента
59 lines
2.1 KiB
Python
59 lines
2.1 KiB
Python
"""Find List<Integer>/Set<Integer>.remove(int) misdecompilations.
|
|
|
|
FernFlower sometimes resolves List.remove(Object) to remove(int index) when the
|
|
element type is Integer. The bytecode always boxes with Integer.valueOf first,
|
|
so a source-level remove() on an Integer collection without that box is a bug:
|
|
it deletes by position, and throws IndexOutOfBounds on an empty list.
|
|
|
|
python find_remove_index_bugs.py <src-root>
|
|
"""
|
|
|
|
import io
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
|
|
FIELD = re.compile(r'\b(?:private|public|protected)?\s*(?:final\s+)?'
|
|
r'((?:java\.util\.)?(?:List|Set|Collection|ArrayList|LinkedList|Queue|Deque))\s*'
|
|
r'<\s*(?:java\.lang\.)?Integer\s*>\s+(\w+)\s*[;=]')
|
|
REMOVE = re.compile(r'\b(\w+)\.remove\(([^()]*)\)')
|
|
|
|
|
|
def main():
|
|
root = sys.argv[1]
|
|
hits = []
|
|
for base, _dirs, files in os.walk(root):
|
|
for name in files:
|
|
if not name.endswith('.java'):
|
|
continue
|
|
path = os.path.join(base, name)
|
|
with io.open(path, encoding='utf-8') as handle:
|
|
text = handle.read()
|
|
fields = {}
|
|
for match in FIELD.finditer(text):
|
|
fields[match.group(2)] = match.group(1)
|
|
if not fields:
|
|
continue
|
|
for number, line in enumerate(text.split('\n'), 1):
|
|
for match in REMOVE.finditer(line):
|
|
receiver, argument = match.group(1), match.group(2).strip()
|
|
if receiver not in fields:
|
|
continue
|
|
if 'valueOf' in argument or 'Integer' in argument:
|
|
continue
|
|
hits.append((os.path.relpath(path, root).replace(os.sep, '/'),
|
|
number, receiver, argument, line.strip()))
|
|
|
|
if not hits:
|
|
print('no List<Integer>.remove(int) candidates found')
|
|
return 0
|
|
print('%d candidate(s):' % len(hits))
|
|
for path, number, receiver, argument, line in hits:
|
|
print(' %s:%d %s.remove(%s)' % (path, number, receiver, argument))
|
|
print(' %s' % line)
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
raise SystemExit(main()) |