Files
RustNotMC/rsx-client/tools/find_self_comparisons.py
sasheg dev 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 для запуска клиента
2026-10-18 21:31:27 +03:00

47 lines
1.5 KiB
Python

"""Find comparisons of a value against itself.
FernFlower sometimes merges two distinct locals into one name, producing
expressions like d1 != d1 that are always false. The compiler accepts them, so
they survive a clean build and silently disable the code around them.
python find_self_comparisons.py <src-root>
"""
import io
import os
import re
import sys
# a.b() != a.b() and simple locals: word != word, word == word
COMPARISON = re.compile(r'(?<![\w.])([A-Za-z_]\w*(?:\(\))?)\s*(!=|==)\s*\1(?![\w(])')
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:
for number, line in enumerate(handle, 1):
stripped = line.strip()
if stripped.startswith('*') or stripped.startswith('//'):
continue
for match in COMPARISON.finditer(line):
hits.append((os.path.relpath(path, root).replace(os.sep, '/'),
number, match.group(0), stripped))
if not hits:
print('no self-comparisons found')
return 0
print('%d self-comparison(s):' % len(hits))
for path, number, found, line in hits:
print(' %s:%d %s' % (path, number, found))
print(' %s' % line)
return 0
if __name__ == '__main__':
raise SystemExit(main())