SemmleCode Introductory Tutorial

Article Index
SemmleCode Introductory Tutorial
Loading Code
Writing Queries
Packaged Queries
Viewing Results
Saving Queries

Writing your own queries in .QL

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))