import ast
from operator import eq
import shlex
import sys
import dataclasses
from typing import Any, cast
import uuid
@dataclasses.dataclass
class Variable:
name: str
requirements: list[str]
def seqmatch(sadi: list[Any], maso: list[Any]):
if len(maso) > len(sadi):
return False
return all(map(lambda x: eq(*x), zip(sadi, maso)))
def debug(*args, **kwargs):
pass
def parser(prog: str):
PUNC = '+-*/<=>;'
prog = ';\n'.join(prog.splitlines())
lex = shlex.shlex(prog, punctuation_chars=PUNC)
lex.whitespace_split = True
variables: set[str] = set()
reqvar: dict[str, list[tuple[uuid.UUID, bool]]] = {}
requirements: list[tuple[uuid.UUID, bool]] = []
unreachable: list[bool] = [False]
cutbranch: set[uuid.UUID] = set()
leakable: dict[uuid.UUID, tuple[set[str], set[str]]] = {}
line = 1
def isVisible(name: str):
if name not in variables:
return False
req = reqvar[name]
return seqmatch(requirements, req)
def checkVar(name: str):
if unreachable[-1]:
return False
if name.isdigit():
return False
if name in lex.punctuation_chars:
return False
if not isVisible(name):
return True
return False
def printvars(names: list[str]):
for name in names:
print(f'Line {line}: variable {name} might not have been initialized')
def reportUnreachable():
if unreachable[-1]:
print(f'Line {line}: unreachable code')
return unreachable[-1]
def leakVar(lhs: str):
if len(reqvar[lhs]) > 1:
leakable.setdefault(reqvar[lhs][-2][0], (set(), set()))
leakable[reqvar[lhs][-2][0]][reqvar[lhs][-2][1]].add(lhs)
reqvar[lhs].pop()
if lhs in leakable[reqvar[lhs][-1][0]][not reqvar[lhs][-1][1]]:
debug(f'Leak (leakable) {lhs} to {reqvar[lhs][:-1]}')
leakVar(lhs)
else:
reqvar[lhs].pop()
def addVar(lhs: str):
if lhs in variables and len(reqvar[lhs]) == len(requirements) and reqvar[lhs][-1][0] == requirements[-1][0]:
debug(f'Leak (double declare) {lhs} to {requirements[:-1]} overriding {reqvar.get(lhs)}')
leakVar(lhs)
elif not isVisible(lhs):
if requirements and requirements[-1] in cutbranch:
debug(f'Leakly declare {lhs} in {requirements[:-1]} over {requirements}')
variables.add(lhs)
reqvar[lhs] = requirements[:-1]
if reqvar[lhs]:
leakable.setdefault(reqvar[lhs][-1][0], (set(), set()))
leakable[reqvar[lhs][-1][0]][reqvar[lhs][-1][1]].add(lhs)
else:
debug(f'Declare {lhs} in {requirements} overriding {reqvar.get(lhs)}')
variables.add(lhs)
reqvar[lhs] = requirements.copy()
if reqvar[lhs]:
leakable.setdefault(reqvar[lhs][-1][0], (set(), set()))
leakable[reqvar[lhs][-1][0]][reqvar[lhs][-1][1]].add(lhs)
else:
debug(f'Skip {lhs} in {requirements} overriding {reqvar.get(lhs)}')
while True:
tok = lex.get_token()
if not tok:
break
match tok:
case 'PARAM':
while True:
lhs = lex.get_token()
if not lhs or lhs == ';':
if lhs:
lex.push_token(lhs)
break
addVar(lhs)
case 'IF':
mute = reportUnreachable()
reported: set[str] = set()
while True:
tok2 = lex.get_token()
if not tok2 or tok2 == 'THEN':
break
if tok2 in reported or checkVar(tok2):
reported.add(tok2)
requirements.append((uuid.uuid4(), False))
unreachable.append(False)
if not mute:
printvars(list(sorted(reported)))
debug(f'Enter IF scope {requirements[-1]}')
case ';':
line += 1
case 'ELSE':
debug(f'Exit IF scope {requirements[-1]}')
requirements.append((cast(tuple[uuid.UUID, uuid.UUID], requirements.pop())[0], True))
debug(f'Enter ELSE scope {requirements[-1]}')
unreachable.pop()
unreachable.append(False)
case 'END':
debug(f'Exit scope {requirements[-1]}')
lex.get_token()
requirements.pop()
unreachable.pop()
case 'RETURN':
mute = reportUnreachable()
debug(f'Return at {requirements[-1] if requirements else None}')
lhs = lex.get_token()
if not lhs:
break
if not mute and checkVar(lhs):
printvars([lhs])
if requirements:
cutbranch.add(requirements[-1][0])
for name, req in reqvar.items():
if req and req[-1][0] == requirements[-1][0]:
debug(f'Leak (cut branch) {name} to {req[:-1]} overridng {req}')
leakVar(name)
unreachable[-1] = True
case lhs:
dry = reportUnreachable()
op = lex.get_token()
if not op:
break
assert op in lex.punctuation_chars
reported: set[str] = set()
while True:
tok2 = lex.get_token()
if not tok2 or tok2 == ';':
if tok2:
lex.push_token(tok2)
break
if tok2 in reported or checkVar(tok2):
reported.add(tok2)
if not dry:
printvars(list(reported))
addVar(lhs)
prog = sys.stdin.read()
parser(prog)