My goodness, I had already forgotten how complicated the code to insert calls to predicates and especially the code to auto-generate @Combine
-style predicate methods is! I got the part for the @PredicateLink
almost immediately right last night already, but the code for auto-generating methods and then calling them was much harder to deal with. I think I got it now.
In both cases, the first argument passed is still this
, or null
in case of a static method. If method arguments are to be passed, i.e. if the @PredicateLink
or @Combine
annotation specified arguments=true
, then the arguments follow in the exact same order as in the method annotated. This, of course, limits the applicability of an annotation. After that, the values of the annotation are passed, as before.
The biggest problem, or at least the one that took me the longest to figure out, was that I needed to generate variable names for method arguments that were passed to auto-generated predicate methods. I already do that for the variables that contain the annotation values, but I at first didn’t realize I had to do the same for the method arguments. I picked a name pattern that guarantees that the method names don’t class with the flattened annotation values: Since each method will have the arguments only at the beginning, after the this0
value, I just named them arg$$$$0
, arg$$$$1
, etc. Four $
aren’t in use for argument names yet.
Now I can write predicates that compare argument values or check if an arguments lock has been acquired:
public class TCSample14 {
public static class CheckSynchronized {
public static boolean checkUnaryFirst(
Object thisObject, Object o1) {
return ThreadCheckPredicates.checkMonitorOwned(o1);
}
public static boolean checkBinaryFirst(
Object thisObject, Object o1, Object o2) {
return ThreadCheckPredicates.checkMonitorOwned(o1);
}
public static boolean checkBinarySecond(
Object thisObject, Object o1, Object o2) {
return ThreadCheckPredicates.checkMonitorOwned(o2);
}
}
public static interface OnlySynchronized {
@PredicateLink(
value=CheckSynchronized.class,
method="checkUnaryFirst",
arguments=true)
public static @interface Unary { }
@PredicateLink(
value=CheckSynchronized.class,
method="checkBinaryFirst",
arguments=true)
public static @interface Binary1st { }
@PredicateLink(
value=CheckSynchronized.class,
method="checkBinarySecond",
arguments=true)
public static @interface Binary2nd { }
}
public static interface NotSynchronized {
@Combine(
value=Combine.Mode.NOT,
arguments=true)
public static @interface Unary {
OnlySynchronized.Unary value()
default @OnlySynchronized.Unary;
}
@Combine(
value=Combine.Mode.NOT,
arguments=true)
public static @interface Binary1st {
OnlySynchronized.Binary1st value()
default @OnlySynchronized.Binary1st;
}
@Combine(
value=Combine.Mode.NOT,
arguments=true)
public static @interface Binary2nd {
OnlySynchronized.Binary2nd value()
default @OnlySynchronized.Binary2nd;
}
}
// ...
This code above defines predicate methods for unary and binary methods accepting Object
parameters and then checks if the first or second argument’s lock has been acquired. Annotations have been defined to check that or the opposite. Below, the class is continued with usage examples:
// ...
public static void main(String[] args) {
TCSample14 o = new TCSample14();
System.out.println("main!");
o.succeeds();
o.fails();
System.out.println("end main!");
}
// these invariants all succeed
void succeeds() {
String o1 = "foo";
String o2 = "bar";
notUnary(o1);
synchronized(o1) {
unary(o1);
binary1st(o1,o2);
notBinary2nd(o1,o2);
}
synchronized(o2) {
notBinary1st(o1,o2);
binary2nd(o1,o2);
}
}
@OnlySynchronized.Unary
void unary(Object o1) {
System.out.println("unary!");
}
@OnlySynchronized.Binary1st
void binary1st(Object o1, Object o2) {
System.out.println("binary1st");
}
@OnlySynchronized.Binary2nd
void binary2nd(Object o1, Object o2) {
System.out.println("binary2nd");
}
// these invariants all fail
private void fails() {
String o1 = "foo";
String o2 = "bar";
unary(o1);
synchronized(o1) {
notUnary(o1);
notBinary1st(o1,o2);
binary2nd(o1,o2);
}
synchronized(o2) {
binary1st(o1,o2);
notBinary2nd(o1,o2);
}
}
@NotSynchronized.Unary
void notUnary(Object o1) {
System.out.println("notUnary!");
}
@NotSynchronized.Binary1st
void notBinary1st(Object o1, Object o2) {
System.out.println("notBinary1st");
}
@NotSynchronized.Binary2nd
void notBinary2nd(Object o1, Object o2) {
System.out.println("notBinary2nd");
}
}
Now, one of the big problems, of course, is still that no method invocation conversion is performed, i.e. I can’t apply these annotations to methods that have one or two String
parameters, even though String
is a subclass of Object
and could therefore be widened. Another problem is that right now, I have to fix the number of parameters.
Perhaps using an array of Object
would be a good idea nonetheless: The predicate could call arr.length
on the array and arr[i].getClass()
to determine the class. Of course, I would have to auto-box primitive types. This has two disadvantages: The distinction between primitive types and their boxed versions would be lost, and it creates unnecessary objects. The advantage would be a very nice, clean way to write these annotations above:
public class TCSample14 {
public static class CheckSynchronizedArg {
public static boolean check(
Object[] args, short index) {
return ThreadCheckPredicates.checkMonitorOwned(args[index]);
}
public static boolean checkNot(
Object[] args, short index) {
return !ThreadCheckPredicates.checkMonitorOwned(args[index]);
}
}
@PredicateLink(
value=CheckSynchronizedArg.class,
arguments=true)
public static @interface OnlySynchronized {
short value() default 0;
}
@PredicateLink(
value=CheckSynchronizedArg.class,
method="checkNot",
arguments=true)
public static @interface NotSynchronized {
short value() default 0;
}
Written this way, by default @OnlySynchronized
and @NotSynchronized
check whether the lock of the first argument has been acquired or not, and @OnlySynchronized(2)
and @NotSynchronized(2)
do the same for the second argument.
But even without the array, I already think this is very, very cool, especially since it partially alleviates the need for local variable annotations: If I want to check some kind of invariant for a local variable (or argument, or field), I can just write an empty dummy method that takes the variable as parameter, and then annotate the dummy method:
public class TCSample15 {
public static class CheckNonNull {
public static boolean check(
Object thisObject, Object o1) {
return o1!=null;
}
}
@PredicateLink(
value=CheckNonNull.class,
arguments=true)
public static @interface NonNull { }
@NonNull
private static void dummy(Object o1) { }
public static void main(String[] args) {
System.out.println("main!");
String s = "foo"; // local variable
dummy(s); // check invariant
s = null;
dummy(s);
System.out.println("end main!");
}
}
Here I define a predicate that checks if the first argument is non-null. Then I create a @NonNull annotation and a dummy
method that is annotated with it. Now I can check if the local variable s
meets that invariant or not. The first check will succeed, the second will fail.
What else do I have on my “to do” list, besides writing the thesis, which I so elegantly postponed again by two days?
- Allowing the
arguments=true
feature in XML files (@Update: Done. Import and export works now.@) - Checking that member annotations of
@Combine
-style annotations cannot havearguments=true
if the combining annotation doesn’t have it set. (@Update: Done. Also checked that it’s ok to have it set in the outer annotation, but not in the inner.@) @SuppressSubtypingWarning
- Method invocation conversion (@Update: Done, kind of, because I converted to an array of
Object
.@) - More tests with DrJava
- I also noticed that some of my unit tests are breaking now… (@Update: Done.@)
I’ll probably tackle the first item. I don’t know about the rest. Now it’s 12:45 AM, I’ve been coding for half a day again, and I haven’t gone grocery shopping. By now I think it’s time to forgo getting more bread and Gatorade and get some shuteye instead.