|
Page 3 of 6
We can now start writing our own queries over the loaded code.
SemmleCode is not like other code navigation tools and
code checkers, which often confine you
to a fixed, pre-defined set of checks. With SemmleCode, you can
easily author new queries of your own, in a special query language
called .QL.
from-where-select
To write a new custom query, select the Quick query window. We are going
to look for classes that define an equals method but not hashCode.
Paste the following text in the Quick query window:
from Class c
where c.declaresMethod("equals") and
not c.declaresMethod("hashCode")
select c.getPackage(), c
Note the syntax highlighting and coloring
that occurs when you've pasted the text: .QL is fully integrated with Eclipse.
The query itself says that we wish to find a Class c that defines a method named "equals" but it does not declare
a method named "hashCode". For any such class c, we wish to
report its package and c itself.
Note the reversal compared to the traditional select...where...from in SQL.
.QL employs definition before use because that enables a better
editing experience.
To run the query, select the query-as-a-tree icon
( ),
which is the leftmost
icon in the title bar of the Quick query window (its shape indicates
a question mark for the query, a triangle for execution, and a tree for the
format of the results):
There will be
eight results reported:
The shape of the tree is determined by the order of elements selected in
the query. We selected first the package, and then the class, so that is
the order used in displaying the result: for each package, we get the
classes in that package that satisfy the query.
Expand the package net.n3.nanoxml in the Tree results view (by clicking
on the icon next to it): it has a
class named XMLElement whose source is included with the JHotDraw
project. Not defining hashCode while defining equals is bad practice:
suppose we have two XMLElements x and y such that x.equals(y)=true but hashCode(x)!=hashCode(y).
Then storing x in a HashSet, and
subsequently asking whether y is contained in that HashSet will erroneously return false.
With SemmleCode, such problems are easy to spot and rectify.
The other seven matches are in fact all in the Java standard
libraries that are referenced by JHotdraw. To exclude them
from the results, just modify the above query to
from Class c
where c.declaresMethod("equals")
and
not(c.declaresMethod("hashCode"))
and
c.fromSource()
select c.getPackage(), c
As you type the extra text "and c.",
you will notice there is content-assist available. Whenever
you would like to have content-assist but it is
not offered automatically at the point where you are editing,
type CTRL+space. Also, as you are editing, there is
continuous checking of your script, with helpful messages when
problems occur.
When you press the query-as-a-tree icon
( )
again, you now get only
one result. Tailoring queries to specific circumstances is as
easy as that.
Aggregates (sum, count, average, max, min)
Apart from retrieving tuples, you can also write .QL queries to
compute numbers. For instance, let's compute the size of each package
in JHotDraw: the sum of the number of lines in the compilation
units that are contained in the package. Here is the query:
from Package p, int loc
where p.getName().matches("org.jhotdraw%")
and
loc = sum(CompilationUnit cu | cu.getPackage() = p | cu.getNumberOfLines())
select p, loc order by loc desc
In words, our query ranges over packages p and integers loc (loc for Lines Of Code). The where clause states that the name of p must begin with "org.jhotdraw". Furthermore, to obtain the lines of code,
we sum over all compilation units (the bit saying "CompilationUnit cu")
whose package is p (expressed by "cu.getPackage()=p"),
taking the lines of code for each compilation unit (cu.getNumberOfLines()).
Finally, we wish to display the tuples (p,loc) in decreasing order
of loc, so the largest package comes first.
You could hit the query-as-a-tree button once again,
but just having a list is a bit boring. So let's be adventurous, and
instead select Query as a chart (indicated by the icon
( ).
This is in fact a little drop-down menu: select as a pie chart. To view the result nicely,
you may have to drag your result window to give it a bit more space:
We shall further discuss how you can format your results below.
You can also directly select an aggregate. To illustrate, here is
a query for counting the number of classes in JHotDraw packages:
select count(Class c | c.getPackage().getName().matches("org.jhotdraw%"))
There are no fancy ways of displaying a single number, so run it with
the Query-as-a-tree button; the result is 484.
In general, the notation for aggregates is
aggfunc(declarations | condition | value)
Here aggfunc is one of count, sum, max, min or avg (for average). The declarations are just typed variables, and the value captures the numbers we are
counting, summing, minning, maxing, or averaging. Note the analogy between
the from...where...select syntax of general queries, and the .QL
notation for aggregates.
It is rather common to have only one
variable declaration, and that same variable then also occurs in the value position. In that case the value item is optional,
so our last query was just shorthand for the full form:
select count(Class c | c.getPackage().getName().matches("org.jhotdraw%") | c)
We conclude our discussion of aggregates with an example illustrating a more complex aggregate expression: Computing the average number of methods per class in all JHotDraw classes. We could achieve this by the following nested aggregate expression:
select avg(Class c | c.fromSource() |
count(Method m | m.getDeclaringType() = c))
|