baseline: post wave1+i2b+b2i, vfbuild13=377 errors
This commit is contained in:
commit
e11e749e4b
2248 changed files with 378506 additions and 0 deletions
BIN
tools/__pycache__/assemble.cpython-314.pyc
Normal file
BIN
tools/__pycache__/assemble.cpython-314.pyc
Normal file
Binary file not shown.
BIN
tools/__pycache__/fixwaves.cpython-314.pyc
Normal file
BIN
tools/__pycache__/fixwaves.cpython-314.pyc
Normal file
Binary file not shown.
284
tools/assemble.py
Normal file
284
tools/assemble.py
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
"""Сборка src/main/java из vf-plain + jadx-дополнений + нобэкенд-патча."""
|
||||
import os, re, shutil, zipfile, sys
|
||||
|
||||
EVO = '/storage/project/jvm/LoVisual/ref/evo'
|
||||
VF = f'{EVO}/vf-plain'
|
||||
JADX = f'{EVO}/mod/jar-src-plain/sources/defpackage'
|
||||
DEST = '/storage/aboba/evovis-client'
|
||||
JAVA = f'{DEST}/src/main/java'
|
||||
CONSTS = 'GENERAL|MODULE|RENDER|SHADER|GUI|CONFIG|STORAGE|COSMETIC|MEDIA|NETWORK|AUTH|SOCIAL|UPDATE|COMPAT|SECURITY'
|
||||
RULES = [
|
||||
(re.compile(r'\bif\.(' + CONSTS + r')\b'), r'Cif.\1'),
|
||||
(re.compile(r'(?<![\w.$])if\.(?=[a-z])'), 'Cif.'),
|
||||
(re.compile(r'(?<![\w.$])do\.(?=[A-Za-z_$])'), 'Cdo.'),
|
||||
(re.compile(r'\(if\)'), '(Cif)'),
|
||||
(re.compile(r'\binstanceof if\b'), 'instanceof Cif'),
|
||||
(re.compile(r'\bif\.class\b'), 'Cif.class'),
|
||||
(re.compile(r'\bif\s*\[\]'), 'Cif[]'),
|
||||
(re.compile(r'(?<![\w.$])if(?=\s+[A-Za-z_$][\w$]*\s*[=,);:])'), 'Cif'),
|
||||
(re.compile(r'(?<=[<,\s])if(?=\s*>)'), 'Cif'),
|
||||
]
|
||||
def token_fix(text):
|
||||
for rx, rep in RULES:
|
||||
text = rx.sub(rep, text)
|
||||
return text
|
||||
|
||||
def fix_strings(text):
|
||||
out = []; in_s = False; in_c = False; esc = False; n = 0
|
||||
for ch in text:
|
||||
if in_s:
|
||||
if esc: out.append(ch); esc = False; continue
|
||||
if ch == '\\': out.append(ch); esc = True; continue
|
||||
if ch == '"': out.append(ch); in_s = False; continue
|
||||
if ch == '\n': out.append('\\n'); n += 1; continue
|
||||
if ch == '\r': out.append('\\r'); n += 1; continue
|
||||
if ord(ch) < 0x20 and ch != '\t': out.append('\\u%04x' % ord(ch)); n += 1; continue
|
||||
out.append(ch); continue
|
||||
if in_c:
|
||||
if esc: out.append(ch); esc = False; continue
|
||||
if ch == '\\': out.append(ch); esc = True; continue
|
||||
if ch == "'": out.append(ch); in_c = False; continue
|
||||
out.append(ch); continue
|
||||
if ch == '"': in_s = True; out.append(ch); continue
|
||||
if ch == "'": in_c = True; out.append(ch); continue
|
||||
out.append(ch)
|
||||
return ''.join(out), n
|
||||
|
||||
TOKEN = re.compile(r'\d+L?|>>>|<<|>>|[+\-*/%&|^~()]')
|
||||
def toks(s):
|
||||
out = []; i = 0
|
||||
while i < len(s):
|
||||
if s[i].isspace(): i += 1; continue
|
||||
m = TOKEN.match(s, i)
|
||||
if not m: return None
|
||||
out.append(m.group()); i = m.end()
|
||||
return out
|
||||
|
||||
def wrap(v, long_):
|
||||
bits = 64 if long_ else 32
|
||||
v &= (1 << bits) - 1
|
||||
if v >= (1 << (bits - 1)): v -= (1 << bits)
|
||||
return v
|
||||
|
||||
def evaluate(ts):
|
||||
prec = {'|':1,'^':2,'&':3,'<<':4,'>>':4,'>>>':4,'+':5,'-':5,'*':6,'/':6,'%':6}
|
||||
right = {'<<','>>','>>>','-','/','%'}
|
||||
is_op = lambda x: x in prec
|
||||
out = []; ops = []; prev = None
|
||||
for t in ts:
|
||||
if t == '(':
|
||||
ops.append(t); prev = '('; continue
|
||||
if t == ')':
|
||||
while ops and ops[-1] != '(': out.append(ops.pop())
|
||||
if not ops: return None
|
||||
ops.pop()
|
||||
prev = ')'; continue
|
||||
if t == '~':
|
||||
ops.append('u~'); prev = t; continue
|
||||
if is_op(t):
|
||||
if t in '+-' and prev in (None, '(', '+', '-', '*', '/', '%', '&', '|', '^', '<<', '>>', '>>>'):
|
||||
if t == '-': ops.append('u-')
|
||||
prev = t; continue
|
||||
while ops and ops[-1] != '(':
|
||||
top = ops[-1]
|
||||
pt = 7 if top in ('u-', 'u~') else prec[top]
|
||||
if pt > prec[t] or (pt == prec[t] and t not in right):
|
||||
out.append(ops.pop())
|
||||
else: break
|
||||
ops.append(t); prev = t; continue
|
||||
out.append(('n', t)); prev = 'n'
|
||||
while ops:
|
||||
o = ops.pop()
|
||||
if o == '(': return None
|
||||
out.append(o)
|
||||
st = []
|
||||
long_ = any(isinstance(x, tuple) and x[1].endswith('L') for x in out)
|
||||
for x in out:
|
||||
if isinstance(x, tuple):
|
||||
st.append(int(x[1][:-1] if x[1].endswith('L') else x[1])); continue
|
||||
if x == 'u-':
|
||||
if not st: return None
|
||||
st.append(wrap(-st.pop(), long_)); continue
|
||||
if x == 'u~':
|
||||
if not st: return None
|
||||
st.append(wrap(~st.pop(), long_)); continue
|
||||
if len(st) < 2: return None
|
||||
b = st.pop(); a = st.pop()
|
||||
try:
|
||||
if x == '+': r = a + b
|
||||
elif x == '-': r = a - b
|
||||
elif x == '*': r = a * b
|
||||
elif x == '/':
|
||||
if b == 0: return None
|
||||
r = int(a / b)
|
||||
elif x == '%':
|
||||
if b == 0: return None
|
||||
r = a - int(a / b) * b
|
||||
elif x == '&': r = a & b
|
||||
elif x == '|': r = a | b
|
||||
elif x == '^': r = a ^ b
|
||||
elif x == '<<': r = a << (b & 63)
|
||||
elif x == '>>': r = a >> (b & 63)
|
||||
elif x == '>>>':
|
||||
bits = 64 if long_ else 32
|
||||
r = (a & ((1 << bits) - 1)) >> (b & 63)
|
||||
else: return None
|
||||
except Exception:
|
||||
return None
|
||||
st.append(wrap(r, long_))
|
||||
if len(st) != 1: return None
|
||||
return st[0]
|
||||
|
||||
CODE = re.compile(r'"(?:\\.|[^"\\])*"|\'(?:\\.|[^\'\\])\'|//[^\n]*|/\*.*?\*/', re.S)
|
||||
NUMRUN = re.compile(r'(?<![\w.])(?:[0-9(][0-9+\-*/%&|^~()<>\s]*)(?![\w.])')
|
||||
|
||||
def flatten_file(text):
|
||||
parts = []; last = 0
|
||||
for m in CODE.finditer(text):
|
||||
parts.append(('code', text[last:m.start()])); parts.append(('skip', m.group())); last = m.end()
|
||||
parts.append(('code', text[last:]))
|
||||
total = 0; new_parts = []
|
||||
for kind, seg in parts:
|
||||
if kind == 'skip':
|
||||
new_parts.append(seg); continue
|
||||
def repl(m):
|
||||
nonlocal total
|
||||
s = m.group()
|
||||
if not re.search(r'[+\-*/%&|^~]', s): return s
|
||||
if re.search(r'[^0-9+\-*/%&|^~()<>\s]', s): return s
|
||||
core = s; k = 0
|
||||
while core.startswith('(') and core.endswith(')'):
|
||||
inner = core[1:-1]
|
||||
bal = 0; ok = True
|
||||
for ch in inner:
|
||||
if ch == '(': bal += 1
|
||||
elif ch == ')':
|
||||
bal -= 1
|
||||
if bal < 0: ok = False; break
|
||||
if not ok or bal != 0: break
|
||||
core = inner; k += 1
|
||||
ts = toks(core)
|
||||
if not ts: return s
|
||||
if not any(t in ('+','-','*','/','%','&','|','^','<<','>>','>>>') for t in ts): return s
|
||||
if any(t in ('<','>','==','!=') for t in ts): return s
|
||||
v = evaluate(ts)
|
||||
if v is None: return s
|
||||
total += 1
|
||||
return '(' * k + str(v) + ')' * k
|
||||
new_parts.append(NUMRUN.sub(repl, seg))
|
||||
return ''.join(new_parts), total
|
||||
|
||||
TYPE_SW = re.compile(r'SwitchBootstraps\.typeSwitch<\s*"typeSwitch"\s*,([^>]*)>\s*\(\s*([^,]+),\s*([^)]+?)\s*\)', re.S)
|
||||
def fix_typeswitch(t):
|
||||
def rep(m):
|
||||
labels = [x.strip() for x in m.group(1).split(',')]
|
||||
return 'typeSwitch(%s, %s, %s)' % (m.group(2).strip(), m.group(3).strip(),
|
||||
', '.join(l + '.class' for l in labels))
|
||||
t, n = TYPE_SW.subn(rep, t)
|
||||
if n:
|
||||
helper = '''
|
||||
private static int typeSwitch(Object selector, int state, Class<?>... labels) {
|
||||
if (state < 0 || state > labels.length) {
|
||||
throw new IndexOutOfBoundsException("Index " + state + " out of bounds for length " + (labels.length + 1));
|
||||
}
|
||||
for (int i = state; i < labels.length; i++) {
|
||||
if (labels[i].isInstance(selector)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return labels.length;
|
||||
}
|
||||
}
|
||||
'''
|
||||
t = t.rstrip()[:-1].rstrip() + '\n' + helper
|
||||
return t, n
|
||||
|
||||
def fix_this0(text):
|
||||
changed = False
|
||||
if 'this$0' in text:
|
||||
m = re.search(r'\n\s*(?:private|public|protected|final|static|\s)*\s*(?:class|enum|interface)\s+\w+[^{\n]*\{', text)
|
||||
if m and not re.search(r'\b(this\$0|private\s+[\w.\[\]]+\s+this\$0)\s*;', text.replace('this.this$0', '')):
|
||||
am = re.search(r'this\.this\$0 = (\w+);', text)
|
||||
if am:
|
||||
pname = am.group(1); ctor = None
|
||||
for cm in re.finditer(r'(?:private|public|protected)?\s*(\w+)\s*\(([^)]*)\)\s*\{', text):
|
||||
for p in [x.strip() for x in cm.group(2).split(',')]:
|
||||
parts = p.split()
|
||||
if len(parts) >= 2 and parts[-1] == pname:
|
||||
ctor = parts[-2]; break
|
||||
if ctor: break
|
||||
if ctor:
|
||||
text = text[:m.end()] + f'\n private {ctor} this$0;\n' + text[m.end():]
|
||||
changed = True
|
||||
text, n = re.subn(r';\n(\s*)super\(\);\n', ';\n', text)
|
||||
if n: changed = True
|
||||
return text, changed
|
||||
|
||||
def main():
|
||||
z = zipfile.ZipFile(f'{EVO}/mod/evovis-dec.jar')
|
||||
jar = {n[:-6] for n in z.namelist() if n.endswith('.class') and not n.startswith('META-INF/')}
|
||||
vfset = set()
|
||||
for dp, _, fns in os.walk(VF):
|
||||
for fn in fns:
|
||||
if fn.endswith('.java'):
|
||||
vfset.add(os.path.relpath(os.path.join(dp, fn), VF)[:-5].replace(os.sep, '/'))
|
||||
missing = sorted(jar - vfset)
|
||||
if os.path.isdir(JAVA): shutil.rmtree(JAVA)
|
||||
os.makedirs(f'{JAVA}/defpackage', exist_ok=True)
|
||||
st = {'flat': 0, 'this0': 0, 'tw': 0, 'str': 0}
|
||||
for fn in sorted(os.listdir(VF)):
|
||||
if not fn.endswith('.java') or fn == 'if.java': continue
|
||||
t = token_fix(open(os.path.join(VF, fn), encoding='utf-8').read())
|
||||
if not t.lstrip().startswith('package '): t = 'package defpackage;\n\n' + t.lstrip('\n')
|
||||
open(f'{JAVA}/defpackage/{fn}', 'w', encoding='utf-8').write(t)
|
||||
for pkg in ['a', 'b', 'evo']:
|
||||
s = os.path.join(VF, pkg); d = os.path.join(JAVA, pkg)
|
||||
for dp, _, fns in os.walk(s):
|
||||
rel = os.path.relpath(dp, s); t = os.path.join(d, rel) if rel != '.' else d
|
||||
os.makedirs(t, exist_ok=True)
|
||||
for fn in fns:
|
||||
if not fn.endswith('.java'): continue
|
||||
t_ = token_fix(open(os.path.join(dp, fn), encoding='utf-8').read())
|
||||
m = re.search(r'^package [\w.]+;\s*\n', t_)
|
||||
if m and 'import defpackage.' not in t_:
|
||||
t_ = t_[:m.end()] + 'import defpackage.*;\n' + t_[m.end():]
|
||||
elif not m:
|
||||
t_ = 'import defpackage.*;\n' + t_
|
||||
open(os.path.join(t, fn), 'w', encoding='utf-8').write(t_)
|
||||
n_add = 0
|
||||
for name in list(missing) + ['Cif', 'Cdo']:
|
||||
src = os.path.join(JADX, name + '.java')
|
||||
if not os.path.exists(src): continue
|
||||
shutil.copy(src, os.path.join(JAVA, 'defpackage', name + '.java')); n_add += 1
|
||||
for cls in ['cp', 'akj', 'alr']:
|
||||
body = open(f'{EVO}/patch/src/defpackage/{cls}.java', encoding='utf-8').read()
|
||||
if not body.lstrip().startswith('package '): body = 'package defpackage;\n\n' + body.lstrip('\n')
|
||||
open(f'{JAVA}/defpackage/{cls}.java', 'w', encoding='utf-8').write(body)
|
||||
for dp, _, fns in os.walk(JAVA):
|
||||
for fn in fns:
|
||||
if not fn.endswith('.java'): continue
|
||||
p = os.path.join(dp, fn); t = open(p, encoding='utf-8').read()
|
||||
t = re.sub(r'^import lombok\.Generated;\n', '', t, flags=re.M)
|
||||
t = re.sub(r'[ \t]*@Generated\s*\n', '', t)
|
||||
if fn == 'wb.java':
|
||||
t, n = fix_typeswitch(t); st['tw'] += n
|
||||
if fn == 'bz.java':
|
||||
t = t.replace('\n@b\n', '\n')
|
||||
if fn == 'zm.java':
|
||||
t = re.sub(r'\?\? IsEnabled = [^;]+;', 'boolean IsEnabled = false;', t)
|
||||
t = re.sub(r'\?\? flag = [^;]+;', 'boolean flag = false;', t)
|
||||
if 'this$0' in t:
|
||||
t, ch = fix_this0(t)
|
||||
if ch: st['this0'] += 1
|
||||
t, n1 = flatten_file(t); st['flat'] += n1
|
||||
if fn == 'wq.java':
|
||||
t = re.sub(r'(?<![\w.])rx(?![\w])', 'defpackage.rx', t)
|
||||
t, n2 = fix_strings(t); st['str'] += n2
|
||||
if t != open(p, encoding='utf-8').read():
|
||||
open(p, 'w', encoding='utf-8').write(t)
|
||||
if os.path.exists(f'{JAVA}/defpackage/b.java'): os.remove(f'{JAVA}/defpackage/b.java')
|
||||
cnt = sum(len(f) for _, _, f in os.walk(JAVA))
|
||||
print('missing-джадж:', n_add, 'файлов:', cnt, st)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
670
tools/fixwaves.py
Normal file
670
tools/fixwaves.py
Normal file
|
|
@ -0,0 +1,670 @@
|
|||
"""Error-driven фиксы по логу javac: волна boolean-кастов и private→package-private."""
|
||||
|
||||
import re, sys, os, collections
|
||||
|
||||
LOG = sys.argv[1]
|
||||
WAVES = sys.argv[2] if len(sys.argv) > 2 else "ab"
|
||||
|
||||
HDR = re.compile(r"^(\S+\.java):(\d+): error: (.+)$")
|
||||
|
||||
|
||||
def load_errors(path):
|
||||
errs = []
|
||||
lines = open(path, encoding="utf-8", errors="replace").read().splitlines()
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
m = HDR.match(lines[i])
|
||||
if m:
|
||||
col = None
|
||||
srcline = None
|
||||
if i + 2 < len(lines) and lines[i + 2].lstrip().startswith("^"):
|
||||
srcline = lines[i + 1]
|
||||
col = len(lines[i + 2]) - len(lines[i + 2].lstrip()) + 1
|
||||
extra = []
|
||||
j = i + 1
|
||||
while j < len(lines) and j < i + 8 and lines[j].strip():
|
||||
if (
|
||||
lines[j]
|
||||
.lstrip()
|
||||
.startswith(("symbol:", "location:", "required:", "found:"))
|
||||
):
|
||||
extra.append(lines[j].strip())
|
||||
j += 1
|
||||
errs.append(
|
||||
(
|
||||
m.group(1),
|
||||
int(m.group(2)),
|
||||
col,
|
||||
srcline,
|
||||
m.group(3) + (" | " + " | ".join(extra) if extra else ""),
|
||||
)
|
||||
)
|
||||
i += 3
|
||||
else:
|
||||
i += 1
|
||||
return errs
|
||||
|
||||
|
||||
OPS_STOP = set("+-*/%&|^<>!=?:,")
|
||||
|
||||
|
||||
def scan_end(line, start):
|
||||
"""Конец primary-выражения, начиная с start (0-based). None = не найден."""
|
||||
depth = 0
|
||||
i = start
|
||||
n = len(line)
|
||||
first = True
|
||||
while i < n:
|
||||
c = line[i]
|
||||
if c in "([{":
|
||||
depth += 1
|
||||
elif c in ")]}":
|
||||
if depth == 0:
|
||||
break
|
||||
depth -= 1
|
||||
elif depth == 0 and not first and (c in OPS_STOP or c == ";"):
|
||||
break
|
||||
elif depth == 0 and c == ")":
|
||||
break
|
||||
first = False
|
||||
i += 1
|
||||
return i
|
||||
|
||||
|
||||
def scan_multiline(lines, ln, start):
|
||||
"""Возвращает (li, idx, ok): конец operand, начиная с lines[ln-1][start]."""
|
||||
depth = 0
|
||||
first = True
|
||||
li = ln - 1
|
||||
i = start
|
||||
guard = 0
|
||||
while li < len(lines) and guard < 400:
|
||||
guard += 1
|
||||
line = lines[li]
|
||||
while i < len(line):
|
||||
c = line[i]
|
||||
if c in "([{":
|
||||
depth += 1
|
||||
elif c in ")]}":
|
||||
if depth == 0:
|
||||
return li, i, True
|
||||
depth -= 1
|
||||
elif depth == 0 and not first and (c in OPS_STOP or c == ";"):
|
||||
return li, i, True
|
||||
first = False
|
||||
i += 1
|
||||
li += 1
|
||||
i = 0
|
||||
return li, i, False
|
||||
|
||||
|
||||
def wave_a(errs, stats, i2b=True, b2i=True):
|
||||
per_file = collections.defaultdict(list)
|
||||
for f, ln, col, srcline, msg in errs:
|
||||
if i2b and "int cannot be converted to boolean" in msg and srcline and col:
|
||||
per_file[f].append((ln, col, srcline, "i2b"))
|
||||
elif b2i and "boolean cannot be converted to int" in msg and srcline and col:
|
||||
per_file[f].append((ln, col, srcline, "b2i"))
|
||||
for f, items in per_file.items():
|
||||
lines = open(f, encoding="utf-8").read().splitlines(keepends=True)
|
||||
by_line = collections.defaultdict(list)
|
||||
for ln, col, srcline, kind in items:
|
||||
by_line[ln].append((col, kind))
|
||||
touched = False
|
||||
for ln, cols in sorted(by_line.items(), reverse=True):
|
||||
if ln - 1 >= len(lines):
|
||||
continue
|
||||
i2b_done = False
|
||||
for col, kind in sorted(cols, reverse=True):
|
||||
if kind != "i2b":
|
||||
continue
|
||||
if i2b_done:
|
||||
stats["i2b_dup_line"] += 1
|
||||
continue
|
||||
pos = col - 1
|
||||
# найти (boolean): на этой строке перед колонкой, иначе выше до 3 строк
|
||||
cast_line, cast_pos = None, None
|
||||
for back in range(0, 4):
|
||||
bl = ln - 1 - back
|
||||
if bl < 0:
|
||||
break
|
||||
hay = lines[bl]
|
||||
limit = pos + 1 if back == 0 else len(hay)
|
||||
cp = hay.rfind("(boolean)", 0, limit)
|
||||
if cp != -1:
|
||||
cast_line, cast_pos = bl, cp
|
||||
break
|
||||
if cast_line is None:
|
||||
stats["i2b_no_cast"] += 1
|
||||
continue
|
||||
if cast_line == ln - 1:
|
||||
op_start = cast_pos + len("(boolean)")
|
||||
while (
|
||||
op_start < len(lines[cast_line])
|
||||
and lines[cast_line][op_start] == " "
|
||||
):
|
||||
op_start += 1
|
||||
else:
|
||||
# cast на предыдущей строке: operand начинается в начале строки ln
|
||||
op_start = 0
|
||||
cast_line = ln - 1
|
||||
while (
|
||||
op_start < len(lines[cast_line])
|
||||
and lines[cast_line][op_start] == " "
|
||||
):
|
||||
op_start += 1
|
||||
end_li, end_i, ok = scan_multiline(lines, ln, op_start)
|
||||
if not ok:
|
||||
stats["i2b_no_end"] += 1
|
||||
continue
|
||||
# собрать span
|
||||
if end_li == ln - 1:
|
||||
span = lines[ln - 1][op_start:end_i]
|
||||
new_line = (
|
||||
lines[ln - 1][:op_start]
|
||||
+ "(("
|
||||
+ span
|
||||
+ ") != 0)"
|
||||
+ lines[ln - 1][end_i:]
|
||||
)
|
||||
lines[ln - 1] = new_line
|
||||
else:
|
||||
span = (
|
||||
lines[ln - 1][op_start:]
|
||||
+ "".join(lines[ln:end_li])
|
||||
+ lines[end_li][:end_i]
|
||||
)
|
||||
tail = lines[end_li][end_i:]
|
||||
head = lines[ln - 1][:op_start]
|
||||
merged = head + "((" + span + ") != 0)" + tail
|
||||
if not merged.endswith("\n"):
|
||||
merged += "\n"
|
||||
del lines[ln - 1 : end_li + 1]
|
||||
lines.insert(ln - 1, merged)
|
||||
stats["i2b"] += 1
|
||||
touched = True
|
||||
i2b_done = True
|
||||
if touched:
|
||||
open(f, "w", encoding="utf-8").write("".join(lines))
|
||||
# b2i оставлен на потом
|
||||
if b2i:
|
||||
stats["b2i_skipped"] = sum(
|
||||
1 for x in errs if "boolean cannot be converted to int" in x[4]
|
||||
)
|
||||
|
||||
|
||||
def _scan_stmt_end(lines, li, start):
|
||||
"""Конец statement: ';' на depth 0, начиная с lines[li][start]. (end_li, end_i, ok)."""
|
||||
depth = 0
|
||||
i = start
|
||||
guard = 0
|
||||
while li < len(lines) and guard < 500:
|
||||
guard += 1
|
||||
line = lines[li]
|
||||
while i < len(line):
|
||||
c = line[i]
|
||||
if c in "([{":
|
||||
depth += 1
|
||||
elif c in ")]}":
|
||||
if depth == 0 and c == "}":
|
||||
return li, i, False
|
||||
if depth > 0:
|
||||
depth -= 1
|
||||
elif c == ";" and depth == 0:
|
||||
return li, i, True
|
||||
i += 1
|
||||
li += 1
|
||||
i = 0
|
||||
return li, i, False
|
||||
|
||||
|
||||
def wave_b2i(errs, stats):
|
||||
"""boolean-выражение в int-контексте: обернуть RHS в (EXPR) ? 1 : 0."""
|
||||
per_file = collections.defaultdict(list)
|
||||
for f, ln, col, srcline, msg in errs:
|
||||
if "boolean cannot be converted to int" in msg and srcline and col:
|
||||
per_file[f].append((ln, col))
|
||||
for f, items in per_file.items():
|
||||
lines = open(f, encoding="utf-8").read().splitlines(keepends=True)
|
||||
for ln, col in sorted(set(items), reverse=True):
|
||||
if ln - 1 >= len(lines):
|
||||
continue
|
||||
eq = None
|
||||
for back in range(0, 8):
|
||||
li = ln - 1 - back
|
||||
if li < 0:
|
||||
break
|
||||
hay = lines[li]
|
||||
limit = col if back == 0 else len(hay)
|
||||
for m in re.finditer(r"(?<![=!<>+\-*/&|^])=(?!=)", hay[:limit]):
|
||||
idx = m.start()
|
||||
e_li, e_i, ok = _scan_stmt_end(lines, li, idx + 1)
|
||||
if not ok:
|
||||
continue
|
||||
if (e_li, e_i) >= (ln - 1, col):
|
||||
eq = (li, idx)
|
||||
break
|
||||
if eq:
|
||||
break
|
||||
if not eq:
|
||||
stats["b2i_no_eq"] += 1
|
||||
continue
|
||||
li, idx = eq
|
||||
rs = idx + 1
|
||||
while rs < len(lines[li]) and lines[li][rs] in " \t":
|
||||
rs += 1
|
||||
end_li, end_i, ok = _scan_stmt_end(lines, li, rs)
|
||||
if not ok:
|
||||
stats["b2i_no_end"] += 1
|
||||
continue
|
||||
if end_li == li:
|
||||
span = lines[li][rs:end_i]
|
||||
else:
|
||||
span = (
|
||||
lines[li][rs:]
|
||||
+ "".join(lines[li + 1 : end_li])
|
||||
+ lines[end_li][:end_i]
|
||||
)
|
||||
if span.rstrip().endswith("? 1 : 0"):
|
||||
stats["b2i_dup"] += 1
|
||||
continue
|
||||
if end_li == li:
|
||||
lines[li] = (
|
||||
lines[li][:rs] + "(" + span + ") ? 1 : 0" + lines[li][end_i:]
|
||||
)
|
||||
else:
|
||||
head = lines[li][:rs]
|
||||
tail = lines[end_li][end_i:]
|
||||
merged = head + "(" + span + ") ? 1 : 0" + tail
|
||||
if not merged.endswith("\n"):
|
||||
merged += "\n"
|
||||
del lines[li : end_li + 1]
|
||||
lines.insert(li, merged)
|
||||
stats["b2i"] += 1
|
||||
open(f, "w", encoding="utf-8").write("".join(lines))
|
||||
|
||||
|
||||
PRIV = re.compile(r"has private access in (\w+)$")
|
||||
|
||||
|
||||
def wave_b(errs, stats):
|
||||
members = collections.defaultdict(set)
|
||||
for f, ln, col, srcline, msg in errs:
|
||||
m = PRIV.search(msg)
|
||||
if not m:
|
||||
continue
|
||||
owner = m.group(1)
|
||||
sig = msg[: m.start()].strip()
|
||||
name = sig.split("(")[0].strip()
|
||||
# убрать квалификаторы вида SomeClass.member -> member
|
||||
name = name.split(".")[-1]
|
||||
if re.fullmatch(r"[A-Za-z_$][\w$]*", name):
|
||||
members[owner].add(name)
|
||||
for owner, names in members.items():
|
||||
path = None
|
||||
for cand in (
|
||||
"/storage/aboba/evovis-client/src/main/java/defpackage/%s.java" % owner,
|
||||
"/storage/aboba/evovis-client/src/main/java/a/%s.java" % owner,
|
||||
"/storage/aboba/evovis-client/src/main/java/b/%s.java" % owner,
|
||||
"/storage/aboba/evovis-client/src/main/java/evo/%s.java" % owner,
|
||||
):
|
||||
import os
|
||||
|
||||
if os.path.exists(cand):
|
||||
path = cand
|
||||
break
|
||||
if not path:
|
||||
stats["b_noowner"] += 1
|
||||
continue
|
||||
text = open(path, encoding="utf-8").read()
|
||||
orig = text
|
||||
for name in names:
|
||||
rx = re.compile(
|
||||
r"^(\s*)private\s+((?:static\s+|final\s+|volatile\s+|transient\s+|abstract\s+|native\s+|default\s+|synchronized\s+)*"
|
||||
r"[\w.$<>\[\], ?]+\s+(?<![\w$])"
|
||||
+ re.escape(name)
|
||||
+ r"(?![\w$])\s*[;=(])",
|
||||
re.M,
|
||||
)
|
||||
text, n = rx.subn(r"\1\2", text)
|
||||
stats["b_priv"] += n
|
||||
if text != orig:
|
||||
open(path, "w", encoding="utf-8").write(text)
|
||||
|
||||
|
||||
RECORD_RX = re.compile(
|
||||
r"^(?P<mods>(?:public\s+|final\s+|abstract\s+)*)record\s+(?P<name>[A-Za-z_$][\w$]*)\s*\(",
|
||||
re.M,
|
||||
)
|
||||
|
||||
|
||||
def _split_top(s, sep=","):
|
||||
out, depth, cur = [], 0, ""
|
||||
for ch in s:
|
||||
if ch in "<([":
|
||||
depth += 1
|
||||
elif ch in ">)]":
|
||||
depth -= 1
|
||||
if ch == sep and depth == 0:
|
||||
out.append(cur)
|
||||
cur = ""
|
||||
else:
|
||||
cur += ch
|
||||
if cur.strip():
|
||||
out.append(cur)
|
||||
return out
|
||||
|
||||
|
||||
def wave_records(errs, stats):
|
||||
owners = set()
|
||||
for f, ln, col, srcline, msg in errs:
|
||||
m = PRIV.search(msg.split(" | ")[0])
|
||||
if m:
|
||||
owners.add(m.group(1))
|
||||
base = "/storage/aboba/evovis-client/src/main/java/"
|
||||
for owner in sorted(owners):
|
||||
path = None
|
||||
for pkg in ("defpackage/", "a/", "b/", "evo/"):
|
||||
cand = base + pkg + owner + ".java"
|
||||
if os.path.exists(cand):
|
||||
path = cand
|
||||
break
|
||||
if not path:
|
||||
continue
|
||||
text = open(path, encoding="utf-8").read()
|
||||
head = text[:2000]
|
||||
m = RECORD_RX.search(text)
|
||||
if not m or m.group("name") != owner:
|
||||
continue
|
||||
# найти компоненты: от '(' до matching ')' с балансом
|
||||
start = m.end() - 1
|
||||
depth = 0
|
||||
i = start
|
||||
while i < len(text):
|
||||
if text[i] == "(":
|
||||
depth += 1
|
||||
elif text[i] == ")":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
break
|
||||
i += 1
|
||||
comps_raw = text[start + 1 : i]
|
||||
comps = []
|
||||
for piece in _split_top(comps_raw):
|
||||
piece = " ".join(piece.split())
|
||||
if not piece:
|
||||
continue
|
||||
parts = piece.rsplit(None, 1)
|
||||
if len(parts) != 2:
|
||||
continue
|
||||
typ, name = parts
|
||||
typ = re.sub(r"\s*\bfinal\b\s*", " ", typ).strip()
|
||||
comps.append((typ, name))
|
||||
if not comps:
|
||||
stats["rec_skip"] += 1
|
||||
continue
|
||||
# тело: от ')' до конца класса — сохранить как есть
|
||||
rest = text[i + 1 :]
|
||||
brace = rest.find("{")
|
||||
if brace == -1:
|
||||
stats["rec_skip"] += 1
|
||||
continue
|
||||
body = rest[brace + 1 :]
|
||||
header = text[: m.start()]
|
||||
mods = (m.group("mods") or "").replace("record", "").strip()
|
||||
is_public = "public" in mods
|
||||
lines_cls = []
|
||||
vis = "public " if is_public else ""
|
||||
lines_cls.append(f"{vis}final class {owner} {{")
|
||||
for typ, name in comps:
|
||||
lines_cls.append(f" {typ} {name};")
|
||||
lines_cls.append("")
|
||||
ctor_args = ", ".join(f"{typ} {name}" for typ, name in comps)
|
||||
lines_cls.append(f" {vis}{owner}({ctor_args}) {{")
|
||||
for typ, name in comps:
|
||||
lines_cls.append(f" this.{name} = {name};")
|
||||
lines_cls.append(" }")
|
||||
lines_cls.append("")
|
||||
for typ, name in comps:
|
||||
lines_cls.append(f" public {typ} {name}() {{")
|
||||
lines_cls.append(f" return this.{name};")
|
||||
lines_cls.append(" }")
|
||||
lines_cls.append("")
|
||||
# equals
|
||||
lines_cls.append(" @Override")
|
||||
lines_cls.append(" public boolean equals(Object o) {")
|
||||
lines_cls.append(f" if (!(o instanceof {owner} that)) {{")
|
||||
lines_cls.append(" return false;")
|
||||
lines_cls.append(" }")
|
||||
for typ, name in comps:
|
||||
if "[]" in typ:
|
||||
if typ.endswith("[]") and typ[:-2] in (
|
||||
"int",
|
||||
"long",
|
||||
"double",
|
||||
"float",
|
||||
"boolean",
|
||||
"byte",
|
||||
"short",
|
||||
"char",
|
||||
):
|
||||
lines_cls.append(
|
||||
f" if (!java.util.Arrays.equals(this.{name}, that.{name})) {{ return false; }}"
|
||||
)
|
||||
else:
|
||||
lines_cls.append(
|
||||
f" if (!java.util.Objects.deepEquals(this.{name}, that.{name})) {{ return false; }}"
|
||||
)
|
||||
elif typ in (
|
||||
"int",
|
||||
"long",
|
||||
"double",
|
||||
"float",
|
||||
"boolean",
|
||||
"byte",
|
||||
"short",
|
||||
"char",
|
||||
):
|
||||
if typ == "float":
|
||||
lines_cls.append(
|
||||
f" if (Float.compare(this.{name}, that.{name}) != 0) {{ return false; }}"
|
||||
)
|
||||
elif typ == "double":
|
||||
lines_cls.append(
|
||||
f" if (Double.compare(this.{name}, that.{name}) != 0) {{ return false; }}"
|
||||
)
|
||||
else:
|
||||
lines_cls.append(
|
||||
f" if (this.{name} != that.{name}) {{ return false; }}"
|
||||
)
|
||||
else:
|
||||
lines_cls.append(
|
||||
f" if (!java.util.Objects.equals(this.{name}, that.{name})) {{ return false; }}"
|
||||
)
|
||||
lines_cls.append(" return true;")
|
||||
lines_cls.append(" }")
|
||||
lines_cls.append("")
|
||||
lines_cls.append(" @Override")
|
||||
lines_cls.append(" public int hashCode() {")
|
||||
args = ", ".join(
|
||||
(
|
||||
"java.util.Arrays.deepHashCode(this.%s)" % n
|
||||
if "[]" in ty
|
||||
else "this.%s" % n
|
||||
)
|
||||
for ty, n in comps
|
||||
)
|
||||
lines_cls.append(f" return java.util.Objects.hash({args});")
|
||||
lines_cls.append(" }")
|
||||
lines_cls.append("")
|
||||
lines_cls.append(" @Override")
|
||||
lines_cls.append(" public String toString() {")
|
||||
sb = 'return "%s[" + ' % owner
|
||||
parts = []
|
||||
for ty, n in comps:
|
||||
parts.append('"%s=" + this.%s' % (n, n))
|
||||
lines_cls.append(" " + sb + ' + ", " + '.join(parts) + ' + "]";')
|
||||
lines_cls.append(" }")
|
||||
lines_cls.append(" ")
|
||||
new_text = header + "\n".join(lines_cls) + body
|
||||
open(path, "w", encoding="utf-8").write(new_text)
|
||||
stats["records"] += 1
|
||||
|
||||
|
||||
def wave_ctors(errs, stats):
|
||||
pairs = set()
|
||||
for f, ln, col, srcline, msg in errs:
|
||||
m = PRIV.search(msg.split(" | ")[0])
|
||||
if not m:
|
||||
continue
|
||||
owner = m.group(1)
|
||||
sig = msg.split(" | ")[0][: m.start()].strip()
|
||||
if sig.startswith(owner + "("):
|
||||
pairs.add(owner)
|
||||
base = "/storage/aboba/evovis-client/src/main/java/"
|
||||
for owner in pairs:
|
||||
path = None
|
||||
for pkg in ("defpackage/", "a/", "b/", "evo/"):
|
||||
cand = base + pkg + owner + ".java"
|
||||
if os.path.exists(cand):
|
||||
path = cand
|
||||
break
|
||||
if not path:
|
||||
continue
|
||||
text = open(path, encoding="utf-8").read()
|
||||
new = re.sub(
|
||||
r"(?m)^(\s*)private(\s+)" + re.escape(owner) + r"\s*\(",
|
||||
r"\1\2" + owner + "(",
|
||||
text,
|
||||
)
|
||||
if new != text:
|
||||
open(path, "w", encoding="utf-8").write(new)
|
||||
stats["ctors"] += 1
|
||||
|
||||
|
||||
def wave_pkgqual(errs, stats):
|
||||
base = "/storage/aboba/evovis-client/src/main/java/"
|
||||
per_file = collections.defaultdict(list)
|
||||
for f, ln, col, srcline, msg in errs:
|
||||
if not f.startswith(base + "b/"):
|
||||
continue
|
||||
if "cannot find symbol" not in msg:
|
||||
continue
|
||||
parts = msg.split(" | ")
|
||||
symbol = location = None
|
||||
for p in parts:
|
||||
if p.startswith("symbol:"):
|
||||
symbol = p[len("symbol:") :].strip()
|
||||
elif p.startswith("location:"):
|
||||
location = p[len("location:") :].strip()
|
||||
if not location:
|
||||
continue
|
||||
lm = re.match(r"(?:class|interface|enum|record)\s+([\w$]+)", location)
|
||||
if not lm:
|
||||
continue
|
||||
loc = lm.group(1)
|
||||
if symbol and symbol.startswith("method "):
|
||||
member = symbol[len("method ") :].split("(")[0]
|
||||
elif symbol and symbol.startswith("class "):
|
||||
member = None
|
||||
else:
|
||||
member = None
|
||||
per_file[f].append((ln, col, loc, member))
|
||||
for f, items in per_file.items():
|
||||
lines = open(f, encoding="utf-8").read().splitlines(keepends=True)
|
||||
for ln, col, loc, member in sorted(set(items), reverse=True):
|
||||
if ln - 1 >= len(lines):
|
||||
continue
|
||||
line = lines[ln - 1]
|
||||
if member:
|
||||
needle = loc + "." + member
|
||||
idx = line.find(needle)
|
||||
if idx == -1:
|
||||
stats["qual_miss"] += 1
|
||||
continue
|
||||
lines[ln - 1] = (
|
||||
line[:idx] + "defpackage." + needle + line[idx + len(needle) :]
|
||||
)
|
||||
stats["qual"] += 1
|
||||
else:
|
||||
# класс: заменить идентификатор на позиции колонки
|
||||
pos = col - 1
|
||||
m = re.match(r"[A-Za-z_$][\w$]*", line[pos:])
|
||||
if not m or m.group() != loc:
|
||||
# найти loc как целый идентификатор
|
||||
m2 = re.search(r"(?<![\w.$])" + re.escape(loc) + r"(?![\w$])", line)
|
||||
if not m2:
|
||||
stats["qual_miss"] += 1
|
||||
continue
|
||||
idx = m2.start()
|
||||
else:
|
||||
idx = pos
|
||||
lines[ln - 1] = (
|
||||
line[:idx] + "defpackage." + loc + line[idx + len(loc) :]
|
||||
)
|
||||
stats["qual"] += 1
|
||||
open(f, "w", encoding="utf-8").write("".join(lines))
|
||||
|
||||
|
||||
def wave_vec(errs, stats):
|
||||
per_file = collections.defaultdict(list)
|
||||
for f, ln, col, srcline, msg in errs:
|
||||
if "cannot find symbol" not in msg or not col:
|
||||
continue
|
||||
parts = msg.split(" | ")
|
||||
symbol = location = None
|
||||
for p in parts:
|
||||
if p.startswith("symbol:"):
|
||||
symbol = p[len("symbol:") :].strip()
|
||||
elif p.startswith("location:"):
|
||||
location = p[len("location:") :].strip()
|
||||
if not symbol or not location or "Vector3fc" not in location:
|
||||
continue
|
||||
vm = re.match(r"variable\s+([\w$]+)", location)
|
||||
sm = re.match(r"variable\s+([\w$]+)", symbol)
|
||||
if not vm or not sm:
|
||||
continue
|
||||
per_file[f].append((ln, col, vm.group(1), sm.group(1)))
|
||||
for f, items in per_file.items():
|
||||
lines = open(f, encoding="utf-8").read().splitlines(keepends=True)
|
||||
for ln, col, var, field in sorted(items, reverse=True):
|
||||
if ln - 1 >= len(lines):
|
||||
continue
|
||||
line = lines[ln - 1]
|
||||
needle = var + "." + field
|
||||
idx = line.rfind(needle, 0, col + 10)
|
||||
if idx == -1:
|
||||
idx = line.find(needle)
|
||||
if idx == -1:
|
||||
stats["vec_miss"] += 1
|
||||
continue
|
||||
lines[ln - 1] = (
|
||||
line[:idx] + var + "." + field + "()" + line[idx + len(needle) :]
|
||||
)
|
||||
stats["vec"] += 1
|
||||
open(f, "w", encoding="utf-8").write("".join(lines))
|
||||
|
||||
|
||||
def main():
|
||||
errs = load_errors(LOG)
|
||||
stats = collections.Counter()
|
||||
if "a" in WAVES:
|
||||
wave_a(errs, stats, i2b=("1" in WAVES or WAVES == "a"), b2i=False)
|
||||
elif "1" in WAVES or "2" in WAVES:
|
||||
wave_a(errs, stats, i2b="1" in WAVES, b2i=False)
|
||||
if "2" in WAVES:
|
||||
wave_b2i(errs, stats)
|
||||
if "b" in WAVES:
|
||||
wave_b(errs, stats)
|
||||
if "c" in WAVES:
|
||||
wave_records(errs, stats)
|
||||
wave_ctors(errs, stats)
|
||||
if "e" in WAVES:
|
||||
wave_pkgqual(errs, stats)
|
||||
if "f" in WAVES:
|
||||
wave_vec(errs, stats)
|
||||
print(dict(stats))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
133
tools/postbuild.py
Normal file
133
tools/postbuild.py
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
import os
|
||||
import struct
|
||||
import sys
|
||||
import zipfile
|
||||
|
||||
RENAME = {"Cif": "if", "Cdo": "do"}
|
||||
|
||||
|
||||
def rewrite_utf8(s: str) -> str:
|
||||
if "defpackage/" in s:
|
||||
s = s.replace("defpackage/", "")
|
||||
for old, new in RENAME.items():
|
||||
if old in s:
|
||||
s = s.replace(old, new)
|
||||
return s
|
||||
|
||||
|
||||
def rewrite_class(data: bytes) -> bytes:
|
||||
if data[:4] != b"\xca\xfe\xba\xbe":
|
||||
return data
|
||||
cp_count = struct.unpack_from(">H", data, 8)[0]
|
||||
i = 10
|
||||
entries = []
|
||||
idx = 1
|
||||
while idx < cp_count:
|
||||
tag = data[i]
|
||||
i += 1
|
||||
if tag == 1:
|
||||
ln = struct.unpack_from(">H", data, i)[0]
|
||||
i += 2
|
||||
raw = data[i : i + ln]
|
||||
i += ln
|
||||
try:
|
||||
txt = raw.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
entries.append(bytes([1]) + struct.pack(">H", ln) + raw)
|
||||
idx += 1
|
||||
continue
|
||||
new = rewrite_utf8(txt).encode("utf-8")
|
||||
entries.append(struct.pack(">BH", 1, len(new)) + new)
|
||||
elif tag in (5, 6):
|
||||
entries.append(bytes([tag]) + data[i : i + 8])
|
||||
i += 8
|
||||
entries.append(None)
|
||||
idx += 2
|
||||
continue
|
||||
elif tag in (3, 4):
|
||||
entries.append(bytes([tag]) + data[i : i + 4])
|
||||
i += 4
|
||||
elif tag in (7, 8, 16, 19, 20):
|
||||
entries.append(bytes([tag]) + data[i : i + 2])
|
||||
i += 2
|
||||
elif tag in (9, 10, 11, 12, 17, 18):
|
||||
entries.append(bytes([tag]) + data[i : i + 4])
|
||||
i += 4
|
||||
else:
|
||||
raise ValueError(f"unknown constant pool tag {tag} at offset {i - 1}")
|
||||
idx += 1
|
||||
out = bytearray(data[:8])
|
||||
out += struct.pack(">H", cp_count)
|
||||
for e in entries:
|
||||
if e is not None:
|
||||
out += e
|
||||
out += data[i:]
|
||||
return bytes(out)
|
||||
|
||||
|
||||
def process_dir(root: str) -> tuple[int, int]:
|
||||
moved = 0
|
||||
touched = 0
|
||||
for dp, _, files in os.walk(root):
|
||||
for name in files:
|
||||
if not name.endswith(".class"):
|
||||
continue
|
||||
path = os.path.join(dp, name)
|
||||
data = open(path, "rb").read()
|
||||
new = rewrite_class(data)
|
||||
rel = os.path.relpath(path, root)
|
||||
prefix = "defpackage" + os.sep
|
||||
if rel.startswith(prefix):
|
||||
target = os.path.join(root, rel[len(prefix) :])
|
||||
os.makedirs(os.path.dirname(target), exist_ok=True)
|
||||
open(target, "wb").write(new)
|
||||
os.remove(path)
|
||||
moved += 1
|
||||
elif new != data:
|
||||
open(path, "wb").write(new)
|
||||
touched += 1
|
||||
return moved, touched
|
||||
|
||||
|
||||
def process_jar(path: str) -> tuple[int, int]:
|
||||
src = zipfile.ZipFile(path)
|
||||
tmp = path + ".tmp"
|
||||
dst = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED)
|
||||
moved = 0
|
||||
touched = 0
|
||||
for info in src.infolist():
|
||||
name = info.filename
|
||||
data = src.read(name)
|
||||
if name.endswith(".class"):
|
||||
new = rewrite_class(data)
|
||||
if name.startswith("defpackage/"):
|
||||
name = name[len("defpackage/") :]
|
||||
moved += 1
|
||||
elif new != data:
|
||||
touched += 1
|
||||
data = new
|
||||
dst.writestr(info, data)
|
||||
dst.close()
|
||||
src.close()
|
||||
os.replace(tmp, path)
|
||||
return moved, touched
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: postbuild.py <classes-dir | jar>", file=sys.stderr)
|
||||
return 2
|
||||
target = sys.argv[1]
|
||||
if os.path.isdir(target):
|
||||
moved, touched = process_dir(target)
|
||||
elif target.endswith(".jar"):
|
||||
moved, touched = process_jar(target)
|
||||
else:
|
||||
print(f"not a dir or jar: {target}", file=sys.stderr)
|
||||
return 2
|
||||
print(f"postbuild: moved={moved} rewritten={touched} ({target})")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue