Einige Server auf denen ich Scripte habe haben die perl Version von 5.18 auf 5.24 angehoben. Dabei sind mir ein paar der Scripte auseinander gefallen.
Der Bug taucht wie folgt auf:
my @array = split(/;/,'A;1;2;;;;');
$array[6]=6;
$array[3]=3;
was aber funktioniert ist:
my @array = split(/;/,'A;1;2;;;;');
$array[3]=3;
$array[6]=6;
(das hier mit Vorsicht benutzen, sieh den Rest des Beitrages unten)
oder
my @array = split(/;/,'A;1;2;;;;',-1);
$array[6]=6;
$array[3]=3;
oder
my @array = @{[split(/;/,'A;1;2;;;;')]};
$array[6]=6;
$array[3]=3;
Nach kurzer suche Suchen fand ich den fix-Patch:
http://perlmonks.org/?node_id=1176910
Da der Bug für mich akut wird wollte ich hier eine Warnung Aussprechen das es auch andere hier betreffen könnte.
Der Bug wurde in Version 5.21.4 am 10.12.2014 eingebaut und in der Version 5.25.8 vom 30.11.2016 behoben. Es sind also die Versionen 5.22 und 5.24 von dem Bug betroffen. Ich habe nicht geschaut ob es Patches für die Versionen für den Bug in den Distributionen drin sind.
Aber ich will auch den Hintergrund des Bugs erklären weil ich das ganz interessant finde. Es zeigt das schon kleine Optimierungen wo anders OK-Code zu Bug-Code machen können.
Also erst eine grobe Übersicht was split intern mit dem Array der Daten macht.
Nachdem der String mit der regexp-engine zerlegt wurde wird bestimmt ob das Array gekürzt werden soll. Das passiert immer wenn keine Länge es zurück gegebenen Arrays angegeben wurde (dritter Wert). Dazu wird das Array rückwärts durch gegangen und alle Einträge auf "undef" gesetzt die als leer betrachtet werden, bis auf ein gefüllten Eintrag gestoßen wird. Zum Schluss wird im Array-Kopf die neue Länge des Arrays eingetragen. Hier zu beachten ist das die "undef" gesetzten Einträge nicht gelöscht sind, nur versteckt.
Vor der Version 5.22 war das kein Problem da die Array-Einträge bei der Rückgabe aus dem split in das Ziel-Array kopiert wurden, dabei fielen die "undef" gesetzten Einträge weg. Ab der Version 5.22 wurde das split optimiert, so das wenn das split-Ziel ein Array ist dieses direkt verwendet wird und kein Kopiervorlag mehr stattfindet. Das beschleunigt die Funktion unter Umständen ungemein. Aber nun kommen die im split selber undef gesetzten Einträge nach außen. Das scheint auf den ersten Blick kein Problem zu sein, da sie
1. hinter dem Ende des Arrays liegen und damit keinen Einfluss auf den Ablauf haben und
2. einfach "undef" enthalten.
Aber es ist wichtig zu wissen, das alle Werte die außerhalb des "Perl-Kontext" im C-Code gesetzt werden ein "read-only" Flag haben und dieses gelöscht werden muss, wenn der Wert überschreibbar sein soll (Die
$1 ... $10 der regexp sind ein bekanntes Beispiel dafür). Wenn man nun das Array-Ende hinter die im split selber "undef" gesetzten Einträge ausdehnt ohne die Einträge zu überschreiben (perl prüft die Flags nicht wenn ein neuer Array Eintrag hinter dem ende das Arrays liegt), Dann tauchen die schreibgeschützten "undef" Einträge auf und es kommt zu dem oben genannten Fehlverhalten.
Das Problem wurde ganz einfach behoben indem ein flag-loser Wert statt dem "undef" in den Array-Eintrag gesetzt wurde. (NULL)
Ich fand es interessant dem Problem auf den Grund zu gehen. Und ich hoffe das Ihr jetzt nicht zu viel Arbeit habt diesen Bug zu umschiffen.