0

Is there a tool that can analyze which Tcl proc calls which other procs, building a set of call trees?

It would only have to work under the following simplifying assumptions:

  • The name of each proc is fixed, not formed from strings.
  • A proc is only invoked directly or in execution brackets [...].
  • A list of existing procs could be easily obtained from info.

The tool should work by analyzing the source code, not by executing the code.

Such a tool would be helpful for a project with a meanwhile complex structure.

Thanks a lot!

Ralf
  • 1,203
  • 1
  • 11
  • 20
  • 2
    Tricky. In general impossible because you can't be 100% sure if something is code or other data, but you can usually have a very good guess (stuff that parses as code usually is code). Callbacks are the trickiest cases, as those are indirect calls. Don't know of any tool that does it, but I've never looked so I'm really not the best authority on this! – Donal Fellows Jan 27 '22 at 15:22
  • 1
    If a source file has all of its code in procs, and a well defined entry point, I think it would be possible to do with execution traces... But that involves executing, of course. – Shawn Jan 27 '22 at 17:18
  • I just thought about getting the names of all procs from `info commands` (minus the ones defined before the program defined any procs), then the body of each proc from `info body`, and then doing a regexp match for the proc names in the bodies. That might cover direct calls (but not the indirect ones like callbacks). – Ralf Jan 27 '22 at 19:00

1 Answers1

0

Here's my somewhat raw solution for building a call tree. It looks for proc names which appear at the beginning of a line (ignoring leading spaces and tabs) or immediately after an opening square bracket. It has several other limitations, e.g. it doesn't consider multiple tcl commands on the same line (separated by ;), and it also matches proc names if they appear after square brackets inside curly brackets.

The result are two arrays called and isCalledBy which can be used for further analysis.

# https://stackoverflow.com/a/15043787/3852630
proc reEscape {str} {
    regsub -all {\W} $str {\\&}
}

proc buildCallTree {} {
    set procNameList [info procs]
    foreach caller $procNameList {
        puts $caller
        foreach callee $procNameList {
            set escCallee [reEscape $callee]
            set calleeRE [string cat {(^|[^\\]\[)} $escCallee {($|[\s\]])}]
            set callerBodyLines [split [info body $caller] \n]
            foreach callerBodyLine $callerBodyLines {
                set callerBodyLine [string trim $callerBodyLine]
                if {[string index $callerBodyLine 0] != "#"} {
                    if {[regexp $calleeRE $callerBodyLine]} {
                        lappend ::calls($caller) $callee
                        lappend ::isCalledBy($callee) $caller
                        break
                    }
                }
            }
        }
    }
}

Any comments or suggestions for improvements are welcome.


Edit: Code now ignores proc names after escaped opening square brackets.

Ralf
  • 1,203
  • 1
  • 11
  • 20