Jlint consists of two separate programs performing syntax and semantic verification. As far as Java mostly inherits C/C++ syntax and so inherits most of the problems caused by C syntax, the idea was to create common syntax verifier for all C-family languages: C, C++, Objective C and Java. This program was named AntiC, because it fixes problems with C grammar, which can cause dangerous programmer's bugs, undetected by compiler. By using hand-written scanner and simple top-down parser, AntiC is able to detect such bugs as suspicious use of operators priorities, absence of break in switch code, wrong assumption about constructions bodies...
Semantic verifier Jlint extracts information from Java class files. As far as Java class file has very well specified and simple format, it greatly simplifies Jlint in comparison with source level verifiers, because development of Java grammar parser is not a simple task (even through Java grammar is simpler and less ambiguous than C++ grammar). Also dealing only with class files, protect Jlint from further Java extensions (format of virtual byte instructions is more conservative). By using debugging information Jlint can associate reported messages with Java sources.
Jlint performs local and global data flow analyses, calculating possible values of local variables and catching redundant and suspicious calculations. By performing global method invocation analysis, Jlint is able to detect invocation of method with possible "null" value of formal parameter and using of this parameter in method without check for "null". Jlint also builds lock dependency graph for classes dependencies and uses this graph to detect situations, which can cause deadlock during multithreaded program execution. Except deadlocks, Jlint is able to detect possible race condition problem, when different threads can concurrently access the same variables. Certainly Jlint can't catch all synchronization problems, but at least it can do something, which can save you a lot of time, because synchronization bugs are the most dangerous bugs: non-deterministic, and not always reproducible. Unfortunately Java compiler can't help you with detecting synchronization bugs, may be Jlint can...
Jlint uses smart approach to message reporting. All messages are grouped in categories, and it is possible to enable or disable reporting messages of specific category as well as concrete messages. Jlint can remember reported messages and do not report them once again when you run Jlint second time. This feature is implemented by means of history file. If you specify -history option, then before reporting a message, Jlint searches in this file if such message was already reported in the past. If so, then no message is reported and programmer will not have to spend time parsing the same messages several times. If message was not found in history file, it is reported and appended to history file to eliminate reporting of this message in future. Some messages refer to class/method name and are position independent, while some messages are reported for specific statement in method's code. Messages of second type will not be repeatedly reported only if method's source is not changed.
printf("\128");
printf("\1234");
System.out.println("\uABCDE:");
printf("\x");
char* p = "???=undefined";
char ch = 'ab';
long l = 0x111111l;
x & y == z x && y & z x || y = z
x || y && z
x>>y - 1 x >> y&7
if (x = y) {}
if (x>>=1 != 0) {}
if (x == y & 1) {}
while (x != 0) x >>= 1; n += 1; return x;
if (x > y); { int tmp = x; x = y; y = tmp; } if (x != 0) x = -x; sign = -1; sqr = x*x;
if (rc != 0) if (perr) *perr = rc; else return Ok;
switch(j) { case 1: ... case 2: switch(ch); { case 'a': case 'b': ... } }
switch (n & 3) { do { default: *dst++ = 0; case 3: *dst++ = *drc++; case 2: *dst++ = *drc++; case 1: *dst++ = *drc++; } while ((n -= 4) > 0; }
case '+': case '-': sign = 1; break;
nobreak
macro is defined and used in switch statement:
#define nobreak ... switch (cop) { case sub: sp[-1] = -sp[1]; nobreak; case add: sp[-2] += sp[-1]; break; ... }
switch (x) { case do_some_extra_work: ... // fall thru case do_something: ... }
switch (action) { case op_remove: do_remove(); case op_insert: do_insert(); case op_edit: do_edit(); }
synchronized
language construction. Monitor is always
associated with object and prevents concurrent access to the object by using
mutual exclusion strategy. Java also supports facilities for waiting and
notification of some condition.Unfortunately, providing these synchronization primitives, Java compiler and virtual machine are not able to detect or prevent synchronization problems. Synchronization bugs are the most difficult bugs, because of non-deterministic behavior of multithreaded program. There are two main sources of synchronization problems: deadlocks and race conditions.
Situation in which one or more threads mutually lock each other is called deadlock. Usually the reason of deadlock is inconsistent order of resource locking by different threads. In Java case resources are object monitors and deadlock can be caused by some sequence of method invocations. Let's look at the following example of multithreaded database server:
class DatabaseServer { public TransactionManager transMgr; public ClassManager classMgr; ... } class TransactionManager { protected DatabaseServer server; public synchronized void commitTransaction(ObjectDesc[] t_objects) { ... for (int i = 0; i < t_objects.length; i++) { ClassDesc desc = server.classMgr.getClassInfo(t_objects[i]); ... } ... } ... } class ClassManager { protected DatabaseServer server; public synchronized ClassDesc getClassInfo(ObjectDesc object) { ... } public synchronized void addClass(ClassDesc desc) { ObjectDesc t_objects; ... // Organized transaction to insert new class in database server.transMgr.commit_transaction(t_objects); } };If database server has one thread for each client and one client is committing transaction while another client adds new class to database, then deadlock can arise. Consider the following sequence:
TransactionManager.commitTransaction()
.
While execution of this method monitor of TransactionManager object is locked.
ClassManager.addClass()
and
locks monitor of ClassManager object.
TransactionManager.commitTransaction()
tries to invoke
method ClassManager.getClassInfo()
but has to wait because this
object is locked by another thread.
ClassManager.addClass()
tries to invoke
method TransactionManager.commitTransaction()
but has to wait
because this object is locked by another thread.
So we have deadlock and database server is halted and can't serve any client.
The reason of this deadlock is loop in locking graph. Let's explain it less
formally. We will construct oriented graph G of monitor lock relations.
As far as locked resource are objects, so vertexes of this graph should be
objects. But this analysis can't be done statically, because set of all object
instances is not known at compile time. So the only kind
of analysis, which Jlint is able to perform, is analysis of interclass
dependencies. So the vertexes of graph G will be classes. More precisely,
each class C is represented by two vertexes: vertex C for class itself and
vertex C' for metaclass. First kind of vertexes are used for dependencies
caused by instance methods invocation, and second - by static methods.
We will add edge (A,B) with mark "foo" to the graph if some synchronized
method foo()
of class B, can be invoked directly or indirectly
from some synchronized method of class A for object other than
this
.
For example for the following classes:
class A { public synchronized void f1(B b) { b.g1(); f1(); f2(); } public void f2(B b) { b.g2(); } public static synchronized void f3() { B.g3(); } } class B { public static A ap; public static B bp; public synchronized void g1() { bp.g1(); } public synchronized void g2() { ap.f1(); } public static synchronized void g3() { g3(); } }will add the following edges:
g1 A --------> B, because of invocation of b.g1() from A.f1() g2 A --------> B, because of following call sequence: A.f1 -> A.f2 -> B.g2 g3 A' --------> B', because of invocation of b.g3() from A.f3() g1 B --------> B, loop edge because of recursive call for non-this object in B.g1(). f1 B --------> A, because of invocation of ap.f1() from B.g2()Deadlock is possible only if there is loop in graph G. This condition is necessary, but not enough (presence of loop in graph G doesn't mean that program is not correct and deadlock can happen during it's execution). So using this criterion Jlint can produce messages about deadlock probability in case where deadlock is not possible.
As far as task of finding all loops in the graph belongs to the NP class, no efficient algorithm for reporting all such loops exists at this moment. To do it work best and fast, Jlint uses restriction for number of loops, which pass through some graph vertex.
There is another source of deadlock - execution of wait()
method.
This method unlocks monitor of current object and waits until some other thread
notify it. Both methods wait()
and notify()
should be called with monitor locked. When thread is awaken from wait state,
it tries to reestablish monitor lock and only after it can continue
execution. The problem with wait()
is that only one monitor is
unlocked. If method executing wait()
was invoked from synchronized
method of some other object O, monitor of this object O will not be released by
wait
. If thread, which should notify sleeping thread, needs to
invoke some synchronized method of object O, we will have deadlock:
one thread is sleeping and thread, which can awoke it, waits until monitor
will be unlocked. Jlint is able to detect situations when wait()
method is called and more than one monitors are locked.
But deadlock is not the only synchronization problem. Race condition or concurrent access to the same data is more serious problem. Let's look at the following class:
class Account { protected int balance; public boolean get(int sum) { if (sum > balance) { balance -= sum; return true; } return false; } }What will happen if several threads are trying to get money from the same account? For example account balance is $100. First thread tries to get $100 from the account - check is ok. Then, before first thread can update account balance, second thread tries to perform the same operation. Check is ok again! This situation is called race condition, because result depends on "speed" of threads execution.
How can Jlint detect such situations? First of all Jlint builds
closure of all methods, which can be executed concurrently. The obvious
candidates are synchronized methods and method run
of classes
implemented Runnable
protocol or inherited from
Thread
class. Then all other methods, which can be invoked from
these methods, are marked as concurrent. This process repeats until no
more method can be added to concurrent closure. Jlint produces message
about non-synchronized access only if all of the following conditions
are true:
volatile
or final
.
this
object of the method.
It is necessary to explain last two items. When object is created and initialized, usually only one thread can access this object through its local variables. So synchronization is not needed in this case. The explanation of item 5 is that not all objects, which are accessed by concurrent threads, need to be synchronized (and can't be declared as synchronized in some cases to avoid deadlocks). For example consider implementation of database set:
class SetMember { public SetMember next; public SetMember prev; } class SetOwner { protected SetMember first; protected Setmember last; public synchronized void add_first(SetMember mbr) { if (first == null) { first = last = mbr; mbr.next = mbr.prev = null; } else { mbr.next = first; mbr.prev = null; first.prev = mbr; first = mbr; } } public synchronized void add_last(SetMember mbr) {...} public synchronized void remove(SetMember mbr) {...} };In this example
next
and prev
components
of class SetMemeber
can be accessed only from synchronized
methods of SetOwner
class, so no access conflict is possible.
Rule 5 was included to avoid reporting of messages in situations like this.Rules for detecting synchronization conflicts by Jlint are not finally defined, some of them can be refused or replaced, new candidates can be added. The main idea is to detect as much suspicious places as possible, while not producing confusing messages for correct code.
Message category: | deadlock |
Message code: | sync_loop |
Loop in class graph G (see Synchronization) is detected. One such message is produced for each edge of the loop. All loops are assigned unique identifier, so it is possible to distinguish messages for edges of one loop from another.
Message category: | deadlock |
Message code: | loop |
Reported invocation is used in call sequence from synchronized method of class
A to synchronized method foo()
of class B,
so that edge (A,B) is in class graph G
(see Synchronization).
If method foo()
is invoked directly, then only previous message
(sync_loop) is reported. But if call sequence includes some other invocations
(except invocation of foo()
), then this message is produced for
each element of call sequence. If several call paths exist for classes A, B
and method foo()
, then all of them (but not more than specified
by MaxShownPaths
parameter) are printed. PathId
identifier is used to group messages for each path.
Message category: | deadlock |
Message code: | wait |
At the moment of wait()
method invocations, more than one monitor
objects are locked by the thread. As far as wait unlocks only one monitor,
it can be a reason of deadlock.
Successive messages of type wait_path specify
call sequence, which leads to this invocation.
Monitors can be locked by invocation of synchronized method or by
explicit synchronized construction. Jlint handle both of the cases.
Message category: | deadlock |
Message code: | wait_path |
By the sequence of such messages Jlint informs about possible invocation chain,
which locks at least two object monitors and is terminated by method
calling wait()
. As far as wait()
unlocks only one
monitor and suspend thread, this can cause deadlock.
Message category: | race_condition |
Message code: | nosync |
Method is declared as synchronized in base class, but is overridden in derived class by non-synchronized method. It is not a bug, but suspicious place, because if base method is declared as synchronized, then it is expected that this method can be called from concurrent threads and access some critical data. Usually the same is true for derived method, so disappearance of synchronized modifier looks suspiciously.
Message category: | race_condition |
Message code: | concurrent_call |
Non-synchronized method is invoked from method marked as concurrent for
object other than this
(for instance methods) or for class,
which is not base class of caller method class (for static methods).
This message is reported only if invocation is not enclosed in synchronized
construction and this method also can be invoked from methods of other classes.
Message category: | race_condition |
Message code: | concurrent_access |
Field is accessed from method marked as concurrent. This message is produced only if:
this
(for instance methods) or to classes which are not base for
class of static method.
new
and assigned to local variable.
Message category: | race_condition |
Message code: | run_nosync |
Method run()
of class implementing Runnable
interface
is not declared as synchronized. As far as different threads can be started
for the same object implementing Runnable
interface, method
run
can be executed concurrently and is first candidate
for synchronization.
Message category: | wait_nosync |
Message code: | wait_nosync |
Method wait()
or notify()
is invoked from method,
which is not declared as synchronized. It is not surely a bug, because
monitor can be locked from other method, which directly or indirectly
invokes current method. But you should agree that it is not common case.
Message category: | not_overridden |
Message code: | not_overridden |
Derived class contains the method with the same name as in base class, but profiles of these methods do not match. More precisely: message is reported when for some method of class A, exists method with the same name in derived class B, but there is no method with the same name in class B, which is compatible with definition of the method in class A (with the same number and types of parameters). Programmer writing this code may erroneously expect that method in derived class overrides method in base class and that virtual call of method of base class for object of derived class will cause execution method of the derived class.
Message category: | not_overridden |
Message code: | not_overridden |
A class contains the method hashCode(), but does not also define the method equals(). These two method have an important relation, as defined in contract for the java.lang.Object hashCode() method:
If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.Alteration of one method will probably break this relation unless there is an equivalent change to the other. Programmers who break the relation set out in the contract of java.lang.Object will find their objects do not function correctly as keys in a Hashtable.
Message category: | not_overridden |
Message code: | not_overridden |
A class contains the method equals(), but does not also define the method hashCode(). See the explanation given for the item above
Message category: | field_redefined |
Message code: | field_redefined |
Field in derived class has the same name as field of some of base classes. It can cause some problems because this two fields points to different locations and methods of base class will access one field, while methods of derived class (and classes derived from it) will access another field. Sometimes it is what programmer expected, but in any case it will not improve readability of program.
Message category: | shadow_local |
Message code: | shadow_local |
Local variable of method shadows class component with the same name.
As far as it is common practice in constructors to use formal parameters
with the same name as class components, Jlint detects situations, when
class field is explicitly accessed by using this
reference and doesn't report this message in this case:
class A { public int a; public void f(int a) { this.a = a; // no message } public int g(int a) { return a; // message "shadow_local" will be reported } }
Message category: | super_finalize |
Message code: | super_finalize |
As it is mentioned in book "The Java Programming Language" by Ken Arnold
and James Gosling, calling of super.finalize()
from
finalize()
is good practice of programming,
even if base class doesn't define finalize()
method.
This makes class implementations less dependent from each other.
null/not_null
is calculated, selecting variables which value can
be null
.
When value of expression is assigned to variable,
these characteristics are copied to correspondent variable descriptor.
Jlint handles control transfer instruction in special way: saving,
modifying, merging or restoring context depending on type of instruction.
Context in this consists of local variables states (minimal, maximal
values and mask) and state of top of the stack (for handling ?: instruction).
Initially all local integer variable are considered to have minimum and maximum
properties equal to the range of correspondent type, and mask indicating
that all bits in this range can be set. Object variables attribute initially
is set to not_null
. The same characteristics are always used
for class components, because Jlint is not able to perform full data flow
analysis (except checking for passing null value to formal parameter of
methods).
Table below summarizes actions performed by Jlint for handling
control transfer instruction:
Instruction type | Correspondent Java construction | Action |
---|---|---|
Forward conditional jump | IF statement | Save current context. Modify current context in assumption that condition is false (no jump). Modify saved context in assumption that condition is true (jump takes place) |
Forward unconditional jump | Start of loop, jump around ELSE branch of IF | Save current context | Backward conditional jump | Loop statement condition | Modify context in assumption that condition is false (no jump) |
Backward unconditional jump | Infinite loop | Do nothing |
Label of forward jump | End of IF body or SWITCH case | If previous instruction is no-pass instruction (return, unconditional jump, throw exception) then restore saved context, otherwise merge current context with saved context (set minimum property of integer variable to minimum of this property value in current and saved contexts, maximum - to maximum of values in two contexts, and mask as join of masks in two context; for object variable - mark it as "may contain null" if it is marked so in one of contexts). If label corresponds to switch statement case, and switch expression is single local variable, then update properties of this variable by setting its minimum and maximum values and mask to value of case selector. |
Label of backward jump | Start of loop body | Reset properties of all variables modified between this label and backward jump instructions. Reset for integer variables means setting minimum property to minimum value of correspondent type, ... Reset for object variable clears mark "may contain null". |
Message category: | null_reference |
Message code: | null_param |
Formal parameter is used in the method without check for null (component of object is accessed or method of this object is invoked), while this method can be invoked with null as the value of this parameter (detected by global data flow analysis). Example:
class Node { protected Node next; protected Node prev; public void link(Node after) { next = after.next; // Value of 'after' parameter can be null prev = after; after.next = next.prev = this; } } class Container { public void insert(String key) { Node after = find(key); if (after == null) { add(key); } Node n = new Node(key); n.link(after); // after can be null } }
Message category: | null_reference |
Message code: | null_var |
Variable is used in the method without check for null.
Jlint detects that referenced variable was previously assigned
null
value or was found to be null
in one of
control paths in the method.
Jlint can produce this message in some situations, when value of variable can not actually be null:
public int[] create1nVector(int n) { int[] v = null; if (n > 0) { v = new int[n]; } for (int i = 0; i < n; i++) { v[i] = i+1; // message will be reported } return v; }
Message category: | null_reference |
Message code: | null_ptr |
Constant null
is used as left operand of '.' operation:
public void printMessage(String msg) { (msg != null ? new Message(msg) : null).Print(); }
Message category: | zero_operand |
Message code: | zero_operand |
One of operands of binary operation is zero. This message can be produced for sequence of code like this:
int x = 0; x += y;
Message category: | zero_result |
Message code: | zero_result |
Jlint detects that for given operands, operation always produces zero result. This can be caused by overflow for arithmetic operations or by shifting all significant bits in shift operations or clearing all bits by bit AND operation.
Message category: | domain |
Message code: | shift_count |
This message is reported when minimal value of shift count operand exceeds 31 for int type and 63 for long type or maximal value of shift count operand is less than 0:
if (x > 32) { y >>= x; // Shift right with count greater than 32 }
Message category: | domain |
Message code: | shift_count |
Range of shift count operand is not within [0,31] for int type or [0,63] for long type. Jlint doesn't produce this message when distance between maximum and minimum values of shift count is greater than 255. So this message will not be reported if shift count is just variable of integer type:
public int foo(int x, int y) { x >>= y; // no message x >>= 32 - (y & 31); // range of count is [1,32] }
Message category: | domain |
Message code: | conversion |
Converted value is out of range of target type. This message can be reported not only for explicit conversions, but also for implicit conversions generated by compiler:
int x = 100000; short s = x; // will cause this message
Message category: | truncation |
Message code: | truncation |
This message is reported when significant bits can be lost as a result of conversion from large integer type to smaller. Such conversions are always explicitly specified by programmer, so Jlint tries to reduce number of reported messages caused by data truncation. Example below shows when Jlint produces this message and when not:
public void foo(int x, long y) { short s = (short)x; // no message char c = (char)x; // no message byte b = (byte)y; // no message b = (byte)(x & 0xff); // no message b = (byte)c; // no message c = (x & 0xffff); // no message x = (int)(y >>> 32); // no message b = (byte)(x >> 24); // truncation s = (int)(x & 0xffff00); // truncation x = (int)(y >>> 1); // truncation s = (short)c; // truncation }
Message category: | overflow |
Message code: | overflow |
Result of operation, which has good chance to cause overflow
(multiplication, left shift), is converted to long. As far as operation is performed with int
operands, overflow can happen before conversion.
Overflow can be avoided by conversion of one of operation operands to long,
so operation will be performed with long
operands.
This message is produced not only for explicit type conversion done by
programmer, but also for implicit type conversions performed by compiler:
public long multiply(int a, int b) { return a*b; // operands are multiplied as integers // and then result will be converted to long }
Message category: | redundant |
Message code: | same_result |
Using information about possible ranges of operands values, Jlint can
make a conclusion, that logical expression is always evaluated to the
same value (true
or false
):
public void foo(int x) { if (x > 0) { ... if (x == 0) // always false { } } }
Message category: | redundant |
Message code: | disjoint_mask |
By comparing operands masks, Jlint makes a conclusion that
operands of ==
or !=
operations can be equal
only when both of them are zero:
public boolean foo(int x, int y) { return ((x & 1) == y*2); // will be true only for x=y=0 }
Message category: | redundant |
Message code: | redundant |
This message is produced for %
operation when right operand
is either greater either less than zero, and absolute value of left operand
is less than absolute value of right operand. In this case
x % y == x
or x % y == -x
.
Message category: | short_char_cmp |
Message code: | short_char_cmp |
Comparison of short
operand with char
operand.
As far as char
type is unsigned, and is converted to
int
by filling high half of the word with 0, and
short
type is signed and is converted to int
using sign extension, then symbols in range 0x8000...0xFFFF
will not be considered equal in such comparison:
boolean cmp() { short s = (short)0xabcd; char c = (char)s; return (c == s); // false }
Message category: | string_cmp |
Message code: | string_cmp |
String operands are compared by ==
or !=
operator.
As far as ==
returns true
only if operands
point to the same object, so it can return false for two strings with same
contents. The following function will return false
in JDK1.1.5:
public boolean bug() { return Integer.toString(1) == Integer.toString(1); }
Message category: | weak_cmp |
Message code: | weak_cmp |
This message is produced in situations when ranges of compared operands intersect only in one point. So inequality comparison can be replaced with equality comparison. Such message can be caused by error in program, when programmer has wrong assumption about ranges of compared operands. But even if this inequality comparison is correct, replacing it with equality comparison can make code more clear:
public void foo(char c, int i) { if (c <= 0) { // is it a bug ? if ((i & 1) > 0) { // can be replaced with (i & 1) != 0 ... } } }
Message category: | incomp_case |
Message code: | incomp_case |
Constant in switch case is out of range of switch expression or has incompatible bit mask with switch expression:
public void select(char ch, int i) { switch (ch) { case 1: case 2: case 3: ... case 256: // constant is out of range of switch expression } switch (i & ~1) { case 0: case 0xabcde: ... case 1: // switch expression is always even } }
Message category: | bounds |
Message code: | neg_len |
Array with negative length is created.
int len = -1; char[] a = new char[len]; // negative array length
Message category: | bounds |
Message code: | maybe_neg_len |
Range of length expression of created array contains negative values. So it is possible that length of created array will be negative:
public char[] create(int len) { if (len >= 0) { return new char[len-1]; // length of created array may be negative } return NULL; }JLINT will not report this message if minimal value of length is less than -127 (to avoid messages for all expressions of sign types).
Message category: | bounds |
Message code: | bad_index |
Index expression is out of array bounds. This message means that index expression either always produce negative values either it's minimal value is greater or equal to maximal possible length of the accessed array:
int len = 10; char[] s = new char[len]; s[len] = '?'; // index out of the array bounds
Message category: | bounds |
Message code: | maybe_bad_index |
Value of index expression can be out of array bounds. This message is produced when either index expression can be negative or it's maximal value is greater than maximal value of accessed array length. JLINT doesn't produce this message when minimal value of index is less than -127 or difference between maximal value of index and array length is greater or equal than 127.
public void putchar(char ch) { boolean[] digits = new boolean[9]; if (ch >= '0' && ch <= '9') { digits[ch-'0'] = true; // index may be out of range digits[ch-'1'] = true; // index may be negative } }
Options are always compared ignoring letters case and '_' symbols. So the following two strings specify the same option: -ShadowLocal and -shadow_local.
All Jlint options are prefixed by '-' or '+'. For options, which can be enabled or disabled, '+' means that option is enabled and '-' means that option is disabled. For options like source or help there is no difference between '-' and '+'.
Top level category | subcategory | Message code |
---|---|---|
Synchronization | deadlock | syncLoop |
loop | ||
wait | ||
waitPath | ||
raceCondition | noSync | |
concurrentCall | ||
concurrentAccess | ||
runNoSync | ||
waitNoSync | waitNoSync | |
Inheritance | notOverridden | notOverridden |
fieldRedefined | fieldRedefined | |
shadowLocal | shadowLocal | |
superFinalize | superFinalize | |
DataFlow | nullReference | nullParam |
nullVar | ||
nullPtr | ||
zeroOperand | zeroOperand | |
zeroResult | zeroResult | |
domain | shiftCount | |
shiftRange | ||
conversion | ||
truncation | truncation | |
overflow | overflow | |
redundand | sameResult | |
disjointMask | ||
noEffect | ||
shortCharCmp | shortCharCmp | |
stringCmp | stringCmp | |
weakCmp | weakCmp | |
incompCase | incompCase | |
bounds | negLen | |
maybeNegLen | ||
badIndex | ||
maybeBadIndex |
To use Jlint you need to compile first you Java sources to byte code. As far as format of Java class is standard, you can use any available Java compiler. It is preferable to make compiler to include debug information in compiled classes (line table and local variables mapping). In this case Jlint messages will be more detailed. If your are using Sun javac compiler, required option is -g. Most of compilers by default includes line table, but do not generate local variable table. For example free Java compiler guavac can't generate it at all. Some compilers (like Sun's javac) can't generate line table if optimization is switch on. If you specify -verbose option to Jlint, it will report when it can't find line or local variable table in the class file.
Now Jlint and AntiC produce message in Emacs format: "file:line: message text". So it is possible to walk through these messages in Emacs if you start Jlint or AntiC as compiler. You can change prefix MSG_LOCATION_PREFIX (defined in jlint.h) from "%0s:%1d: " to one recognized by your favorite editor or IDE. All Jlint messages are gathered in file jlint.msg, so you can easily change them (but recompilation is needed).
AntiC also includes in the message position in the line.
All AntiC messages are produced
by function message_at(int line, int coln, char* msg)
,
defined in file antic.c.
You can change format of reported messages
by modifying this function.
Look for new version at my homepage | E-mail me about bugs and problems