BenBE's humble thoughts Thoughts the world doesn't need yet …

10.07.2009

Konsequent inkonsistent

Filed under: Software — Schlagwörter: , , — BenBE @ 13:05:40

PHP ist eine wunderbare Programmiersprache … zumindest für Masochisten. Wer zudem noch auf Inkonsistenz steht, hat spätestens mit PHP eine verlässliche Quelle für tägliche Überraschungen zur Verfügung. Wer das nicht glaubt, sollte sich PHP einmal anschauen: Hier also einmal ein Einsteiger-Kurs, für alle die, die schon immer eine Sprache haben wollten, die mehr Ausnahmen wie das deutsche Steuerrecht besitzt und komplizierter wie Brainfuck zu programmieren ist. Gut: Beides war vor PHP da, von daher könnte man maximal schlechte Immitation konstatieren, aber selbst das passt zur Sprache an sich ja bereits mehr als treffend.

Als erstes schauen wir uns einmal an, wie Funktionen arbeiten. Ja, Funktionen: Diese Dinge, die PHP mitliefert, um aus Parametern Ergebnisse zu generieren. Das einfachste Beispiel für eine solche ist strpos. Diese liefert für einen gegebenen Suchstring die Angabe, wo in einem Quellstring das erste Vorkommen anzutreffen ist. Der Rückgabewert ist hierbei der Offset des ersten Zeichens des Vorkommens, was viele veranlasst, nach dem Vorhandensein mal eben so zu fragen:

if($p = strpos($a,$b)) {
    echo "Gefunden an Position $p.";
}

Das ist doch mal richtig elegant! Naja, und falsch, da wir nie „Hallo“ in „Hallo Welt!“ finden werden – zumindest nicht mit diesem Source. Viele werden jetzt sagen, dass da was fehlt: Ja, die explizite Typ-Prüfung:

if(false !== ($p = strpos($a,$b))) {
    echo "Gefunden an Position $p.";
}

Das wären dann aber auch gleich die schlimmsten Abarten verschiedenster Sprachen übernommen: String-Indizierung von C, Weak-Typing und Inline-Assignments – zumindest wenn man sauberen Quelltext schreiben möchte, ist solcher syntaktischer Zucker Gift. Aber das ist eine andere Geschichte. Genauso wie das Einfügen von Variablen in geparste Strings. Die im obigen Beispiel bereits verwendete Möglichkeit, Variablen direkt in einen String einzubetten mag für einfache Fälle zwar ausreichen, verfehlt aber mindestens die Möglichkeit, Escaping gleich mit zu erledigen. Wer so seine Strings ausgibt hat zumindestens eine neue Stelle geschaffen, bei der unkontrolliert gefährliche Daten an die Welt gelangen könnten. Aber lassen wir das vorerst, da hier weniger die Art der Ausgabe, als vielmehr die übergebenen Daten Schuld wären. Aber einwas schönes hat diese Syntax schon: Man kann auch direkt Arrays einbetten:

$a = array("test");
echo "\$a[0] = $a[0]";

Soweit klar. Aber wehe man will verschachtelte Arrays nutzen, denn dann liefert

$a = array(array("test"));
echo "\$a[0][0] = $a[0][0]";

nicht etwa die gewünschte Ausgabe ‚$a[0][0] = test‘, sondern ‚Array[0]‘. Hups? Was ist da passiert? PHP parst Arrays in Strings zwar, jedoch nicht deren Verschachtelungen. Möchte man dies erreichen, muss man dies markieren:

$a = array(array("test"));
echo "\$a[0][0] = {$a[0][0]}";

Dann kann man aber auch gleich übertreiben:

$a = array(array(0));
echo "\$a[0][0] = {$a[$a[0][0]][$a[0][0]]}";

Gelle?

Okay. Zugegebenermaßen ist die Variablen-Syntax innerhalb von Strings nicht ganz logisch. Die Ecken und Kanten von Heredoc und PHPDoc spare ich mir jetzt aber einmal – etwas so schlecht von Perl abgeschautes muss man nicht auch noch kommentieren.

Von daher lassen wir das und schauen uns dafür einmal Referenzen an. Referenzen sollen in PHP in etwa das ermöglichen, wofür es in anderen Programmiersprachen Pointer gibt – nur halt etwas weniger gefährlich. Naja, eine Gefahr ist PHP ja bereits an sich, also brauch es der User nicht auch noch erweitern. Aber zurück zu Referenzen. Was macht folgender Code?

$a[] =& $a = array();

Je nach PHP-Version entweder ein in sich rekursives Array erzeugen, oder ein Array, in dem ein rekursives Array anzutreffen ist. Halt je nach PHP-Version, aktueller Weltraum-Wetterlage und derzeitigem Ladestatus des beiliegenden Flux-Kompensators.

Ach ja: Rekursive Arrays, da war ja noch was: wenn man Spaß haben will, versucht man obiges Array einmal zu serialisieren. Dies wird einem von PHP mit dem Hinweis „Out of Memory“ oder „Stack Overflow“ (wieder je nach Weltraumwetterlage) quittiert. Korrekt wäre „a:{i:0;r:0;};“ Was? r ist noch niemandem aufgefallen beim Serialisieren? Vermutlich deshalb, weil unserialize Daten lesen kann, die serialize nicht einmal generieren kann – einmal ganz abgesehen davon, dass die Rekursions-Prüfung zur Kreis-Erkennung im Serialisierungsgraphen fehlt…

Witzig ist übrigens auch die Abwandlung „a:{i:0;r:1;};“. Diese liefert nicht etwa einen Fehler wie „ungültige Referenz“ zurück, sondern erzeugt einfach ein neues Array, was dann in das bestehende eingehängt wird (und in dem dann die korrekte Rekursion auf sich selbst stattfindet). Wobei Serialisierung, da war ja noch PHP 6 … Ja! Da soll das mit der Serialisierung, den __wakeup- und den __sleep-Methoden abgeschafft werden! Aber nicht ohne im gleichen Atemzug Serialisierung nach XML einzuführen. Zumal __wakeup und __sleep ja gerade erst in der 5er Version eingeführt wurden. Naja. War halt eine der Irrungen der Kindheit.

Genauso kindlich, wie das Type-Hinting in PHP4. Ja, man konnte PHP4 sagen, welchen Datentyp man erwartet und sich dann eine Meldung generieren lassen, sollte dies nicht der Fall sein. Nunja, eben dieses Feature, was nur Basis-Typen in PHP4 und nur Klassen in PHP5 unterstützt. Bessere Kompatibilität kann man gar nicht gewährleisten. Doch: Array geht in beiden Versionen! Wahrscheinlich ein Grund mehr, das in PHP6 endlich mal zu ändern, damit wenigstens Type Hinting nicht mehr interoperabel ist. Aber gut: Dafür lässt sich das ja seit PHP5 über einen Error Handler emulieren – wenn auch die Performance dabei arg in die Knie geht; aber was soll’s: Wir ham’s ja!

Ja, wir haben es: Endlich sind sie da: Anonyme Funktionen! Zumindest für all jene, die PHP 5.3 einsetzen. Diese Funktion, die es endlich erlaubt, ohne mit create_function unkontrollierbare Speicherlöscher aufzureißen, syntaktisch prüfbare Quelltext-Blöcke an der Stelle einzubauen, wo sie hingehören. Wie? Was soll an create_function Speicherlöscher reißen? Glaubt ihr nicht? Wohl noch keinen Code, wie folgendes Beispiel gesehen?

$result = preg_replace_callback("/a/", create_function("return 'b';"), $text);

Möglichst in einer Schleife, damit es sich auch lohnt 😉 Ja, sowas geht heute effizienter:

$result = preg_replace_callback("/a/", function($m) { return 'b'; }, $text);

und da wir gerade dabei sind, führen wir auch gleich noch Closures ein – und ergänzen für den Spaßfaktor gleich noch die nächste Inkonsistenz:

$answer = 0;
$foo = function($bar) use (&$answer) { $answer = $bar; };
$foo(42);
echo $answer;

Was soll daran inkonsistent sein? Nunja … Passing By-Ref wurde eben erst deprecated. Ein Grund mehr, es bei Closures frisch neu einzuführen. Wäre ja sonst logisch…

Wer’s nicht kennt: Pass-By-Ref ist das, was so undurchsichtig gehalten ist, dass es nicht nur auf jeder PHP-Version anders funktioniert, sondern zudem sogar noch unterschiedlich angefordert werden muss. Schrieb man also in PHP4 im Aufruf das & vor die Variable, so gehört es in PHP5 in die Parameterzeile – oder so. Und während PHP4 Objekte per Default kopiert, sorgt PHP5 bei Angabe des Referenz-Operators für das Kopieren. Ich habe nicht erst eine Klasse zwischen PHP4 und PHP5 umgeschrieben, weil sich dieses Verhalten grundlegend umgekehrt hat. Wenigstens darauf kann man sich verlassen …

Eigentlich nicht mal darauf. Manche Fehler werden wenigstens konsequent fortgeführt. Autoloading wäre da ein Beispiel. Wenn einmal ein Script eine __autoload-Methode enthalten hatte, konnte sich keine weitere Datei einen eigenen Autoloader leisten, außer diese haben kooperiert. Dies mag für kleinere Skripte durchaus gehen, benötigt man jedoch ein oder mehrere Frameworks, so schießt man sich damit effektiv ins Knie. Also ärgenzen wir doch einfach einmal eine SPL-Bibliothek, die dieses Autoloading regelt. Nur gut, dass wir nun die Hälfte vergessen und man somit wieder für jede PHP-Version Sonderbehandlungen anstellen darf. Was aber wenigstens bleibt, ist die fehlende Möglichkeit, ohne Exception-Workarounds PHP darauf hinzuweisen, dass eine Klasse nicht geladen werden kann, ohne dass PHP gleich darauf durch diverse Speicherfehler wegschmiert. Dies ist die einzige Stelle, an der ich ernsthaft die Verwendung von eval benötige – was bei entsprechender Policy das Script zwar auch zum Absturz bringt, aber das würde es ja sowieso … Von daher ja kein Verlust!

Apropro kein Verlust: schon jemand einmal versucht die Sicherheits-Funktionen von PHP verstanden? Ja, ich meine diese Krücken, die wohl besser unter Black Magic hätten bekannt gemacht werden sollen, statt sie Safe Mode und Magic Quotes zu nennen. Eben diese Krücken haben extra erst dafür gesorgt, dass hunderte von Anwendern erst recht richtige Sicherheitslücken bei sich aufgerissen haben, weil durch das verworrene Verhalten dieser Funktionen alles Mögliche, nur nicht das vom Anwender spezifizierte Verhalten aufgezeigt haben. So gehören SQL-Statements escaped. Laut PHP mit einem Backslash – je nach DBMS aber mit was ganz anderem. Dabei wollen wir mal noch nicht darauf herumhacken, dass nicht einmal MySQL sich einig ist, was denn nun einen Backslash verdient. Wie soll das dann wohl PHP erraten. Ganz magisch halt, indem es einfach mal vor alles, was suspekt erscheint einen Backslash pflastert. Wer dann aus Versehen französische Backticks und andere falsch escapete Unicode-Sonderzeichen seiner Eingabe spendiert, hat seinen Payload schon fast durchgebracht. Und da dieses Escaping nicht nur für Datenbank-Befehle operiert, sondern auch für eine Reihe anderer Kommandos verliert man schnell mal die Übersicht. Wenn man dann noch nicht einmal zuverlässig erkennen kann, ob dieses Escaping auch funktioniert, ist das Chaos perfekt.

Die Open Basedir Restriction jetzt zu flamen liegt mir fern. Dieses Feature funktioniert zur Ausnahme ja öfters sogar mal. Zumindest meist dann, wenn man das Funktionieren gerade nicht gebrauchen kann. Für den umgekehrten Fall gibt es cURL und diverse andere Bibliotheken, die diesen Schwachsinn nicht ganz so ernst nehmen.

Wer also eine Sprache sucht, die in ihrem Design nicht einmal für Gewinner des IOCCC verständlich wird, sollte sofort einen Blick auf PHP werfen. Komprimierter kann man Designfehler nicht zusammenfassen: Selbst Oracle ist größer 😉

Flattr this!

3 Comments »

  1. Oha.

    Kennt man eigentlich alles, aber das vom GeSHi-Maintainer und CoreSCI-Bastler so zusammengefasst zu kriegen hat schon was einschüchterndes.
    Bringt aber gleich ne Idee für einen nächsten Artikel mit: wie programmiert man um diese ganzen Macken drumrum 😉
    Eigentlich helfen doch da nur möglichst massive Frameworks, die alles wegabstrahieren was irgendwo geht, oder?

    Grüße,
    Martok

    Kommentar by Martok — 10.07.2009 @ 16:28:49

  2. Hmm. Das Ätzenste an der ganzen Geschichte ist dann ja auch noch, dass es eigentlich keine Alternative gibt. Wo keine Konkurrenz, da keine Besserung. Aber mein Kompliment an den Vielschreiber, so lese ich gerne in Blogs!

    Liebe Grüße,

    FinnO

    Kommentar by Finn Ole — 13.07.2009 @ 21:58:46

  3. Wieso gibt es keine alternativen. Die gibt es doch genug nur leider hat jede Sprache derzeit ihre Macken und jede ist nur für bestimmte Sachen gut.

    Kommentar by neo — 20.07.2009 @ 18:20:07

RSS feed for comments on this post. TrackBack URL

Leave a comment

Powered by WordPress