Funktionsbaum bzw. Callgraph

  • Hallo Zusammen,

    ich hab jetzt die Hilfe und das Forum mit ein paar Schlagworten durchsucht, bin aber nicht fündig geworden.

    Gibt es ein Tool (oder evtl. sogar schon ne Funktion in SciTE) welches mir, egal in welcher Form, auflistet, welche meiner Funktionen, welche Funktion aufruft?
    In erster Linie, welche meiner Funktionen welche meine eigenen Funktionen aufruft. Die AutoIt-eigenen sind nicht zwingend notwendig.

    Gibt es sowas?

    Besten Dank und happy computing!
    R@iner

  • Hier wäre ein Ansatz, der aber nicht funktioniert, wenn das Script schon eine .exe ist.

    Außerdem muss das Script folgendes Format vorweisen:

    [autoit]

    ; Includes

    [/autoit][autoit][/autoit][autoit]

    ; Eigener Code (Programmablauf, Hauptschleife usw.)

    [/autoit][autoit][/autoit][autoit]

    ; (Eigene) Funktionen

    [/autoit]


    Spoiler anzeigen
    [autoit]

    #include <Array.au3>
    _Funktion2("Test") ; -> funktion2
    _Funktionnummer3() ; -> funktion3 -> funktion2
    _Funktion1() ; ruft -> funktion1 -> funktion3 -> funktion2

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Func _Funktion1($iSLN = @ScriptLineNumber)
    ConsoleWrite("_Funktion1" & @TAB & "Aufgerufen von: " & _GetParentFunktion($iSLN) & @CRLF)
    _Funktionnummer3()
    EndFunc ;==>_Funktion1

    [/autoit] [autoit][/autoit] [autoit]

    Func _Funktionnummer3($Parameter1 = "Def", $Param2 = "Test", $iSLN = @ScriptLineNumber)
    ConsoleWrite("_Funktion3" & @TAB & "Aufgerufen von: " & _GetParentFunktion($iSLN) & @CRLF)
    _Funktion2("Testen wir mal!")
    EndFunc ;==>_Funktionnummer3

    [/autoit] [autoit][/autoit] [autoit]

    Func _Funktion2($iParameter, $iSLN = @ScriptLineNumber)
    ConsoleWrite("_Funktion2" & @TAB & "Aufgerufen von: " & _GetParentFunktion($iSLN) & @CRLF)
    EndFunc ;==>_Funktion2

    [/autoit] [autoit][/autoit] [autoit]

    Func _GetParentFunktion($iSLN)
    If @Compiled Then Return "Unable to retrieve information when Script is compiled"
    $aFunc = StringRegExp(FileRead(@ScriptFullPath), '(?m)\A(?>.*\r\n){1,' & $iSLN & '}Func (\w+)\(.*\)', 1)
    If @error Then Return "MainScript"
    Return $aFunc[0]
    EndFunc ;==>_GetParentFunktion

    [/autoit]

    vielleicht hilft dir das ja ein wenig weiter.

  • Hallo SEuBo,

    dank Dir für Deine Antwort, aber das hilft mir bei meinem großen Script leider aus mehreren Gründen nicht weiter, denn

    - ich müßte jede Funktion anpacken (geschätzt derzeit 250 Funktionen ohne includes) und diese um den Parameter LineNumber und den ConsoleWrite inkl._GetParentFunktion zu erweitern
    - bei derzeit 38.681 Zeilen (kein Schreibfehler!) au3-Script (natürlich auch einiges an Kommentaren dabei) wird das sehr lange dauern, denn die Funktion _GetParentFunktion liest bei jedem Aufruf mit FileRead das Script komplett ein
    - komme ich beim Programm-Durchlauf nicht immer an allen Funktionen "vorbei/durch"
    - habe ich teilweise in Kommentaren auch die Funktionsnamen stehen, was evtl. das Such-Ergebnis des StringRegExp in _GetParentFunktion verfälschen könnte

    Ne, es müßte schon ein Programm sein, was mein Script einliest, alle Kommentare rauswirft, alle Funktionsdefinitionen sammelt und dann jede Funktion untersucht, inkl. Hauptteil, welche Funktion von wem ausgerufen wird und dann diese gesammelten Information irgendwie ausgibt oder in eine Datei rausschreibt.

    So ähnlich stell ich mir die Funktion des Obfuscators vor, denn der muss ja auch erst das Script nebst Includes einlesen und untersuchen, dann alle Funktionen und Variablen umbenennen (dazu muss er ja wissen, wer was aufruft) und ein neues obfusciertes au3 draus machen.

    Apropos Obfuscator. Mein obfusciertes Script hat noch 21.628 lines of code.

    Besten Dank und viele Grüße!
    R@iner

  • Hey,

    Ich weiß ja, dass die Lösung nicht optimal ist, allerdings haben sich an dieser Frage schon einmal die großen AutoIt Geister hier geschieden - Leider auch dort ohne Ergebnis.
    https://autoit.de/index.php?page=Thread&amp;threadID=21035

    Zitat

    - ich müßte jede Funktion anpacken (geschätzt derzeit 250 Funktionen ohne includes) und diese um den Parameter LineNumber und den ConsoleWrite inkl._GetParentFunktion zu erweitern


    Das sollte ja nicht das Problem sein, wir sind ja alle fleißige Programmierer ;)

    Spoiler anzeigen
    [autoit]

    Local $sFilePath, $sFileRead
    $sFilePath = FileOpenDialog("Script auswählen", "", "Au3 Files (*.au3)")
    If @error Then Exit

    [/autoit] [autoit][/autoit] [autoit]

    $sFileRead = FileRead($sFilePath)
    $sFileRead = StringRegExpReplace($sFileRead, '(?m)^(Func (\w+)\((?:.(?!iSLN = @ScriptLineNumber))+)\)', '\1, $iSLN = @ScriptLineNumber)' & @CRLF & @TAB & 'ConsoleWrite("\2" & @TAB & "Aufgerufen von: " & _GetParentFunktion($iSLN) & @CRLF)')
    $sFileRead = StringRegExpReplace($sFileRead, '(?m)^(Func (\w+)\()\)', '\1$iSLN = @ScriptLineNumber)' & @CRLF & @TAB & 'ConsoleWrite("\2" & @TAB & "Aufgerufen von: " & _GetParentFunktion($iSLN) & @CRLF)')
    $sFileRead &= @CRLF & @CRLF & _
    'Func _GetParentFunktion($iSLN)' & @CRLF & _
    @TAB & 'If @Compiled Then Return "Unable to retrieve information when Script is compiled"' & @CRLF & _
    @TAB & 'Local Static $sRead = FileRead(@ScriptFullPath)' & @CRLF & _
    @TAB & 'Local $aFunc = StringRegExp($sRead, "(?m)\A(?>.*\r\n){1," & $iSLN & "}Func (\w+)\(.*\)", 1)' & @CRLF & _
    @TAB & 'If @error Then Return "MainScript"' & @CRLF & _
    @TAB & 'Return $aFunc[0]' & @CRLF & _
    'EndFunc ;==>_GetParentFunktion' & @CRLF

    [/autoit] [autoit][/autoit] [autoit]

    $sFilePath = StringRegExpReplace($sFilePath, '(.+)\.au3$', '\1_changed.au3')
    FileDelete($sFilePath) ; Keine Sorge, nur die *_changed.au3 wird gelöscht, nicht die originale.
    FileWrite($sFilePath, $sFileRead)

    [/autoit]
    Zitat

    - bei derzeit 38.681 Zeilen (kein Schreibfehler!) au3-Script (natürlich auch einiges an Kommentaren dabei) wird das sehr lange dauern, denn die Funktion _GetParentFunktion liest bei jedem Aufruf mit FileRead das Script komplett ein


    Auch das lässt sich beheben, indem die Funktion deinen Quelltext als Static Variable speichert. Die Scriptdatei wird dann nur beim ersten Funktionsaufruf eingelesen.

    Zitat

    - komme ich beim Programm-Durchlauf nicht immer an allen Funktionen "vorbei/durch"


    Ehrlich gesagt verstehe ich diesen Satz nicht - Nochmal für Dumme, bitte.

    Zitat

    - habe ich teilweise in Kommentaren auch die Funktionsnamen stehen, was evtl. das Such-Ergebnis des StringRegExp in _GetParentFunktion verfälschen könnte


    Das sollte eigentlich kein Problem werden. Wenn doch - lässt es sich immer noch anpassen.

    Zitat

    Ne, es müßte schon ein Programm sein, was mein Script einliest, alle Kommentare rauswirft, alle Funktionsdefinitionen sammelt und dann jede Funktion untersucht, inkl. Hauptteil, welche Funktion von wem ausgerufen wird und dann diese gesammelten Information irgendwie ausgibt oder in eine Datei rausschreibt.

    So ähnlich stell ich mir die Funktion des Obfuscators vor, denn der muss ja auch erst das Script nebst Includes einlesen und untersuchen, dann alle Funktionen und Variablen umbenennen (dazu muss er ja wissen, wer was aufruft) und ein neues obfusciertes au3 draus machen.

    Auch das wäre alles machbar. Etwas "fertiges" habe ich aber dafür noch nie gesehen.
    Ein Obfuscator arbeitet da nach einem etwas einfacheren Prinzip. Funktions- oder Variablennamen können mit StringRegExp eingelesen werden, und durch einfaches Suchen/Ersetzen im Quelltext ausgetauscht. Viel "geparsed" wird da nicht wirklich. Zumindest war das bei dem Obfuscator, den ich vor Ewigkeiten mal geschrieben habe, so der Fall. Vielleicht arbeiten andere Obfuscator unterschiedlich - ich weiß es wie gesagt nicht.

  • Hello again,

    ok, ich muss es nochmal anders erklären, damit mein Anliegen besser verstanden wird.

    Da mein Script mittlerweile exakt 277 Funktionen umfasst (ermittelt mit "type MeinScript.au3 | findstr /B /i /c:Func"), würde ich gerne die ein oder andere Funktion erweitern/vereinfachen/ersetzen.

    Mittlerweile sind es sehr komplexe Funktionen geworden, sodass ich gerne vorher wüßte, in welchen anderen Funktionen die betreffende Funktion aufgerufen wird, damit ich abschätzen kann, an wie vielen Stellen ich ggf. auch noch nachbessern muß.

    Ich brauche dabei keinen Callbaum mit Tiefe, also wie oft welche Funktion aufgerufen wird. Mir reicht, wenn ich weiß, welche Funktion von welcher Funktion ggf aufgerufen werden kann.

    Da meine Script/Programm ne Gui mit einigen Buttons und Menüs hat, müßte ich, wenn ich Dein Beispiel nehme, alle Funktionen, Buttons in allen Möglichkeiten mindestens einmal durchspielen, damit ich ne Aussage bekomme, welche Funktion was aufruft. Das wäre ein großer Aufwand. Lass ich z.B. bei einem Testlauf das Drücken ein oder mehrere Buttons/Menü-Funktionen weg, werden die betreffenden Funktionen nicht aufgerufen und somit auch nicht das "ConsoleWrite("_FunktionXYZ" & @TAB & "Aufgerufen von: " & _GetParentFunktion($iSLN) & @CRLF)" ausgeführt.

    Daher suche ich eher einen "passiven" Ansatz, also daß das au3-Script eingelesen und "analysiert" wird, welche Funktion von welcher Funktion aufgerufen wird oder werden könnte. Super wäre es auch noch, wenn das gleiche mit den globalen Variablen machbar wäre, also welche globale Variable von welcher Funktion benutzt wird.

    Nice to have wäre es dann noch, wenn auch der Hauptteil noch untersucht wird. Denn wenn eine Funktion weder von einer anderen Funktion, noch im Hauptteil aufgerufen wird, ist es schlichtweg eine tote Funktion und kann entfernt werden. Das gleiche gilt für globale Variablen. Ich bin mir zwar noch sicher, dass das in meinem Script noch nicht der Fall ist, aber bei soviel Funktionen verliert man leicht den Überblick.

    Ich hoffe, es ist jetzt verständlicher, oder soll ich es anhand eines Beispielscripts deutlicher machen?

    Und ja, Du hast wahrscheinlich Recht, dass ein Obfuscator einfacher arbeitet, als ich komplizierterweise gedacht habe :)

    Vielen Dank und happy computing!
    R@iner

  • Also hattest du sowas im Sinn?

    Spoiler anzeigen
    [autoit]

    #include <Array.au3>
    #include <GUIConstantsEx.au3>
    #include <WindowsConstants.au3>
    #include <GUITreeView.au3>
    Local _
    $sFilePath, $sFileRead, $aFunction, _
    $aCallTree, $aTemp_Arr, $aTempArr2, _
    $iUbound

    [/autoit] [autoit][/autoit] [autoit]

    ; Datei einlesen
    $sFilePath = FileOpenDialog("Script auswählen", "", "Au3 Files (*.au3)")
    If @error Then Exit
    $sFileRead = FileRead($sFilePath)

    [/autoit] [autoit][/autoit] [autoit]

    ; Funktionsnamen und -inhalt auslesen
    $aFunction = _RegExp($sFileRead, '(?ims)^Func (\w+)\([^)]*\)(.+?)^EndFunc')

    [/autoit] [autoit][/autoit] [autoit]

    ; Hauptteil von Kommentaren und funktionen befreien.
    $sFileRead = StringRegExpReplace($sFileRead, '(?m)(^Func (\w+)\(.*\)\s*((?:.+\s+)*?)EndFunc.*)', '')
    $sFileRead = StringRegExpReplace($sFileRead, '(?s)#cs(?:.)+#ce|(?m-s)(?:^;.+|;[^"''\r\n]+)', '')

    [/autoit] [autoit][/autoit] [autoit]

    ; Array mit Funktionen vorbereiten und Hauptteil einfügen
    $iUbound = UBound($aFunction) + 1
    _Array2DInsert($aFunction, 0)

    [/autoit] [autoit][/autoit] [autoit]

    $aFunction[0][0] = "%% MAIN SCRIPT %%"
    $aFunction[0][1] = $sFileRead

    [/autoit] [autoit][/autoit] [autoit]

    $aCallTree = $aFunction
    ReDim $aCallTree[$iUbound][3]
    $aCallTree[0][2] = $sFileRead

    [/autoit] [autoit][/autoit] [autoit]

    ; Array zusammenstellen
    For $i = 0 To $iUbound - 1
    $aTemp_Arr = $aFunction
    For $j = 0 To $iUbound - 1
    ; Anzahl der Funktionsaufrufe anderer Funktionen zählen
    StringReplace($aFunction[$i][1], $aTemp_Arr[$j][0] & "(", $aTemp_Arr[$j][0] & "(")
    $aTemp_Arr[$j][1] = @extended
    Next
    $aCallTree[$i][1] = $aTemp_Arr

    [/autoit] [autoit][/autoit] [autoit]

    For $j = 0 To $iUbound - 1
    ; Anzahl der Funktionsaufrufe in anderen Funktionen zählen
    StringReplace($aFunction[$j][1], $aTemp_Arr[$i][0] & "(", $aTemp_Arr[$i][0] & "(")
    $aTemp_Arr[$j][1] = @extended
    Next
    $aCallTree[$i][2] = $aTemp_Arr
    Next

    [/autoit] [autoit][/autoit] [autoit]

    ; Und anzeigen
    _DisplayTree($aCallTree)

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Func _DisplayTree($aArray)
    Local $hGUI, $hTreeView, $aTemp_Arr, $iUbound, $hLast
    $hGUI = GUICreate("", 400, 400)
    GUICtrlCreateTab(10,0,380,380)
    GUICtrlCreateTabItem("Ruft auf")
    $hTreeView = GUICtrlCreateTreeView(10, 20, 380, 370)

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 0 To UBound($aArray) - 1
    $hLast = GUICtrlCreateTreeViewItem($aArray[$i][0], $hTreeView)
    $aTemp_Arr = $aArray[$i][1]
    For $j = 1 To UBound($aArray) - 1
    GUICtrlCreateTreeViewItem($aTemp_Arr[$j][0] & " - " & $aTemp_Arr[$j][1] & "x", $hLast)
    Next
    Next

    [/autoit] [autoit][/autoit] [autoit]

    GUICtrlCreateTabItem("Wird aufgerufen von")
    $hTreeView = GUICtrlCreateTreeView(10, 20, 380, 370)

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 1 To UBound($aArray) - 1
    $hLast = GUICtrlCreateTreeViewItem($aArray[$i][0], $hTreeView)
    $aTemp_Arr = $aArray[$i][2]
    For $j = 0 To UBound($aArray) - 1
    GUICtrlCreateTreeViewItem($aTemp_Arr[$j][0] & " - " & $aTemp_Arr[$j][1] & "x", $hLast)
    Next
    Next
    GUISetState(@SW_SHOW, $hGUI)
    While GUIGetMsg() <> -3
    Sleep(10)
    WEnd
    GUIDelete($hGUI)
    EndFunc ;==>_DisplayTree

    [/autoit] [autoit][/autoit] [autoit]

    Func _Array2DInsert(ByRef $aArray, $iIndex)
    Local $iUB = UBound($aArray, 1), $iUB2 = UBound($aArray, 2) - 1
    ReDim $aArray[$iUB + 1][$iUB2 + 1]

    [/autoit] [autoit][/autoit] [autoit]

    For $i = $iUB To $iIndex + 1 Step -1
    For $j = $iUB2 To 0 Step -1
    $aArray[$i][$j] = $aArray[$i - 1][$j]
    Next
    Next
    EndFunc ;==>_Array2DInsert

    [/autoit] [autoit][/autoit] [autoit]

    Func _RegExp($sTest, $sPattern, $iOffset = 0)
    ; Easy RegExp with >1 Subpattern (SEuBo)
    Local $aRet, $iUB, $iUB2, $aDummy, $aNewArr
    $aRet = StringRegExp($sTest, $sPattern, 4, $iOffset)
    If @error Then Return ConsoleWrite(@extended & @CRLF)

    [/autoit] [autoit][/autoit] [autoit]

    Local $iUB = UBound($aRet), $iUB2 = UBound($aRet[0]), $aNewArr[$iUB][$iUB2 - 1]
    For $i = 0 To $iUB - 1
    Local $aDummy = $aRet[$i], $iUBD = UBound($aDummy)
    ;~ _ArrayDisplay($aDummy)
    If $iUBD - 1 > $iUB2 Then
    $iUB2 = $iUBD
    ReDim $aNewArr[$iUB][$iUB2]
    EndIf
    For $j = 1 To $iUBD - 1
    $aNewArr[$i][$j - 1] = $aDummy[$j]
    Next
    Next
    Return $aNewArr
    EndFunc ;==>_RegExp

    [/autoit]

    StringRegExp ist ne tolle Sache ;)

  • Hallo SEuBo,

    super, vielen Dank für Deine Mühe!

    Vermutlich käme das meinem Wunsch schon nahe, aber Dein Programm zeigt mir nichts an, sondern beendet sich ziemlich schnell, wenn ich mein au3-Script inspizieren lasse. :(

    In der Console steht:

    Code
    >Running:(3.3.6.1):C:\Programme\AutoIt3\autoit3.exe "E:\AutoIT-Programme\WhoCallsWho.au3" 
    !>20:45:03 AutoIT3.exe ended.rc:-1073741819
    >Exit code: -1073741819    Time: 10.381

    Ansonsten kein weitere Meldung oder Fehler.

    Wenn ich Dein Script damit untersuchen lasse, kommt die Gui hoch mit den 2 Tabs.

    Kannst Du mal bitte mehr Debug-Ausgaben einbauen, oder wie können wir den Fehler einkreisen?

    Ja, RegExp kann mächtig sein, wenn man weiß, wie man es anwendet. Ich hab mich damit noch nicht näher beschäftigt, hätte es aber gewiß schon ein paar mal gebrauchen können.

    Dank Dir und happy computing!
    R@iner

    /edit:
    Egal welches ich meiner Scripte damit untersuchen lassen will, es kommt überall der gleiche Exit-Code

    Einmal editiert, zuletzt von skyteddy (20. März 2011 um 21:15)

  • Hi

    Ich hab da auch was für dich:

    Spoiler anzeigen
    [autoit]

    #include <WindowsConstants.au3>
    #include <GUITreeView.au3>

    [/autoit] [autoit][/autoit] [autoit]

    Global $oDict = ObjCreate('Scripting.Dictionary')
    If Not IsObj($oDict) Then
    MsgBox(0, "ERROR", "creating scripting.dictionary object failed")
    Exit
    EndIf
    $oDict.CompareMode = 1

    [/autoit] [autoit][/autoit] [autoit]

    Global $sPath = FileOpenDialog("Script öffnen", "", "(*.au3)")
    If @error Then Exit

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Global $hGui = GUICreate("", 800, 580)
    Global $hTreeView = _GUICtrlTreeView_Create($hGui, 0, 0, 800, 580, BitOR($TVS_EDITLABELS, $TVS_HASBUTTONS, $TVS_HASLINES, $TVS_LINESATROOT, $TVS_DISABLEDRAGDROP, $TVS_SHOWSELALWAYS, $TVS_CHECKBOXES), $WS_EX_CLIENTEDGE)
    GUISetState()

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Global $sScript = FileRead($sPath)
    _RemoveComments($sScript)
    $sScript = StringRegExpReplace($sScript, '["][^"]*["]|' & "['][^']*[']", "")

    [/autoit] [autoit][/autoit] [autoit]

    Global $aRegExp = StringRegExp($sScript, "(?mis)^\h*func\h+\w+\h*\(.*?^\h*endfunc", 3)
    If @error Or Not IsArray($aRegExp) Then Exit
    Global $aFunc[UBound($aRegExp) + 1][3] = [[UBound($aRegExp)]]

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 1 To $aFunc[0][0]
    $aFunc[$i][0] = $aRegExp[$i - 1]
    $aFunc[$i][1] = StringRegExpReplace($aFunc[$i][0], "(?mis)(^\h*func\h+)(\w+)(\h*\(.*?^\h*endfunc)", "$2")
    $aFunc[$i][2] = False
    If Not $oDict.Exists($aFunc[$i][1]) Then $oDict.Add($aFunc[$i][1], $i)
    Next

    [/autoit] [autoit][/autoit] [autoit]

    Global $aTemp, $hItem
    For $i = 1 To $aFunc[0][0]
    $aTemp = $aFunc
    $hItem = _GUICtrlTreeView_Add($hTreeView, 0, $aFunc[$i][1])
    _FindFunctions($aFunc[$i][0], $aFunc[$i][1], $aTemp, $hItem, 1)
    Next

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    _GUICtrlTreeView_Expand($hTreeView)

    [/autoit] [autoit][/autoit] [autoit]

    While GUIGetMsg() <> -3
    Sleep(10)
    WEnd

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Func _FindFunctions($sFunc, $sFuncName, $aFunc, $hParent, $iLvl = 0)
    $sFunc = StringRegExpReplace($sFunc, "(?mis)(^\h*func\h+)(\w+)(\h*\(.*?^\h*)(endfunc)", "$3")
    Local $aFuncCall = StringRegExp($sFunc, "(?<![\w\$@])([a-zA-Z_]\w*)(?:\W)", 3)
    If @error Or Not IsArray($aFuncCall) Then Return
    Local $iIndex, $hItem
    For $i = 0 To UBound($aFuncCall) - 1
    If Not $oDict.Exists($aFuncCall[$i]) Then ContinueLoop
    $iIndex = $oDict.Item($aFuncCall[$i])
    If Not $aFunc[$iIndex][2] Then
    $aFunc[$iIndex][2] = True
    If $aFunc[$iIndex][1] = $sFuncName Then
    $hItem = _GUICtrlTreeView_AddChild($hTreeView, $hParent, $aFunc[$iIndex][1] & " ist rekursiv")
    Else
    $hItem = _GUICtrlTreeView_AddChild($hTreeView, $hParent, $aFunc[$iIndex][1])
    _FindFunctions($aFunc[$iIndex][0], $aFunc[$iIndex][1], $aFunc, $hItem, $iLvl + 1)
    EndIf
    EndIf
    Next
    EndFunc ;==>_FindFunctions

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Func _RemoveComments(ByRef $sScript)
    $sScript = StringRegExpReplace($sScript, '(?x)(?:\r?\n?\s*;.*)|' & '((?>"[^"]*")+|' & "(?>'[^']*')+)", "\1") ;entferne ;-Komments
    $sScript = StringRegExpReplace($sScript, '(?i)(?:[\r\n]\s*)(\#ce|\#comments-end)(.*)', '$1') ;manche #ce haben noch Text dahinter
    $sScript = StringRegExpReplace($sScript, '(?x)(?:\r?\n?\s*;.*)|(?s)\r?\n?\s*\#c(?>s|omments-start)(?>(?>"[^"]*")+|' & "(?>'[^']*')+|.)*?\#c(?>e|omments-end)(?-s)|" & '((?>"[^"]*")+|' & "(?>'[^']*')+)", "\1")
    EndFunc ;==>_RemoveComments

    [/autoit]


    Evtl solltest du _GUICtrlTreeView_Expand($hTreeView) auskommentieren, sonst dauert das unter Umständenen zu lange...


    Edit: Um auch alle #Includes zu berücksichtigen kannst du vorher das hier ausprobieren: Another AutoIt PreProcessor

    E

  • Hallo,

    Habe hier auch nochmal eine leicht geänderte Version - es lag an einem einzelnen StringRegExpReplace, das bei sehr langen Quelltexten abgestürzt ist..

    Spoiler anzeigen
    [autoit]

    #include <Array.au3>
    #include <GUIConstantsEx.au3>
    #include <WindowsConstants.au3>
    #include <GUITreeView.au3>
    Local _
    $sFilePath, $sFileRead, $aFunction, _
    $aCallTree, $aTemp_Arr, $aTempArr2, _
    $iUbound

    [/autoit] [autoit][/autoit] [autoit]

    ; Datei einlesen
    $sFilePath = FileOpenDialog("Script auswählen", "", "Au3 Files (*.au3)")
    If @error Then Exit
    $sFileRead = FileRead($sFilePath)

    [/autoit] [autoit][/autoit] [autoit]

    ; Funktionsnamen und -inhalt auslesen
    $aFunction = _RegExp($sFileRead, '(?ims)^Func (\w+)\([^)]*\)(.+?)^EndFunc')
    ;~ MsgBox(0,"","RegExp")

    [/autoit] [autoit][/autoit] [autoit]

    ; Hauptteil von Kommentaren und funktionen befreien.
    $sFileRead = StringRegExpReplace($sFileRead, '(?ims)(^Func \w+\([^)]*\).+?^EndFunc.*)', '')
    $sFileRead = StringRegExpReplace($sFileRead, '(?s)#cs(?:.)+#ce|(?m-s)(?:^;.+|;[^"''\r\n]+)', '')
    ;~ MsgBox(0,"","RegExpReplace")

    [/autoit] [autoit][/autoit] [autoit]

    ProgressOn("Bitte Warten...", "Analysiere Funktionen")

    [/autoit] [autoit][/autoit] [autoit]

    ; Array mit Funktionen vorbereiten und Hauptteil einfügen
    $iUbound = UBound($aFunction) + 1
    _Array2DInsert($aFunction, 0)
    ;~ MsgBox(0,"","ArrayInsert")

    [/autoit] [autoit][/autoit] [autoit]

    $aFunction[0][0] = "%% MAIN SCRIPT %%"
    $aFunction[0][1] = $sFileRead

    [/autoit] [autoit][/autoit] [autoit]

    $aCallTree = $aFunction
    ReDim $aCallTree[$iUbound][3]
    $aCallTree[0][2] = $sFileRead
    ;~ MsgBox(0,"","ReDim")

    [/autoit] [autoit][/autoit] [autoit]

    ; Array zusammenstellen
    For $i = 0 To $iUbound - 1
    $aTemp_Arr = $aFunction
    ProgressSet($i / ($iUbound - 1) * 100, "Parse " & $aTemp_Arr[$i][0] & "()")
    For $j = 0 To $iUbound - 1

    [/autoit] [autoit][/autoit] [autoit]

    ; Anzahl der Funktionsaufrufe anderer Funktionen zählen
    StringReplace($aFunction[$i][1], $aTemp_Arr[$j][0] & "(", $aTemp_Arr[$j][0] & "(")
    $aTemp_Arr[$j][1] = @extended
    Next
    $aCallTree[$i][1] = $aTemp_Arr

    [/autoit] [autoit][/autoit] [autoit]

    For $j = 0 To $iUbound - 1
    ; Anzahl der Funktionsaufrufe in anderen Funktionen zählen
    StringReplace($aFunction[$j][1], $aTemp_Arr[$i][0] & "(", $aTemp_Arr[$i][0] & "(")
    $aTemp_Arr[$j][1] = @extended
    Next
    $aCallTree[$i][2] = $aTemp_Arr
    Next
    ProgressOff()
    ; Und anzeigen
    _DisplayTree($aCallTree)

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Func _DisplayTree($aArray)
    Local $hGUI, $hTreeView, $aTemp_Arr, $iUbound, $hLast
    $hGUI = GUICreate("", 400, 400)
    GUICtrlCreateTab(10, 0, 380, 380)
    GUICtrlCreateTabItem("Ruft auf")
    $hTreeView = GUICtrlCreateTreeView(10, 20, 380, 370)
    GUISetState(@SW_SHOW, $hGUI)

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 0 To UBound($aArray) - 1
    $hLast = GUICtrlCreateTreeViewItem($aArray[$i][0], $hTreeView)
    $aTemp_Arr = $aArray[$i][1]
    For $j = 1 To UBound($aArray) - 1
    GUICtrlCreateTreeViewItem($aTemp_Arr[$j][0] & " - " & $aTemp_Arr[$j][1] & "x", $hLast)
    Next
    Next

    [/autoit] [autoit][/autoit] [autoit]

    GUICtrlCreateTabItem("Wird aufgerufen von")
    $hTreeView = GUICtrlCreateTreeView(10, 20, 380, 370)

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 1 To UBound($aArray) - 1
    $hLast = GUICtrlCreateTreeViewItem($aArray[$i][0], $hTreeView)
    $aTemp_Arr = $aArray[$i][2]
    For $j = 0 To UBound($aArray) - 1
    GUICtrlCreateTreeViewItem($aTemp_Arr[$j][0] & " - " & $aTemp_Arr[$j][1] & "x", $hLast)
    Next
    Next
    While GUIGetMsg() <> -3
    Sleep(10)
    WEnd
    GUIDelete($hGUI)
    EndFunc ;==>_DisplayTree

    [/autoit] [autoit][/autoit] [autoit]

    Func _Array2DInsert(ByRef $aArray, $iIndex)
    Local $iUB = UBound($aArray, 1), $iUB2 = UBound($aArray, 2) - 1
    ReDim $aArray[$iUB + 1][$iUB2 + 1]

    [/autoit] [autoit][/autoit] [autoit]

    For $i = $iUB To $iIndex + 1 Step -1
    For $j = $iUB2 To 0 Step -1
    $aArray[$i][$j] = $aArray[$i - 1][$j]
    Next
    Next
    EndFunc ;==>_Array2DInsert

    [/autoit] [autoit][/autoit] [autoit]

    Func _RegExp($sTest, $sPattern, $iOffset = 0)
    ; Easy RegExp with >1 Subpattern (SEuBo)
    Local $aRet, $iUB, $iUB2, $aDummy, $aNewArr
    $aRet = StringRegExp($sTest, $sPattern, 4, $iOffset)
    If @error Then Return ConsoleWrite(@extended & @CRLF)

    [/autoit] [autoit][/autoit] [autoit]

    Local $iUB = UBound($aRet), $iUB2 = UBound($aRet[0]), $aNewArr[$iUB][$iUB2 - 1]
    For $i = 0 To $iUB - 1
    Local $aDummy = $aRet[$i], $iUBD = UBound($aDummy)
    ;~ _ArrayDisplay($aDummy)
    If $iUBD - 1 > $iUB2 Then
    $iUB2 = $iUBD
    ReDim $aNewArr[$iUB][$iUB2]
    EndIf
    For $j = 1 To $iUBD - 1
    $aNewArr[$i][$j - 1] = $aDummy[$j]
    Next
    Next
    Return $aNewArr
    EndFunc ;==>_RegExp

    [/autoit]
  • Hallo Ihr Zwei,

    vielen Dank Euch Zwei für Eure Hilfe!

    SEuBo
    Geht leider immer noch nicht :( Es beendet sich noch viel schneller.

    Code
    >Exit code: -1073741819    Time: 5.953

    eukalyptus
    Bei meinen kleineren Scripten funktioniert Dein Script. Allerdings sehe ich dann als Ergebnis, welche Funktion welche weitere aufruft, nicht aber, der umgedrehte Fall, welche Funktion von welcher aufgerufen wird.

    Mein großes Script kann trotzdem noch nicht damit untersucht werden. Da beendet es sich sehr schnell. Es blitzt kurz das Fenster auf, das war's dann.
    Anfänglich dachte ich, dass es vielleicht an der Main-While-Schleife liegt, aber soweit kommt es garnicht.

    Deinen PreProzessor muss ich mir die Woche noch mal anschauen, da ich ein paar eigene UDFs einbinde, wäre der gewiß hilfreich.

    Besten Dank Euch Zwei und happy computing!
    R@iner

  • OK - die umgedrehte Liste war einfach hinzuzufügen:

    Spoiler anzeigen
    [autoit]

    #include <WindowsConstants.au3>
    #include <GUITreeView.au3>
    #include <Array.au3>

    [/autoit] [autoit][/autoit] [autoit]

    Global $oDict = ObjCreate('Scripting.Dictionary')
    If Not IsObj($oDict) Then
    MsgBox(0, "ERROR", "creating scripting.dictionary object failed")
    Exit
    EndIf
    $oDict.CompareMode = 1

    [/autoit] [autoit][/autoit] [autoit]

    Global $sPath = FileOpenDialog("Script öffnen", "", "(*.au3)")
    If @error Then Exit

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Global $hGui = GUICreate("", 800, 580)
    GUICtrlCreateLabel("Ruft auf", 0, 0, 400, 20, 0x01)
    GUICtrlCreateLabel("wird aufgerufen", 400, 0, 400, 20, 0x01)
    Global $hTreeView = _GUICtrlTreeView_Create($hGui, 0, 20, 400, 560, BitOR($TVS_EDITLABELS, $TVS_HASBUTTONS, $TVS_HASLINES, $TVS_LINESATROOT, $TVS_DISABLEDRAGDROP, $TVS_SHOWSELALWAYS), $WS_EX_CLIENTEDGE)
    Global $hTreeView2 = _GUICtrlTreeView_Create($hGui, 400, 20, 400, 560, BitOR($TVS_EDITLABELS, $TVS_HASBUTTONS, $TVS_HASLINES, $TVS_LINESATROOT, $TVS_DISABLEDRAGDROP, $TVS_SHOWSELALWAYS), $WS_EX_CLIENTEDGE)
    GUISetState()

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Global $sScript = FileRead($sPath)
    _RemoveComments($sScript)
    ConsoleWrite("> remove quotes" & @CRLF)
    $sScript = StringRegExpReplace($sScript, '["][^"]*["]|' & "['][^']*[']", "")
    ConsoleWrite("+ done" & @CRLF & @CRLF)

    [/autoit] [autoit][/autoit] [autoit]

    ConsoleWrite("> extract functions" & @CRLF)
    Global $aRegExp = StringRegExp($sScript, "(?mis)^\h*func\h+\w+\h*\(.*?^\h*endfunc", 3)
    If @error Or Not IsArray($aRegExp) Then Exit
    ConsoleWrite("+ done" & @CRLF & @CRLF)
    Global $aFunc[UBound($aRegExp) + 2][3] = [[UBound($aRegExp)]]

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 1 To $aFunc[0][0]
    $aFunc[$i + 1][0] = $aRegExp[$i - 1]
    $aFunc[$i + 1][1] = StringRegExpReplace($aFunc[$i + 1][0], "(?mis)(^\h*func\h+)(\w+)(\h*\(.*?^\h*endfunc)", "$2")
    $aFunc[$i + 1][2] = False
    If Not $oDict.Exists($aFunc[$i + 1][1]) Then $oDict.Add($aFunc[$i + 1][1], $i + 1)
    Next

    [/autoit] [autoit][/autoit] [autoit]

    ConsoleWrite("> extract mainscript" & @CRLF)
    $aFunc[1][0] = StringRegExpReplace($sScript, "(?mis)^\h*func\h+\w+\h*\(.*?^\h*endfunc", "")
    ConsoleWrite("+ done" & @CRLF & @CRLF)
    $aFunc[1][1] = "mainscript"
    $aFunc[1][2] = True
    If Not $oDict.Exists($aFunc[1][1]) Then $oDict.Add($aFunc[1][1], 1)

    [/autoit] [autoit][/autoit] [autoit]

    Global $aCall[$aFunc[0][0] + 1]
    Global $aTemp, $hItem
    For $i = 1 To $aFunc[0][0]
    $aTemp = $aFunc
    $hItem = _GUICtrlTreeView_Add($hTreeView, 0, $aFunc[$i][1])
    ConsoleWrite(@CRLF & "> Next step: " & $i & "\" & $aFunc[0][0] & @TAB & $aFunc[$i][1] & @CRLF)
    _FindFunctions($aFunc[$i][0], $aFunc[$i][1], $aTemp, $hItem, $aCall, 1)
    Next

    [/autoit] [autoit][/autoit] [autoit]

    Global $aSplit
    For $i = 1 To $aFunc[0][0]
    $hItem = _GUICtrlTreeView_Add($hTreeView2, 0, $aFunc[$i][1])
    $aSplit = StringSplit($aCall[$i], ";")
    For $j = 1 To $aSplit[0] - 1
    _GUICtrlTreeView_AddChild($hTreeView2, $hItem, $aSplit[$j])
    Next
    Next

    [/autoit] [autoit][/autoit] [autoit]

    ;_GUICtrlTreeView_Expand($hTreeView)

    [/autoit] [autoit][/autoit] [autoit]

    While GUIGetMsg() <> -3
    Sleep(10)
    WEnd

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Func _FindFunctions($sFunc, $sFuncName, $aFunc, $hParent, ByRef $aCall, $iLvl = 0)
    ConsoleWrite(@CRLF & "-")
    For $i = 1 To $iLvl
    ConsoleWrite(" ")
    Next
    ConsoleWrite("analyzing " & $sFuncName & @CRLF)
    $sFunc = StringRegExpReplace($sFunc, "(?mis)(^\h*func\h+)(\w+)(\h*\(.*?^\h*)(endfunc)", "$3")
    Local $aFuncCall = StringRegExp($sFunc, "(?<![\w\$@])([a-zA-Z_]\w*)(?:\W)", 3)
    If @error Or Not IsArray($aFuncCall) Then Return
    Local $iIndex, $hItem
    For $i = 0 To UBound($aFuncCall) - 1
    If Not $oDict.Exists($aFuncCall[$i]) Then ContinueLoop
    $iIndex = $oDict.Item($aFuncCall[$i])
    If Not $aFunc[$iIndex][2] Then
    $aFunc[$iIndex][2] = True
    If Not StringInStr($aCall[$iIndex], $sFuncName) Then $aCall[$iIndex] &= $sFuncName & ";"
    If $aFunc[$iIndex][1] = $sFuncName Then
    $hItem = _GUICtrlTreeView_AddChild($hTreeView, $hParent, $aFunc[$iIndex][1] & " ist rekursiv")
    Else
    $hItem = _GUICtrlTreeView_AddChild($hTreeView, $hParent, $aFunc[$iIndex][1])
    _FindFunctions($aFunc[$iIndex][0], $aFunc[$iIndex][1], $aFunc, $hItem, $aCall, $iLvl + 1)
    EndIf
    EndIf
    Next
    EndFunc ;==>_FindFunctions

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Func _RemoveComments(ByRef $sScript)
    ConsoleWrite("> remove comments step 1/3" & @CRLF)
    $sScript = StringRegExpReplace($sScript, '(?x)(?:\r?\n?\s*;.*)|' & '((?>"[^"]*")+|' & "(?>'[^']*')+)", "\1") ;entferne ;-Komments
    ConsoleWrite("> remove comments step 2/3" & @CRLF)
    $sScript = StringRegExpReplace($sScript, '(?i)(?:[\r\n]\s*)(\#ce|\#comments-end)(.*)', '$1') ;manche #ce haben noch Text dahinter
    ConsoleWrite("> remove comments step 3/3" & @CRLF)
    $sScript = StringRegExpReplace($sScript, '(?x)(?:\r?\n?\s*;.*)|(?s)\r?\n?\s*\#c(?>s|omments-start)(?>(?>"[^"]*")+|' & "(?>'[^']*')+|.)*?\#c(?>e|omments-end)(?-s)|" & '((?>"[^"]*")+|' & "(?>'[^']*')+)", "\1")
    ConsoleWrite("+ done" & @CRLF & @CRLF)
    EndFunc ;==>_RemoveComments

    [/autoit]

    allerdings weiß ich nicht, weshalb das dein Script nicht schafft...
    lass es mal laufen und schaue, was alles in der Console steht, dann kann man schon etwas genauer einschätzen wo der Hund begraben liegt

    E

    • Offizieller Beitrag

    SEuBo und eukalyptus, bei mir laufen eure Scripte einwandfrei.
    Ich habe zwar kein 39000 Zeilen Script verwendet, aber mit einem 8000 Zeilen Script funzt es einwandfrei.
    Von der Geschwindigkeit her, ist das Script von eukalyptus ungefähr 2 mal so schnell,als das von SeuBo.

    Finde eure Scripte recht nützlich und werde sie bestimmt mal brauchen. ;)

  • Sehr nett, sowas hatte ich auch schon immer vermisst.

    @eucalyptus:

    Dein Script (letzte Version, umgedrehte Liste) stürtzt bei mir mit folgender Meldung ab:

    Code
    F:\#Programmier-Projekte\_forumsupport\Funktionsanalyse\analyseMyScript.AU3 (91) : ==> Array variable has incorrect number of subscripts or subscript dimension range exceeded.:
    If Not StringInStr($aCall[$iIndex], $sFuncName) Then $aCall[$iIndex] &= $sFuncName & ";"
    If Not StringInStr(^ ERROR


    EDIT: Deine erste Variante läuft fehlerfrei.

    SEuBo:

    Deine Variante läuft mit meinen Scripten.
    Verbesserungsvorschläge an dich:
    - Funktionsliste alphabetisch sortieren
    - Nur einträge anzeigen die nicht 0 sind, also alle 0x Funktionsaufrufe ausblenden, da das sonst sehr unübersichtlich wird

    Einmal editiert, zuletzt von misterspeed (23. März 2011 um 13:12)

  • Hallo Zusammen,

    also, ich hab mir das jetzt mal näher angeschaut.

    SEuBo
    Dein Programm stürzt in Zeile 21 "$sFileRead = StringRegExpReplace($sFileRead, '(?s)#cs(?:.)+#ce|(?m-s)(?:^;.+|;[^"''\r\n]+)', '')" ab.

    eukalyptus
    Hurra, Dein Programm läuft jetzt durch, nachdem ich Tidy über mein Programm drüber laufen habe lassen.

    Allerdings stimmt mindestens mal nicht die Analyse von "mainscript" nicht. Da fehlt einiges. Kann das sein, dass sich das Programm an Kommentaren stört?
    Denn im Vorspann schreib ich so einiges, für was welche Funktion ist.

    Ich hätte gerne noch die Funktionsliste sortiert und dachte mir, ich füge einfach die Zeile "_ArraySort($aFunc, 0, 1, 0, 1)" vor die Zeile 52 "For $i = 1 To $aFunc[0][0]", um das nach Funktionsnamen zu sortieren, aber das verändert total das Ergebnis :(

    Wie bekomme ich die Liste sortiert?

    Besten Dank und happy computing!
    R@iner