|
Allegro CL version 11.0 |
The cross-reference facility provides the ability to analyze compiled code, finding out information such as which functions call which other functions and what global variables are used. The cross-referencer maintains a database of cross-reference information which can be queried by the user to provide answers to questions like 'who calls function foo?'
The cross-referencer functions are in the cross-reference
package (nicknamed xref
). Unless otherwise indicated, all the symbols defined in this chapter are exported from the cross-reference
package. Users must either use the qualifier xref:
on most symbols naming xref functionality or evaluate (use-package :xref)
to make exported symbols accessible without the package qualifier.
The code for the cross-referencer is not contained in the basic Allegro CL image. It is loaded only when needed. Executing any of the user-visible cross-reference functions will cause the correct module to be loaded, but you can ensure that the code is loaded by evaluating the following form: (require :xref)
.
Lisp contains a database of cross-reference information. This database can be queried with the functions and commands described in General description of query functions and commands. The next several headings describe how information is added and removed from the database. What is in and how to access the xref database below describes what information is available and how it can be accessed.
Cross reference information is only generated by the compiler. For that reason, no cross reference information is available for interpreted functions and loading a fasl (compiled lisp) file which does not contain cross reference information does not add information to the cross reference database. (To get cross-reference information, therefore, compile or recompile the source file appropriately, as described next, and load the new fasl file.)
The general rule is that the information is generated when the value of the variable *record-xref-info* is true. However, in certain cases the specific value of that variable can be overridden locally as we describe next.
At the top-level: for functions and macros compiled at the top-level, cross reference information will be generated and immediately stored in the cross reference database if the value of *record-xref-info* is true or if the compilation is done in the body of the macro with-xref.
In an Emacs buffer: compilations done by forms transmitted to Lisp from an Emacs buffer via the Emacs-Lisp interface are like top-level forms with respect to generation of cross-reference information.
In a file: when a Lisp source file is compiled, cross-reference information is generated if the value of the :xref
keyword argument to compile-file is true. The default value of the variable is the value of *record-xref-info* but you may override that value when calling compile-file by explicitly specifying a value for the :xref
keyword argument (or also by calling compile-file within the body of the with-xref macro). Note that the :cf and :cload top-level commands do not accept keyword arguments so whether or not cross-reference information is generated is controlled by *record-xref-info*. The cross-reference information generated for a file is stored in the resulting fasl (compiled lisp) file and cannot be added to the database until the file is loaded.
Even if cross-reference information is generated when a file is compiled, it is never added to the cross-reference database until the file is loaded. Further the cross-reference information is loaded only if the value of *load-xref-info* is non-nil
or the load is done within the body of the macro with-xref.
Note that there is no argument to load that specifies whether cross-reference information should or should not be loaded. The command analogs to load (:ld and :cload also load or do not load xref information as *load-xref-info* is true or false.
Of course, if you have a fasl file with xref info and you load it with *load-xref-info* nil
, you can load it again with that variable true and the xref information will be added to the database.
When you change and recompile a file for which cross-reference information exists in the database, you should be sure to load the revised compiled file with *load-xref-info* true in order to update the database. Warning: if you do change a file for which xref information exists in the database, you must compile it so that xref information is generated and load it so that xref information is loaded or the database will become out of date.
Since macros are expanded by the compiler, the function calls into which a macro form is expanded appear even though they are not evident in the source. Further, because the compiler will transform certain functions into macros (using either the public facility define-compiler-macro or internal Allegro CL-specific functionality), some calls that you expect to be function calls turn out to be macro calls.
Here is a simple example using the utilities in this chapter. Note that the function bar calls the function *record-source-file-info*.
Look what happens when bar is compiled.
10): (xref:start-xref)
USER(
T11): (defun bar ()
USER(
(excl:record-source-file 'foo:type :operator))
BAR12): (compile 'bar)
USER(
BAR
NIL
NIL13): (xref:who-calls 'bar :inverse t)
USER(
BAR directly calls:-1
EXCL::RECORD-SOURCE-FILEthe database
No indirect callees of BAR were found in
BAR calls as a macro:
RECORD-SOURCE-FILE14): USER(
*record-source-file-info* was converted by the compiler to a macro call that expanded to forms that included a call to the internal function :record-source-file-1. This behavior is intended since real (rather than apparent) cross-reference information is being reported. However, you may be surprised by this behavior the first time you see it.
The cross-reference database contains information about the relations between different parts of code. There is a single database that holds all information. This database itself does not store file information: that means that after a fasl file and its associated xref information is loaded, the fact that the information came from that fasl file is forgotten. However, if source file information is collected and stored (as described in source-file-recording.html), that information can be used to provide file-specific cross-reference data, as we describe below.
The information stored deals with which operators (functions or macros) call or are called by what other operators and which operators use (in a fashion we will describe) global variables and which global variables are used by operators. No information is stored relating to references to or uses of constants, that is symbols whose value is defined in a defconstant form or for which constantp returns true.
Specifically, the set of relations between program parts that the cross-referencer records contains the following information:
:direct-calls
- this is true if one function directly calls another. Thus foo directly calls bar in the following case:
defun foo () (bar)) (
:indirect-calls
- this is true if one function contains an indirect reference to another, by, for example, referring to its function object. Thus foo indirectly calls bar in the following case:
defun foo (x) (apply #'bar x)) (
:macro-calls
- this is true if a function calls a macro.
:calls
- this is true if any of :direct-calls
, :indirect-calls
, or :macro-calls
are true.
:references
- this is true when a function makes reference to a global variable.
:binds
- this is true when a function binds a global variable.
:sets
- this is true when a function sets the value of a global variable.
:uses
- this is true when a function :references, :binds, or :sets a global variable.
Information in the database can be retrieved with a number of functions and top-level commands. Many functions have top-level analogs. In addition, the function get-relation is available for programmatic querying of the database.
The function discard-all-xref-info clears all stored information in the database.
There are query functions, query top-level commands, and a programmatic function (that returns rather than prints information and so can be used within a program). We list them here.
The following functions ask specific questions to the database (all are named by symbols in the xref
package but we suppress the package qualifier in the list for clarity). We have divided the functions into two groups: the calls group (which asks who calls functions and macros) and the uses group (which ask who uses global variables). We give the top-level command equivalent where it exists.
Calls group:
Uses group:
who-binds (and :who-binds)
Each of these functions (and equivalent top-level commands) takes either a function name or an object as its first argument and also accepts three keyword arguments: :inverse
, :in-files
, and :in-functions
. We describe these arguments under the next series of headings.
Note that the macros-called-by function asks its question in the reverse way than the other calls questions: the rest ask for all the functions that call a particular function. macros-called-by asks for all macros called by a particular function. We have provided the question that we believe will be most frequently asked in each case. As we describe below, the sense of a question can be reversed by the :inverse
keyword argument.
There are two other query functions: xref-describe and get-relation. xref-describe is a shorthand for calling who-calls and who-uses. get-relation returns the information (either nil
or a true value or a list) about the relation between specified objects. In contrast, the regular query functions print information but do not return anything. get-relation is useful for programs which wish to use cross reference data.
In general, the first (required) argument to the query functions (and commands) is a function name or an object (typically a global variable) according to what would make sense if the function name and the first argument were an English sentence. Thus, the who-calls function takes a function name as its first argument since calling the function asks the question 'who calls [function]' while who-uses takes an object (typically a global variable) since 'who uses [variable]' is the question asked by that function.
The :inverse
keyword argument acts (as we describe below) to reverse the sense of the question. Thus who-calls with :inverse
true transforms the question to 'who is called by [function]'. In any of the call queries, :inverse
being true still requires a function name as the first argument. In the uses queries, however, :inverse
true requires the first argument to be a function name. who-uses with :inverse
true asks the question 'what is used by [function]' while (as we said above) with :inverse
nil
it asks 'who uses [object]'.
A function name is either a symbol or a list. Note that when using the top-level command equivalents of query functions, you do not put a quote before the symbol or list naming the function.
An object can be any Lisp object but is typically a global variable. Cross reference information is only stored for global variables so no information will be printed for other types of objects. However, we permit other objects in order to avoid unnecessary errors and to allow for easy extension in later releases.
File information is available with the cross referencer only if source file information is recorded at the time files are compiled (and cross reference information is generated). In general, source file information will be recorded when both *record-source-file-info* and *load-source-file-info* are true. (See source-file-recording.html for a complete description of source file information.)
The value of the :in-files
argument should be either nil
(the default) or a list of files that have been loaded into Lisp (the list may contain the keyword :top-level
for functions defined directly to the Lisp top-level). If the value is nil
, any information in the database is provided. If the value is a list of files (perhaps with :top-level
), only functions defined in those files are considered when the database is searched.
For example, suppose we have the following files:
File foo.cl:
defun foo () (bar)) (
File baz.cl:
defun baz () (bar)) (
And we define at the top-level:
defun bar nil nil)
(defun hoo () (bar)) (
We compile and load all the files with *record-source-file-info*, *load-source-file-info*, *record-xref-info*, and *load-xref-info* all true. We also compile the top-level functions. Then, the form
(xref:who-calls 'bar)
prints the information that foo, baz, and hoo all call bar. However,
"foo") (xref:who-calls 'bar :in-files '(
prints only that foo calls bar while
"hoo" :top-level)) (xref:who-calls 'bar '(
prints that baz and hoo call bar.
The filenames should be specified in the usual way, that is they can be strings, symbols, or pathname objects. The cl type (extension) will be added by default if it is not supplied (as in our example).
All the query functions including xref-describe and get-relation take this keyword argument.
The value of :in-functions
keyword argument should be nil
or a list of function names. If the value is nil
, the entire database is searched and all information is printed. If the value is a list of functions, the information from the database is intersected with the list of functions and only those functions in the intersection are printed.
When you are asking for which global variables a function uses (i.e. with who-uses and :inverse
true), the list which is the value of :in-functions
can contain the names of global variables and in that case, only the global variables called and in the supplied list will be printed.
For example, we define the following globals and functions:
defvar *var1* 10)
(defvar *var2* 20)
(defun foo (x) (+ x *var1*))
(defun bar () (+ *var1* *var2*)) (
Now,
(who-uses '*var1*)
prints that foo and bar use *var1*
, while
(who-uses '*var1* :in-functions '(foo))
prints that foo uses *var1*
.
t) (who-uses 'bar :inverse
prints the globals used by bar, that is *var1*
and *var2*
. However,
t :in-functions '(*var1*)) (who-uses 'bar :inverse
prints that bar uses *var1*
since the list is restricted by the :in-functions
keyword argument.
All the query functions including xref-describe and get-relation take this keyword argument.
This argument can be used to change the sense of the question asked by the query function. In the case of the call queries, specifying :inverse
true changes the question from who calls to who is called by. In the case of the use queries, specifying :inverse
true changes the question from who uses a global variable to what global variables does a particular function use (and thus changes the first argument from being an object - typically a global variable - to being a function name).
Here are the query functions and the question asked when :inverse
is t.
who-directly-calls: who is directly called by [arg]?
who-indirectly-calls: who is indirectly called by [arg]?
macros-called-by: who macro calls [arg]
who-calls (and :who-calls): who is called by [arg]?
who-binds (and :who-binds): what is bound by [arg]?
who-references (and :who-references): what is referenced by [arg]?
xref-describe: what is called or used by [arg]?
Note that the macros-called-by function asks its question in the reverse sense to the other functions. It asks directly for all macros called by a function and for all functions which call a macro when :inverse
is true. We have chosen for the direct call what we believe will be the most frequently asked question.
All query functions except get-relation accept the :inverse
keyword argument. (In get-relation, the sense of the question is determined by the order of the arguments and so the sense can be reversed by reversing the order.)
Let's say we have a file called example.cl containing the following:
defvar var1 3)
(+ var1 3)
(defmacro addit (x) `(+ var1 ,x))
(defun calladdit (x y) (expt (addit x) y))
(defun call2 (a b) (calladdit a b))
(defclass fooclass ()
(:name :reader foo-name)
((name :initarg
(barg :initarg :barg :accessor get-foo-barg)))defgeneric blarf (x)
(
)defmethod blarf ((x t))
(car x))
(defmethod blarf ((x fooclass))
(
(get-foo-barg x)
(foo-name x)setf var1 3)
( var1)
Then in Allegro CL, we enable the cross-referencer:
user(4): (xref:start-xref)
t
Then compile and load the example file (the compiler messages have been deleted below):
user(5): :cload example.cl
Now we can examine the code with cross-referencer. The output is a list of function-names of forms that reference var1
. The (:top-level-form "example.cl")
corresponds to the forms (defvar var1 3)
and (+ var1 3)
in the source file that reference var1
.
user(6): (xref:who-references 'var1)
var1 is referenced by:
calladdit"example.cl")
(:top-level-form method blarf (fooclass)) (
Now we try who-calls. Note that the call to the addit macro has been expanded so we see that calladdit calls +.
user(12): (xref:who-calls '+)
+ is directly called by:
calladdit"example.cl")
(:top-level-form
+ were found in the database
No indirect callers of + were found in the database No macro callers of
Now let's look at the callers of calladdit.
user(13): (xref:who-calls 'calladdit)
calladdit is directly called by:
call2
the database
No indirect callers of calladdit were found in the database No macro callers of calladdit were found in
We can also see who calladdit calls by asking for the inverse relation. Here we can see the macro invoked by calladdit.
user(14): (xref:who-calls 'calladdit :inverse t)
calladdit directly calls:+
expt
the database
No indirect callees of calladdit were found in
calladdit calls as a macro: addit
We can see all the relevant information about calladdit at once with xref-describe.
user(15): (xref:xref-describe 'calladdit)
calladdit is directly called by:
call2
the database
No indirect callers of calladdit were found in the database
No macro callers of calladdit were found in
calladdit directly calls:
+
expt
the database
No indirect callees of calladdit were found in
calladdit calls as a macro:
addit
calladdit references:
var1
the database
No symbols bound by calladdit were found in set by calladdit were found in the database No symbols
We can also use top-level commands to access cross-reference information.
user(9): :who-calls expt
expt is directly called by:
calladdit
expt were found in the database
No indirect callers of expt were found in the database No macro callers of
Notice the use of a CLOS function-name to obtain information about one of the methods defined for the generic function blarf.
user(22): :who-calls (method blarf (fooclass)) :inverse t
method blarf (fooclass)) directly calls:
(
foo-name
get-foo-bargmethod blarf (fooclass)) were found in the database
No indirect callees of (method blarf (fooclass)) were found in the database No macro callees of (
The low-level programmer's interface to the cross-referencer is through the function get-relation. It can take :wild
as a wildcard argument for queries of the database. The next example returns a list of all forms that call +.
user(23): (xref:get-relation :calls :wild '+)
"example.cl")) (calladdit (:top-level-form
get-relation can be called without a wildcard to determine the truth of a relation. It returns true if the relation exists in the database.
user(27): (xref:get-relation :calls 'calladdit 'addit)
addituser(28): (xref:get-relation :calls 'calladdit 'notaddit)
nil
Copyright (c) Franz Inc. Lafayette, CA., USA. All rights reserved.
|
Allegro CL version 11.0 |