Klaus: Guter Code-Analysator

Hallo,
ich suche ein gutes Tool, um PHP Code auf Fehler zu überprüfen.
Zwar bringt error_reporting(E_ALL); schon viel, aber man erhält nicht immer den besten Code.

Mit der Code-Analyse von Zend bin ich nicht zufrieden, denn:
<?php
$erg = mysql_query("SELECT * FROM tb");
while($row = $erg) { } //Bug: Zuweisung in Bedingung

//Und vor allem:
if($_GET['pw'] == "geheim")
   $log = 1;

if($log == 1)
   echo "Geheimer Bereich";
?>

Dies wird nicht als Bug erkannt, obwohl man in den geheimen Bereich gelangen kann, wenn register globals auf Off steht.

Grüße
Klaus

  1. Wieso suchst du nicht gleich ein Programm,
    welches dir dein Programm fertigt?

    1. Hallo,

      Wieso suchst du nicht gleich ein Programm,
      welches dir dein Programm fertigt?

      das wäre auch was schönes.

      Aber theoretisch ist ein "guter" Code-Analysator gar nicht so schwer zu programmieren.
      Darum frage ich, ob dies evt. schon jmd. gemacht hat.

      Grüße
      Klaus

      1. Moin!

        Wieso suchst du nicht gleich ein Programm,
        welches dir dein Programm fertigt?
        das wäre auch was schönes.

        Aber theoretisch ist ein "guter" Code-Analysator gar nicht so schwer zu programmieren.

        Glaubst du, die Jungs von Zend haben das absichtlich weggelassen?

        Darum frage ich, ob dies evt. schon jmd. gemacht hat.

        Worauf wartest du? Sei der Erste!

        - Sven Rautenberg

        --
        My sssignature, my preciousssss!
        1. Hallo,

          Darum frage ich, ob dies evt. schon jmd. gemacht hat.

          Mir fehlt wohl das Programmier-Können (!= PHP).

          Aber so schwer stell ich mir das auch nicht vor:

          Stritt 1:
          Welche Variablen werden deklariert und in welcher Zeile.
          If-Blöcke werden übersprungen.
          Dann wird ermittelt, wann der erste Zurgiff auf die Variable erfolgt. Wenn dieser vor der Initialisierung erfolgt => Meldung.
          So wäre das $log (Login) Problem zu vermeiden.

          Schritt 2:
          Spring in die IF-Anweisung und Verfahre ähnlich wie in Schritt 1 (Wurden Variablen vorher initialisiert).
          Danach spring in die nächst tiefere IF-Anweisung.
          Wenn der IF-Block fertig ist, spring zum nächsten IF-Block.

          Bsp:
          <?php
          $var = ""; //$var ini. in Zeile 1
          $text .= "Welt"; //Fehler, da $text nicht ini.

          $var .= "Hallo Welt"; //Wurde schon ini.

          //Dieser IF-Block wird in Schritt 1 übersprungen
          if($_GET['pw'] == "abc")
            {
            //Schritt 2:
            $log = 1;
            //Paar Zeilen Code
            if($log == 1) //Alles OK, da es in der IF-Anweisung ini. wurde.
              echo "Alles Klar";
            }

          if($log == 1) //Fehler, da $log nicht ini. wurde
            echo "Asd";
          ?>

          ini = Initialisiert.

          Grüße
          Klaus

          1. Hallo,

            hast du auch nur mal ansatzweise drüber nachgedacht, was das für ein Aufwand wäre, so ein Analysator?

            gruss

            --
            no strict;
            no warnings;
            79.78 cups of Coffee (Brewed) + Me = Death
            Kalorien sind winzig kleine nachtaktive Tiere, die unbeobachtet menschliche Kleidung enger nähen.
          2. echo $begrüßung;

            Aber so schwer stell ich mir das auch nicht vor:

            Nur mal ein paar kleine Probleme, die dein Programm lösen muss.

            • Woher weiß es, welche Keys in den von außen kommenden EGPCS-Variablen enthalten sind?
            • Kennst du das Konzept der variablen Variablen? Wie kommst du an die zur Laufzeit verwendeten Namen?
            • $functionName(...); -> Es gibt auch noch das Konzept der variablen Funktionen.
            • require/include $filename; -- Was steht zur Laufzeit in $filename?

            Kennst du PHP-Entwicklungsumgebungen wie das Zend-Studio, PHPEdit, oder Eclipse mit den Plugins PHPEclipse (mein Favorit) oder TruStudio? Die machen dich schon beim Tippen auf syntaktische und auch Initialisierungs-Fehler aufmerksam, haben also ein eingebautes Codeanalyse-Tool. Die können eine Hilfe sein, aber zaubern können die auch nicht und bringen gelegentlich ungerechtfertige Warnhinweise.

            echo "$verabschiedung $name";

      2. 你好 Klaus,

        Aber theoretisch ist ein "guter" Code-Analysator gar nicht so schwer zu
        programmieren.

        Hehe, ein gutes Code-Analyse-Tool kostet sehr viel Gehirnschmalz und viel
        Fleiss ;) Das grenzt Teilweise schon fast an Tiefen-Magie.

        再见,
         克里斯蒂安

        --
        No Shoes On Mat!
        http://wwwtech.de/
  2. Moin!

    ich suche ein gutes Tool, um PHP Code auf Fehler zu überprüfen.

    Auf Denkfehler. Und da Computer nicht denken können, kann es so ein Tool nicht geben.

    Zwar bringt error_reporting(E_ALL); schon viel, aber man erhält nicht immer den besten Code.

    Den erhält man sowieso nur durch schlaues Programmieren. Denn die Phrase "bester Code" ist sehr variabel. Für einen Husch-Husch-Formmailer mag man mal eben HTML und PHP mischen können - für einen großen Shop dürfte das nicht mehr realisierbar sein, sondern man greift sinnvollerweise auf Templates und eine vernünftige objektorientierte Programmierung zurück.

    Wie soll ein Programm sowas hinkriegen?

    Mit der Code-Analyse von Zend bin ich nicht zufrieden, denn:

    Du hast schräge Erwartungen.

    $erg = mysql_query("SELECT * FROM tb");
    while($row = $erg) { } //Bug: Zuweisung in Bedingung

    Das ist absolut so gewollt, würde ich meinen. Zuweisungen in while-Bedingungen sind jedenfalls häufiger, als du glaubst, und sie gehören dort auch absolut hin.

    //Und vor allem:
    if($_GET['pw'] == "geheim")
       $log = 1;

    if($log == 1)
       echo "Geheimer Bereich";

    Dagegen, dass du deine Variablen nicht ordentlich und immer initialisierst, kann ein Programm kaum was unternehmen, aber PHP würde sich zumindest mit einer Notice melden, wenn man ohne gültiges Passwort versucht, das Skript ablaufen zu lassen, weil $log dann benutzt wird, ohne definiert zu sein. Und wenn das keine Alarmglocken klingeln läßt, ist dir nicht zu helfen.

    Dies wird nicht als Bug erkannt, obwohl man in den geheimen Bereich gelangen kann, wenn register globals auf Off steht.

    Man kann nur in den geheimen Bereich, wenn register_globals auf on steht und man weiß, dass die Variable, die man beeinflussen muß, $log heißt.

    Wie gesagt: Das, was du willst, ist ein Intelligenzprogramm, und künstliche Intelligenz gibts noch nicht.

    - Sven Rautenberg

    --
    My sssignature, my preciousssss!
    1. Hallo,

      ich suche ein gutes Tool, um PHP Code auf Fehler zu überprüfen.

      Auf Denkfehler. Und da Computer nicht denken können, kann es so ein Tool nicht geben.

      Klar, das wollte ich auch nicht.
      Aber es gibt Fehler, die eine Maschine relativ leicht ermitteln könnte, was aber einem Menschen unter 1000 Zeilen Code nicht auffällt.

      Mit der Code-Analyse von Zend bin ich nicht zufrieden, denn:

      Du hast schräge Erwartungen.

      $erg = mysql_query("SELECT * FROM tb");
      while($row = $erg) { } //Bug: Zuweisung in Bedingung

      Das ist absolut so gewollt, würde ich meinen. Zuweisungen in while-Bedingungen sind jedenfalls häufiger, als du glaubst, und sie gehören dort auch absolut hin.

      Es ist aber sehr nervig, wenn man evt. 10 bis 20 MySQL-Abfragen in einem Script hat.
      Außerdem erkennt es keine Variablen die per Include geladen werden.

      Bps:
      <?php
      include("db_verbindung.inc.php");
      mysql_query("DELETE FROM tb",$verbindung);
      ?>

      Da $verbindung in dem Script nicht deklariert wurde, aber in db_verbindung.inc.php, erhält man bei jedem MySQL-Query eine Meldung. Und wenn man viel mit ausgelagerten Variablen/Konstanten arbeitet, z.B. auf Grund von Sprach-Dateien (Pharsen), kann man so schon mal an die 30 Meldungen und mehr Fehler für "nicht deklarierte Variablen" erhalten.
      Auch weil er dann nicht nur 1 Msg. ausgibt, sondern für jeden Zugriff auf die Variable einen neue Meldung.
      Hat man aber eine Variable wirklich gar nicht deklariert, würde das kaum noch auffallen.

      //Und vor allem:
      if($_GET['pw'] == "geheim")
         $log = 1;

      if($log == 1)
         echo "Geheimer Bereich";

      Dagegen, dass du deine Variablen nicht ordentlich und immer initialisierst, kann ein Programm kaum was unternehmen, aber PHP würde sich zumindest mit einer Notice melden, wenn man ohne gültiges Passwort versucht, das Skript ablaufen zu lassen, weil $log dann benutzt wird, ohne definiert zu sein. Und wenn das keine Alarmglocken klingeln läßt, ist dir nicht zu helfen.

      Natürlich, aber wie gesagt, bei vielen Zeilen Code kann soetwas (oder) ähnliches schon mal vorkommen.
      Zwar nicht in dem Ausmaß, aber dies ist ja auch oft ein Grund für XSS.
      Und das Notice erhält man auch nicht immer:
      Bsp:
      <?php
      if(!isset($db_verbindung))
        $fehler = "Keine Verbindung zur DB";

      //Paar weitere Zeilen Code
      echo $fehler;
      ?>

      Dort wäre ja mit register_globals = On eine XSS-Attacke möglich.

      Dies wird nicht als Bug erkannt, obwohl man in den geheimen Bereich gelangen kann, wenn register globals auf Off steht.

      Man kann nur in den geheimen Bereich, wenn register_globals auf on steht und man weiß, dass die Variable, die man beeinflussen muß, $log heißt.

      Ich meinte ja ON ;)

      Wie gesagt: Das, was du willst, ist ein Intelligenzprogramm, und künstliche Intelligenz gibts noch nicht.

      Naja KI braucht man nicht dafür.
      Logikfehler kann es nicht erkennen, aber manche Sachen sind auch für eine Maschine sofort erkennbar.

      Grüße
      Klaus

      1. Moin!

        Mit der Code-Analyse von Zend bin ich nicht zufrieden, denn:

        Du hast schräge Erwartungen.

        $erg = mysql_query("SELECT * FROM tb");
        while($row = $erg) { } //Bug: Zuweisung in Bedingung

        Das ist absolut so gewollt, würde ich meinen. Zuweisungen in while-Bedingungen sind jedenfalls häufiger, als du glaubst, und sie gehören dort auch absolut hin.
        Es ist aber sehr nervig, wenn man evt. 10 bis 20 MySQL-Abfragen in einem Script hat.

        Wenn ich mehr als eine MySQL-Abfrage in einem Skript habe, dann schreibe ich mir dafür mindestens mal eine Funktion, die mir die gesamten nervigen Low-Level-Handlungen abnimmt.

        Bei größeren Projekten schreibe ich mir aber lieber gleich eine komplette Datenbank-Klasse, deren Konstruktor automatisch die Verbindung zur DB herstellt,und dessen weitere Methoden explizit mit in dieser Klasse definierten SQL-Abfragen auf den anfallenden Informationsbedarf ausgerichtet sind und mit einem genormten Interface (meist ein schlichtes Array mit allen DB-Ergebnissen) die Ergebnisse an das Hauptskript zurückmelden.

        Und wenn ich so eine Array-Liste dann ausgeben lassen will, dann schicke ich dieses Array direkt zur Template-Klasse, welche aus dem Array und dem Template dann automatisch eine komplette Liste fertigt.

        So halte ich mein Hauptskript frei von nervigen Detailarbeiten und behalte auch dort den Überblick. Und zusätzlich begrenze ich den Einfluß von schädlichen Variablen, die mir ein eventuelles register_globals=on einschleppen könnte, denn innerhalb von Funktionen und Klassen gelten diese globalen Variablen schließlich nicht.

        Außerdem erkennt es keine Variablen die per Include geladen werden.

        Bps:
        <?php
        include("db_verbindung.inc.php");
        mysql_query("DELETE FROM tb",$verbindung);
        ?>

        Da $verbindung in dem Script nicht deklariert wurde, aber in db_verbindung.inc.php, erhält man bei jedem MySQL-Query eine Meldung. Und wenn man viel mit ausgelagerten Variablen/Konstanten arbeitet, z.B. auf Grund von Sprach-Dateien (Pharsen), kann man so schon mal an die 30 Meldungen und mehr Fehler für "nicht deklarierte Variablen" erhalten.

        Ich benutze gar keinen Code-Analysator. Deine Probleme mit existierenden Modellen kann ich absolut nicht nachvollziehen.

        Dein hier dargestelltes Problem würde aber mit einer Datenbankklasse entfallen. Da gäbe es dann nur noch eine delete-Methode in der Klasse, und das ganze MySQL-Geraffel wäre ebenfalls im Include-File untergebracht. Und schon kann der Analysator auch nicht mehr meckern.

        Mir scheint einfach, dass du noch nicht den richtigen Quelltext-Stil entwickelt hast. Analysatoren müssen natürlich von gewissen Richtlinien ausgehen, die sie als gut erkennen sollen, und Abweichungen davon dann als schlecht. Und es kann in meinen Augen nicht gut sein, wenn ein Skript eine Variable benutzt, deren Existenz in einem ganz anderen Skript erst sichergestellt wird. Zu leicht werden diese zwei Dateien - ggf. unabsichtlich - getrennt und funktionieren einzeln dann nicht mehr wie geplant.

        Und das Notice erhält man auch nicht immer:
        Bsp:
        <?php
        if(!isset($db_verbindung))
          $fehler = "Keine Verbindung zur DB";

        //Paar weitere Zeilen Code
        echo $fehler;
        ?>

        Dort wäre ja mit register_globals = On eine XSS-Attacke möglich.

        Die übliche Vorgehensweise wäre, zu Beginn

          
        $fehler = "";  
        
        

        Dann die ganzen Prüfungen, die ggf. in $fehler Meldungen eintragen.

        Und am Ende dann meinetwegen ein

          
        echo $fehler;  
        
        

        Wahrscheinlicher aber ein

          
        if (strlen($fehler)>0)  
        {  
          echo $fehler;  
        }  
        
        

        - Sven Rautenberg

        --
        My sssignature, my preciousssss!
        1. Tag,

          Da $verbindung in dem Script nicht deklariert wurde, aber in db_verbindung.inc.php, erhält man bei jedem MySQL-Query eine Meldung. Und wenn man viel mit ausgelagerten Variablen/Konstanten arbeitet, z.B. auf Grund von Sprach-Dateien (Pharsen), kann man so schon mal an die 30 Meldungen und mehr Fehler für "nicht deklarierte Variablen" erhalten.

          Ich benutze gar keinen Code-Analysator. Deine Probleme mit existierenden Modellen kann ich absolut nicht nachvollziehen.

          Dein hier dargestelltes Problem würde aber mit einer Datenbankklasse entfallen. Da gäbe es dann nur noch eine delete-Methode in der Klasse, und das ganze MySQL-Geraffel wäre ebenfalls im Include-File untergebracht. Und schon kann der Analysator auch nicht mehr meckern.

          Das stimmt zwar, aber es gibt noch viele andere Beispiele, die man so nicht lösen kann.

          Bsp:
          <?php
          $db = new DB;
          $db->query("SELECT * FROM $tb_user");
          ?>

          Wenn die Tabellen-Namen variable gehalten werden müssen, aufgrund eines Prefix wie es bei jedem größerem Massenscript der Fall ist (z.B. phpBB), dann würde ich pro SQL Anweisung also einen Meldung erhalten.
          Zwar könnte ich mit Konstaten arbeiten, z.B. so:
          $tabelle = "";
          $db->quer("SELECT * FROM _tabelle_","TB_USER");

          Das wäre aber in meinen Augen umständlich, denn die Class müsste erst _tabelle_ durch $tb_user ersetzen (oder ähnliches).

          Zu leicht werden diese zwei Dateien - ggf. unabsichtlich - getrennt und funktionieren einzeln dann nicht mehr wie geplant.

          Wenn man aber bestimmte Config Dateien Variable halten muss, dann wird das nicht funktionieren.

          Analysiert man per Zend-Code-Analyse z.B. die profile.php (2600 Zeilen) vom vBB 3.x, so erhält man fast 200 Meldungen.
          Bei der admincp/user.php (2200 Zeilen) sind es sogar fast 500 Melunden.
          Ich glaube kaum das man dort von "schlampiger" Programmierung seitens vBulletin sprechen kann.

          Die übliche Vorgehensweise wäre, zu Beginn

          $fehler = "";

          Das ist mir ja auch alles bekannt, aber bei ~1000 Zeilen Code kann es passieren, sollte aber nicht, dass eine Variable durchrutsch.  
          Und damit es kein folgen schweren Fehler gibt, sollte eine Maschine den Code gegenprüfen.  
            
          MFG  
          Klaus  
          
          
          1. echo $begrüßung;

            Das ist mir ja auch alles bekannt, aber bei ~1000 Zeilen Code kann es passieren, sollte aber nicht, dass eine Variable durchrutsch.

            Wie wäre es, Funktionalitäten in handlichen Funktionen zu kapseln, damit bekommt man die Übersicht wieder zurück und die Variablen kommen durch die Variablengeltungsbereiche sich nicht ins Gehege.

            Schlechte Programmierstil bekommt man durch Hilfsmittel auch nicht gradegebogen.

            echo "$verabschiedung $name";

          2. Moin!

            Das stimmt zwar, aber es gibt noch viele andere Beispiele, die man so nicht lösen kann.

            Bsp:
            <?php
            $db = new DB;
            $db->query("SELECT * FROM $tb_user");
            ?>

            Mit vernünftiger Ausgestaltung der Klassen wird es zu so einem Konstrukt niemals kommen, weil das so abläuft:

              
            <?php  
            $db = new DB;  
            $db->getUserList();  
              
            class DB  
            {  
              var $dbconnect;  
              var $lasterror;  
              
              function DB ()  
              {  
                $this->dbconnect = mysql_connect(...);  
                mysql_select_db(...);  
              }  
              
              function selectquery($select)  
              {  
                $result = array();  
                if ($res = mysql_query($select))  
                {  
                  while ($cell=mysql_fetch_assoc($res))  
                  {  
                    $result[] = $cell;  
                  }  
                  return $result;  
                }  
                else  
                {  
                  $this-lasterror = mysql_error();  
                  return false;  
                }  
              }  
              
              function getUserList()  
              {  
                return $this->selectquery("SELECT spalte1, spalte2 FROM usertabelle");  
              }  
              
            }  
            ?>  
            
            

            Der SQL-Code bleibt so schön separat und nur in der DB-Klasse, und an jeder Stelle, die was von der Datenbank will, muß man sich halt eine Methode definieren, die die entsprechende Abfrage realisiert.

            Vorteil: Es taucht nirgendwo sonst mehr SQL-Code auf, nur noch in der SQL-Klasse. Das hat den unschätzbaren Vorteil, dass man, sollte sich an der Datenbank was ändern (neue Version, vollkommen anderes Programm, neue/ergänzte Struktur), ausschließlich in dieser DB-Klasse etwas verändern muß.

            Und durch das eindeutig durch die Methoden definierte Interface kann es eigentlich auch keine Anpassungsprobleme geben, denn die Methode muß einfach nur sicherstellen, dass das Interface eingehalten wird - völlig unabhängig davon, ob sich beispielsweise die User-Tabelle nun vollkommen anders strukturiert, weil sie sich z.B. künftig nur noch per JOIN auslesen läßt, um an die gewünschte Information zu kommen.

            Und sollte es mal tatsächlich der Fall sein, dass die Datenbankperformance gesteigert werden muß, würde man in der Datenbankklasse ansetzen und z.B. Caching einbauen können, oder andere geschwindigkeitsfördernde Initiativen ergreifen. Alles jeweils ohne notwendige Eingriffe in den restlichen Programmcode.

            Wenn die Tabellen-Namen variable gehalten werden müssen, aufgrund eines Prefix wie es bei jedem größerem Massenscript der Fall ist (z.B. phpBB), dann würde ich pro SQL Anweisung also einen Meldung erhalten.
            Zwar könnte ich mit Konstaten arbeiten, z.B. so:
            $tabelle = "";
            $db->quer("SELECT * FROM _tabelle_","TB_USER");

            Das wäre aber in meinen Augen umständlich, denn die Class müsste erst _tabelle_ durch $tb_user ersetzen (oder ähnliches).

            Da ja der SQL-Code sowieso in der Klasse steht, mußt du dich im restlichen Programmcode ja gar nicht damit rumschlagen. Die korrekte Ersetzung des Tabellennamens mit Prefix etc. ist allein Sache der Klasse, und diese Information liest sie natürlich aus der globalen Konfiguration aus, die diese Informationen am Besten als echte PHP-Konstante verfügbar macht, denn Konstanten sind automatisch global verfügbar - also auch in der DB-Klasse.

            Zu leicht werden diese zwei Dateien - ggf. unabsichtlich - getrennt und funktionieren einzeln dann nicht mehr wie geplant.
            Wenn man aber bestimmte Config Dateien Variable halten muss, dann wird das nicht funktionieren.

            Ich hab nicht behauptet, dass der Code-Analysator von Zend supertoll ist. Wie gesagt, ich benutze gar keinen, weil ich das nicht brauche.

            Analysiert man per Zend-Code-Analyse z.B. die profile.php (2600 Zeilen) vom vBB 3.x, so erhält man fast 200 Meldungen.
            Bei der admincp/user.php (2200 Zeilen) sind es sogar fast 500 Melunden.
            Ich glaube kaum das man dort von "schlampiger" Programmierung seitens vBulletin sprechen kann.

            Naja, viele dieser Boards sind tatsächlich schlampig programmiert, weil sie schlampig begonnen wurden, und heutzutage niemand mehr gegen diesen gewachsenen Code ankommt, um dort vernünftige Programmierrichtlinien unterzubringen. Und einen Start von Null zu wagen ist eben auch extrem aufwendig.

            Die übliche Vorgehensweise wäre, zu Beginn

            $fehler = "";

            
            > Das ist mir ja auch alles bekannt, aber bei ~1000 Zeilen Code kann es passieren, sollte aber nicht, dass eine Variable durchrutsch.  
              
            Und deswegen gibts eben gewisse Dinge, die man unbedingt tun sollte. Register\_globals auf off und beim Entwickeln alle Meldungen an, auch Notice.  
              
            
            > Und damit es kein folgen schweren Fehler gibt, sollte eine Maschine den Code gegenprüfen.  
              
            Wie schon erwähnt: Es gibt tausende Möglichkeiten, in seinen Code Müll einzubauen, den sogar Menschen nicht erkennen.  
              
            Und wirklich beweisbar sicheren Code zu schreiben, ist sehr teuer und wird nur in den üblichen Ausnahmefällen wie bei Atomkraftwerken, in der Flugsicherheit oder der Raumfahrt eingesetzt.  
              
             - Sven Rautenberg
            
            -- 
            My sssignature, my preciousssss!