#!/usr/bin/env python3
# check-globalmap — eine GLOBALE PureBasic-Map wird gesucht und danach ueber ihr
# AKTUELLES Element gelesen, ohne Sperre in derselben Prozedur.
#
# WARUM: eine PB-Map hat genau EINEN Zeiger aufs aktuelle Element — fuer alle
# Threads gemeinsam. `FindMapElement(m(), k)` setzt ihn, `m()` liest ihn. Sucht
# ein zweiter Thread dazwischen, bekommt der erste SEINEN Eintrag. Isoliert
# gemessen, 8 Threads, 16 Mio. Suchen:
#
#     FindMapElement(m(), k) : wert = m()          ->  24 320 falsche Werte
#     *e.Integer = FindMapElement(m(), k) : *e\i   ->       0
#
# Im Betrieb (HTTPLIB-133): der QPACK-Encoder sperrt nur je Verbindung, die
# statische Tabelle lag in einer globalen Map. Verschiedene H3-Verbindungen
# kodierten parallel, eine Antwort bekam den Index einer fremden — kein
# :status, dafuer accept-ranges doppelt, der Client beendete die ganze
# Verbindung. In den 5xx-Zahlen sieht man davon nichts.
#
# Abhilfe je nach Art der Map:
#   - nach dem Start nur gelesen   -> lhs_arena-Map (liest ohne gemeinsamen
#                                     Zustand; fold=#False, wenn Werte im
#                                     Schluessel stehen)
#   - zur Laufzeit geschrieben     -> unter EINER Sperre lesen UND schreiben
#   - Zustand je Verbindung        -> an die Verbindung haengen statt nachschlagen
#
# Sperrt der AUFRUFER, gehoert die Stelle mit Begruendung in
# tools/check-globalmap.erlaubt ("sicher"). Bekannte, noch offene Faelle stehen
# dort als "offen" mit Ticket — sie werden gemeldet, halten den Bau aber nicht an.
import os, re, sys

os.chdir(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
wurzeln = ["http_lib", "net_lib", "lhs_lib", "inc", "wowa-edge.pb"]
dateien = []
for w in wurzeln:
    if os.path.isfile(w):
        dateien.append(w)
    elif os.path.isdir(w):
        for d, _, fs in os.walk(w):
            dateien += [os.path.join(d, f) for f in fs if f.endswith((".pb", ".pbi"))]
dateien.sort()

erlaubt = {}
pfad_erlaubt = "tools/check-globalmap.erlaubt"
if os.path.exists(pfad_erlaubt):
    for z in open(pfad_erlaubt, encoding="utf-8"):
        z = z.strip()
        if not z or z.startswith("#"):
            continue
        teile = z.split(None, 2)
        if len(teile) >= 2:
            erlaubt[teile[0]] = (teile[1], teile[2] if len(teile) > 2 else "")

quelle = {}
glob = set()
for f in dateien:
    t = open(f, encoding="utf-8-sig", errors="replace").read().splitlines()
    quelle[f] = t
    for z in t:
        m = re.match(r"\s*Global\s+NewMap\s+([A-Za-z_]\w*)", z)
        if m:
            glob.add(m.group(1))

proz_re = re.compile(r"\s*Procedure(C|DLL|CDLL)?(\.\w+)?\s+(\w+)")
harmlos = r"(FindMapElement|DeleteMapElement|AddMapElement|MapSize|ResetMap|ClearMap|FreeMap)\(\s*%s\(\)"

befunde = {}   # "datei:prozedur" -> [zeilen]
for f, t in quelle.items():
    for i, z in enumerate(t):
        code = z.split(";")[0]
        for m in re.finditer(r"FindMapElement\(\s*([A-Za-z_]\w*)\(\)", code):
            name = m.group(1)
            if name not in glob:
                continue
            rest = code[m.end():] + "\n" + "\n".join(x.split(";")[0] for x in t[i + 1:i + 4])
            rest = re.sub(harmlos % re.escape(name), "", rest)
            if not re.search(r"\b%s\(\)" % re.escape(name), rest):
                continue
            s = i
            while s > 0 and not proz_re.match(t[s]):
                s -= 1
            e = i
            while e < len(t) - 1 and not re.match(r"\s*EndProcedure", t[e]):
                e += 1
            pm = proz_re.match(t[s])
            proz = pm.group(3) if pm else "?"
            if any(re.search(r"\b(Try)?LockMutex\(", x.split(";")[0]) for x in t[s:e + 1]):
                continue
            befunde.setdefault("%s:%s" % (f, proz), []).append(i + 1)

fehler = 0
offen = 0
for k in sorted(befunde):
    zeilen = ",".join(str(n) for n in befunde[k])
    if k in erlaubt and erlaubt[k][0] == "sicher":
        continue
    if k in erlaubt and erlaubt[k][0] == "offen":
        offen += 1
        print("  OFFEN: %s (Z. %s) — %s" % (k, zeilen, erlaubt[k][1]))
        continue
    fehler += 1
    print("  WETTLAUF: %s (Z. %s) — globale Map ueber das aktuelle Element gelesen, keine Sperre" % (k, zeilen))

for k in sorted(erlaubt):
    if k not in befunde:
        print("  HINWEIS: %s steht in %s, kommt aber nicht mehr vor — Eintrag entfernen" % (k, pfad_erlaubt))

if fehler:
    print("  -> Sperre, lhs_arena-Map oder Rueckgabezeiger von FindMapElement. Siehe HTTPLIB-133.")
    sys.exit(1)
print("  Globale-Map-Pruefung: in Ordnung" + (" (%d offen, s. Ticket)" % offen if offen else ""))
