|
Duration: 40 minutes approx.
This tutorial demonstrates how to take advantage of the object-oriented
features of .QL by writing your own classes. It assumes knowledge of
everything covered in the SemmleCode Tutorial,
and the example queries are over the same JHotDraw project.
Classes
.QL is truly an object-oriented language: you can write new class
definitions of your own. To illustrate, let us define a new
class for instance fields that are not private (you can paste this
in the Quick query window for nice syntax coloring and
for running the queries below)
class VisibleInstanceField extends Field {
VisibleInstanceField() {
not(this.hasModifier("private")) and
not(this.hasModifier("static"))
}
predicate readExternally() {
exists(FieldRead fr |
fr.getField() = this and
fr.getSite().getDeclaringType() != this.getDeclaringType())
}
}
This class definition states that a VisibleInstanceField is
a special kind of Field. The constructor actually makes the
distinguishing property of the new class precise: this field
does not have modifier private or static.
The class also defines a predicate, which is a property of
some VisibleInstanceFields. It checks whether this
field is read externally. In order to make that check, it introduces
a local variable named fr of type FieldRead: first
we check that fr is indeed an access to this field, and then we
check that the read does not occur in the host type of this.
To try out your new class, paste it in the Quick query window,
and add a select statement to find VisibleInstanceFields
that are not read externally:
from VisibleInstanceField vif
where vif.fromSource()
and
not(vif.readExternally())
select vif.getDeclaringType().getPackage(),
vif.getDeclaringType(),
vif
Run the query via the Query-as-a-tree button
( ).
Surprisingly, there are quite a lot of results to this query, 69 in all.
Upon inspection it turns out that many are protected fields, so
while they are not read externally in the current source, they might
be in future subclasses.
Classless predicates
Sometimes there is no obvious class to put a new predicate, and in
fact .QL allows you to define such predicates outside a class.
To illustrate, here is a classless predicate for checking that one
field masks another in a superclass:
predicate masks(Field masker, VisibleInstanceField maskee) {
maskee.getName() = masker.getName()
and
masker.getDeclaringType().hasSupertype+(maskee.getDeclaringType())
}
In words, the two fields share the same name, but the
masker is defined in a subtype of the maskee,
while the maskee is visible.
You can use classless predicates directly in your queries.
In the Quick query window, include the class VisibleInstanceField
and the predicate masks. Then write the select statement
from Field f, VisibleInstanceField vif
where masks(f,vif)
select f, vif
You will in fact get five results. The matches appear to be examples of
poor programming style. All mask a protected field with a private one;
and several subclasses of AbstractSelectedAction mask its labels
field. Fortunately, however, another quick query (try to write it!)
confirms there are eight other subclasses that do not do such masking.
Often the introduction of a classless predicate is merely a stepping
stone towards introducing a new class. Wrapping predicates in a class
has several advantages. First, your queries become shorter because
you can use method dispatch and so there is no need to name intermediate
results. Second, when typing queries you get much better content
assist, so you do not need to remember details of all existing predicates.
To illustrate, let's define a class MaskedField as a subclass
of VisibleInstanceField:
class MaskedField extends VisibleInstanceField {
MaskedField() { masks(_,this) }
Field getMasker() { masks(result,this) }
string getIconPath() { result = "icons/semmle-logo.png" }
}
In the constructor, this class definition says that this is indeed being masked.
Next the class introduces two methods. The getMasker() method returns
the masker of this. In general, the body of a method is a relation between
two special variables named result and this; the relation may also
involve any method parameters. Our new class also defines a method
iconPath. In fact this overrides a method of the same signature in Field,
and so from now on masked fields will be displayed differently from other fields.
Somewhat frivolously, we have decided to give them the Semmle icon.
To put your new class to work, enter VisibleInstanceField and
MaskedField in the Quick query window, along with the
select statement
from MaskedField mf select mf, mf.getMasker()
When running the query, you will see how the icon used to display the result has changed.
It is also possible to override the toString method, to change the string that
is printed next to an icon. Why don't you try it out yourself!? SemmleCode lets you view the results of your queries just
the way you want them.
Remember this: wrapping your predicates and queries in classes makes it easier
for others to use your work. First, the resulting queries will be shorter
and clearer. Second, better content-assist makes finding the right method
or predicate a doddle. .QL makes your queries reusable.
Default constructors
New classes do not have to define a constructor; when it is not defined,
the default constructor is the same as that of the superclass. Often
this is handy when we want to define a new method that did not exist
in the super class, but which really belongs there.
For instance, suppose that we wish to define a method named depth
that returns the length of a path from Object to a given type in
the inheritance hierarchy. That method is not defined in the standard
library definition of RefType, but it really is a property of
any reference type. In .QL, we can add it as such via the definition
class RT extends RefType {
int depth() {
(this.hasQualifiedName("java.lang", "Object") and result = 0)
or
(result = ((RT)this.getASupertype()).depth() + 1)
}
}
That is, the depth of Object itself is 0. Otherwise, we pick a supertype,
compute its depth and add 1 to it. In the recursive step, we cast a
RefType to a RT, just so we can cast depth on
it. That cast will always succeed, because the characteristic predicates
(=constructors) of RT and RefType are identical.
Let's put this query to work, by computing the average maximum depth over
all reference types in the source of JHotdraw:
select avg(RT t | t.fromSource() | max(int i | i = t.depth()))
The answer is 3.683112 - make of that what you will. SemmleCode .QL allows
you to write modular, reusable queries like no other language.
Generic Queries
We conclude our discussion of inheritance with a checker
for the Factory pattern, where all elements of
a particular kind are constructed in a special factory class.
The JHotDraw documentation lists several examples of that
little pattern (affectionately calling it a 'pattlet'). When a factory is defined it
should be properly used. That is, whenever we construct a 'product',
that must be done from the constructor of another product or
in a factory.
In the SemmleCode query library there is
an abstract class for checking that property; in particular
it defines a predicate getViolation(RefType t,Call c) which
holds when c is a constructor call in t that violates the
factory rule.
To instantiate that abstract class, we need to override its
product and factory predicates, and here we do that for
SVGFigures (a special kind of figure in JHotDraw):
class SVGFactory extends Factory {
predicate product(RefType t) {
exists(Interface svgFigure |
svgFigure.hasName("SVGFigure")
and
t.hasSupertype+(svgFigure))
}
predicate factory(RefType t) {
t.hasName("DefaultSVGFigureFactory")
}
}
from SVGFactory f, Call c
where c = f.getAViolation()
select c.getCaller().getDeclaringType().getPackage(),
c.getCaller().getDeclaringType(),
c
The first predicate says that t is a product if it
has some supertype named SVGFigure. Note how we introduced a new
local variable to name that supertype, via the exists construct.
Here the hasSupertype relation
may be direct or via a chain of other supertypes: that is indicated by the +
in t.hasSupertype+(svgFigure).
To be precise,
t.hasSupertype+(svgFigure)
holds if there exist t1, t2, ...,tn so that
t.hasSupertype(t1),
t1.hasSupertype(t2) , ... ,
tn.hasSupertype(svgFigure)
The second predicate called factory
just defines the name of the factory in question.
With these definitions in place, the query itself is easy. We report
not only the offending call to a constructor, but also the containing
package and class. There are in fact 27 matches. For many of these
using a factory might be overkill,
as only one SVGFigure is created. However,
for SVGPanel, there are 8 distinct violations, and it would have made
sense to use an instance of DefaultSVGFigureFactory there.
SemmleCode makes it very easy to create queries that are tailored
to your project, through the .QL query language.
If you've made it this far, then you must be a great expert of SemmleCode by now to rate this plugin adequately.
|