Pruefung auf doppelte Funktionsnamen — und ein zweiter Fund

Die Kollision von heute war weder fuer den Browser noch fuer einen
Syntaxpruefer sichtbar: Zwei Funktionen desselben Namens sind erlaubtes
JavaScript, die spaetere gewinnt lautlos. Auch die
Oberflaechen-Tests schwiegen -- sie pruefen, ob Kacheln DA sind, nicht
ob sinnvoller Text darin steht.

pruef-namenskollision.mjs durchsucht jetzt alle 110 Dateien mit eigenem
JavaScript. Mit Gegenprobe, damit die Pruefung nicht selbst kaputtgehen
und dabei "sauber" melden kann.

ZWEITER FUND, AELTER ALS MEIN FEHLER

Sie meldete sofort eine weitere Kollision: tageSeit stand zweimal in
verwaltung.html. Beide rechneten dasselbe, mit einem Unterschied -- bei
fehlendem Datum gab die eine null zurueck, die andere 0.

Die spaetere (mit 0) gewann. Damit war die frueher definierte
wirkungslos, und mit ihr die Pruefungen "if (t === null) return ''" in
altersText und dringlichkeit: Ohne Datum stand dort "seit heute" statt
gar nichts.

Entfernt wurde die spaetere. Die beiden verbliebenen Aufrufer vertragen
null genauso wie 0 -- nachgeprueft, nicht angenommen: Math.max(x, null)
ergibt x, und null >= 7 ist falsch, gleich wie bei 0.

110 Dateien jetzt sauber.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-08-26 14:58:12 +02:00
co-authored by Claude Opus 5
parent 0e8c1447f2
commit 92d2b568de
5 changed files with 123 additions and 14 deletions
+1 -1
View File
@@ -838,7 +838,7 @@
Stand haben. Die Nummer wird zusammen mit CACHE_NAME in sw.js
hochgezaehlt -- beide gehoeren zusammen. */
navigator.serviceWorker
.register("/webdesign/sw.js?v=57", { scope: "/webdesign/" })
.register("/webdesign/sw.js?v=58", { scope: "/webdesign/" })
.catch(function () { /* still schlucken: ohne SW funktioniert alles weiter */ });
});
}
+102
View File
@@ -0,0 +1,102 @@
/* =====================================================================
Sucht doppelt vergebene Funktionsnamen.
ANLASS
Am 26.08.2026 brachte neuer Code eine Hilfsfunktion namens "kachel"
mit. Weiter oben in derselben Datei gab es seit langem eine andere
Funktion desselben Namens, die die Kacheln der Uebersicht baut.
JavaScript kennt keine Ueberladung: Die spaetere Definition gewinnt.
Ohne Warnung, ohne Fehler, ohne dass irgendetwas anschlaegt. Die
Uebersicht zeigte danach in jeder Kachel "[object Object]".
WARUM KEIN ANDERES WERKZEUG DAS FINDET
- Der Browser meldet nichts: Zwei Funktionen desselben Namens sind
erlaubtes JavaScript.
- "node --check" meldet nichts: syntaktisch einwandfrei.
- Die Oberflaechen-Tests meldeten nichts: Sie pruefen, ob Kacheln DA
sind, nicht ob sinnvoller Text darin steht.
- Am tueckischsten: Der neue Code selbst funktionierte einwandfrei.
Der Schaden trat an einer ganz anderen Stelle auf, und nichts
deutete auf die Ursache hin.
Diese Pruefung kostet Sekundenbruchteile und haette den Fehler
verhindert, bevor er ausgeliefert wurde.
===================================================================== */
import fs from "fs";
import path from "path";
let ok = 0, fehl = 0;
const pruefe = (name, gut, info) => {
if (gut) { ok++; console.log(" ok " + name + (info ? " -> " + info : "")); }
else { fehl++; console.log(" FEHL " + name + (info ? " -> " + info : "")); }
};
const WURZEL = process.cwd();
/* Alle Dateien mit eigenem JavaScript: die grossen Inline-Bloecke der
Seiten und die Skriptdateien. */
const dateien = [
...fs.readdirSync(WURZEL).filter((f) => f.endsWith(".html")).map((f) => f),
...fs.readdirSync(path.join(WURZEL, "webdesign"))
.filter((f) => f.endsWith(".html")).map((f) => "webdesign/" + f),
...fs.readdirSync(path.join(WURZEL, "assets/js"))
.filter((f) => f.endsWith(".js")).map((f) => "assets/js/" + f),
];
console.log(`Durchsucht: ${dateien.length} Dateien\n`);
const betroffen = [];
for (const rel of dateien) {
const quelle = fs.readFileSync(path.join(WURZEL, rel), "utf8");
/* Nur Deklarationen am Zeilenanfang (mit Einrueckung). Damit bleiben
Methoden in Objekten, verschachtelte Hilfsfunktionen in anderen
Funktionen und Zeichenketten aussen vor -- die kollidieren nicht
miteinander, weil sie in eigenen Gueltigkeitsbereichen liegen. */
const muster = /^[ \t]*function\s+([A-Za-z_$][\w$]*)\s*\(/gm;
const gesehen = new Map();
let t;
while ((t = muster.exec(quelle)) !== null) {
const name = t[1];
const zeile = quelle.slice(0, t.index).split("\n").length;
if (!gesehen.has(name)) gesehen.set(name, []);
gesehen.get(name).push(zeile);
}
for (const [name, zeilen] of gesehen) {
if (zeilen.length > 1) {
betroffen.push({ datei: rel, name, zeilen });
}
}
}
pruefe("kein Funktionsname ist doppelt vergeben", betroffen.length === 0,
betroffen.length ? betroffen.length + " Kollision(en)" : dateien.length + " Dateien sauber");
for (const b of betroffen) {
console.log(`\n ${b.datei}`);
console.log(` "${b.name}" steht in Zeile ${b.zeilen.join(" und ")}`);
console.log(` Die spaetere gewinnt — die fruehere ist damit wirkungslos.`);
}
/* Gegenprobe: Die Pruefung muss auch wirklich etwas finden koennen.
Ohne diesen Abschnitt koennte das Muster kaputt sein und immer
"sauber" melden. */
console.log("\nGEGENPROBE");
const beispiel = `
function tuwas(a) { return a; }
var x = 1;
function tuwas(a, b) { return b; }
`;
const m2 = /^[ \t]*function\s+([A-Za-z_$][\w$]*)\s*\(/gm;
const treffer = [...beispiel.matchAll(m2)].map((x) => x[1]);
pruefe(" findet eine eingebaute Kollision",
treffer.filter((n) => n === "tuwas").length === 2,
treffer.join(", "));
console.log(`\n===== ${ok} bestanden, ${fehl} fehlgeschlagen =====`);
process.exit(fehl ? 1 : 0);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

+1 -1
View File
@@ -34,7 +34,7 @@
Start alle alten Zwischenspeicher weg. Muss bei jeder Änderung an den
Dateien unten hochgezählt werden, sonst hängen Nutzer auf einem alten
Stand fest. */
const CACHE_NAME = "dogfather-webdesign-v57";
const CACHE_NAME = "dogfather-webdesign-v58";
/* Bausteine, die die Oberfläche zum Anzeigen braucht. Bewusst KEINE
HTML-Datei in dieser Liste. */
+19 -12
View File
@@ -3223,18 +3223,25 @@
}
/* Ganze Tage seit einem Zeitpunkt. Bewusst ueber den Kalendertag
gerechnet, nicht ueber 24-Stunden-Bloecke: eine Anfrage von gestern
23:50 Uhr ist umgangssprachlich "gestern", nicht "vor 0 Tagen". */
function tageSeit(zeit) {
if (!zeit) return 0;
var dann = new Date(zeit);
if (isNaN(dann.getTime())) return 0;
var a = new Date(dann.getFullYear(), dann.getMonth(), dann.getDate());
var heute = new Date();
var b = new Date(heute.getFullYear(), heute.getMonth(), heute.getDate());
return Math.round((b - a) / 86400000);
}
/* ⚠️ HIER STAND EINE ZWEITE FUNKTION tageSeit (entfernt 26.08.2026).
Sie rechnete dasselbe wie die weiter oben, unterschied sich aber in
einem Punkt: Bei fehlendem oder unlesbarem Datum gab sie 0 zurueck,
die obere gibt null.
Weil in JavaScript die spaetere Definition gewinnt, war die obere
seit ihrer Einfuehrung wirkungslos -- und mit ihr die Pruefungen
"if (t === null) return ''" in altersText und dringlichkeit. Ohne
Datum stand dort seither "seit heute" statt gar nichts.
Gefunden hat das keine Fehlermeldung, sondern pruef-namenskollision.mjs,
entstanden am selben Tag aus genau demselben Fehler an anderer
Stelle. Zwei Funktionen desselben Namens sind erlaubtes JavaScript;
kein Browser und kein Syntaxpruefer schlaegt an.
Die beiden verbliebenen Aufrufer weiter unten vertragen null
genauso wie 0 -- geprueft: Math.max(x, null) ergibt x, und
null >= 7 ist falsch, gleich wie bei 0. */
function zeitText(tage) {
if (tage <= 0) return "heute";