| Allegro CL version 10.1 Unrevised from 10.0 to 10.1. 10.0 version |
This document contains the following sections:
1.0 Cross reference introductionThe 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 Section 2.2 General description of query functions and commands. The next several headings describe how information is added and removed from the database. Section 2.0 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.
*record-xref-info*
is true or if the compilation is done in the body of the macro with-xref.
: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 xref: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 xref: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. Look what happens when bar is compiled.
USER(10): (xref:start-xref) T USER(11): (defun bar () (excl:record-source-file 'foo :type :operator)) BAR USER(12): (compile 'bar) BAR NIL NIL USER(13): (xref:who-calls 'bar :inverse t) BAR directly calls: EXCL::RECORD-SOURCE-FILE-1 No indirect callees of BAR were found in the database BAR calls as a macro: RECORD-SOURCE-FILE USER(14):
record-source-file 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.htm), 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 xref:get-relation is available for programmatic querying of the database.
The function xref: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:
who-directly-calls who-indirectly-calls macros-called-by who-calls (and :who-calls)
Uses group:
who-binds (and :who-binds) who-references (and :who-references) who-sets (and :who-sets) who-uses (and :who-uses)
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.htm
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,
(xref:who-calls 'bar :in-files '("foo")
prints only that foo calls bar while
(xref:who-calls 'bar '("hoo" :top-level))
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*
.
(who-uses 'bar :inverse t)
prints the globals used by bar, that is
*var1*
and *var2*
. However,
(who-uses 'bar :inverse t :in-functions '(*var1*))
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]?
who-sets (and :who-sets): what is set by [arg]?
who-uses (and :who-uses): what is used 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 xref: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 :initarg :name :reader foo-name) (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 (:top-level-form "example.cl") (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 (:top-level-form "example.cl") No indirect callers of + were found in the database No macro callers of + were found in the database
Now let's look at the callers of calladdit.
user(13): (xref:who-calls 'calladdit) calladdit is directly called by: call2 No indirect callers of calladdit were found in the database No macro callers of calladdit were found in the database
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 No indirect callees of calladdit were found in the database 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 No indirect callers of calladdit were found in the database No macro callers of calladdit were found in the database calladdit directly calls: + expt No indirect callees of calladdit were found in the database calladdit calls as a macro: addit calladdit references: var1 No symbols bound by calladdit were found in the database No symbols set by calladdit were found in the database
We can also use top-level commands to access cross-reference information.
user(9): :who-calls expt expt is directly called by: calladdit No indirect callers of expt were found in the database No macro callers of expt were found in the database
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-barg No indirect callees of (method blarf (fooclass)) were found in the database No macro callees of (method blarf (fooclass)) were found in the database
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 '+) (calladdit (:top-level-form "example.cl"))
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) addit user(28): (xref:get-relation :calls 'calladdit 'notaddit) nil
Copyright (c) 1998-2022, Franz Inc. Lafayette, CA., USA. All rights reserved.
This page was not revised from the 10.0 page.
Created 2019.8.20.
| Allegro CL version 10.1 Unrevised from 10.0 to 10.1. 10.0 version |