//-< JLINT.CC >------------------------------------------------------+--------+
// Jlint Version 2.0 (c) 1998 GARRET | ? |
// (Java Lint) | /\| |
// | / \ |
// Created: 28-Mar-98 K.A. Knizhnik | / [] \ |
// Last update: 15-Feb-01 Cyrille Artho | GARRET |
//-------------------------------------------------------------------+--------+
// Java verifier
//-------------------------------------------------------------------+--------+
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <stdarg.h>
#include <ctype.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <dirent.h>
#endif
#include "jlint.hh"
bool verbose = false;
int max_shown_paths = 4;
int n_messages;
char* source_file_path;
int source_file_path_len;
bool source_path_redefined = false;
int reported_message_mask = cat_all;
FILE* history;
string_pool stringPool;
field_desc* is_const;
message_descriptor msg_table[] = {
#define MSG(category, code, position_dependent, format) \
{cat_##category, MSG_LOCATION_PREFIX##format, #code, position_dependent, true},
#include "jlint.msg"
{cat_all}
};
unsigned int string_hash_function(byte* p) {
unsigned int h = 0, g;
while(*p) {
h = (h << 4) + *p++;
if ((g = h & 0xF0000000) != 0) {
h ^= g >> 24;
}
h &= ~g;
}
return h;
}
message_node* message_node::hash_table[1023];
//
// Functions for extracting information from type descriptors
//
void message_at(int code, utf_string const& file, int line, ...)
{
va_list ap;
va_start(ap, line);
format_message(code, file, line, ap);
va_end(ap);
}
int get_number_of_parameters(utf_string const& str)
{
const char* p = str.as_asciz();
assert(*p++ =='(');
int n_params = 0;
while (*p != ')') {
switch (*p++) {
case '[':
while (*p == '[') p += 1;
if (*p++ == 'L') {
while (*p++ != ';');
}
n_params += 1;
break;
case 'D':
case 'J':
n_params += 2;
break;
case 'L':
while (*p++ != ';');
nobreak;
default:
n_params += 1;
}
}
return n_params;
}
int get_type(utf_string const& str)
{
const char* p = str.as_asciz();
if (*p == '(') {
while (*++p != ')');
p += 1;
}
int indirect = 0;
while (*p == '[') {
p += 1;
indirect += 1;
}
type_tag tag = tp_object;
switch (*p) {
case 'I': tag = tp_int; break;
case 'S': tag = tp_short; break;
case 'D': tag = tp_double; break;
case 'J': tag = tp_long; break;
case 'F': tag = tp_float; break;
case 'V': tag = tp_void; break;
case 'B': tag = tp_byte; break;
case 'C': tag = tp_char; break;
case 'Z': tag = tp_bool; break;
default:
if (strcmp(p, "Ljava/lang/String;") == 0) tag = tp_string;
}
return int(tag) + (indirect << 8);
}
//
// All messages are reported by this function
//
void format_message(int code, utf_string const& file, int line, __VALIST ap)
{
static int loop_id;
static message_node *first, *last;
static char* compound_message;
const void* parameter[MAX_MSG_PARAMETERS];
int n_parameters = 2;
if (code == msg_loop || code == msg_sync_loop) { // extract loop identifier
parameter[n_parameters++] = va_arg(ap, void*);
}
if (history != NULL) {
if (compound_message != NULL
&& ((loop_id != 0
&& ((code != msg_loop && code != msg_sync_loop)
|| (int)parameter[2] != loop_id))
|| (loop_id == 0 && code != msg_wait_path)))
{
if (!message_node::find(compound_message)) {
message_node *msg = first, *next;
do {
next = msg->next;
fprintf(stdout, "%s\n", msg->text);
delete msg;
n_messages += 1;
} while ((msg = next) != NULL);
fprintf(history, "%s\n", compound_message);
}
delete[] compound_message;
compound_message = NULL;
}
}
if ((reported_message_mask & msg_table[code].category)
&& msg_table[code].enabled)
{
static int prev_line = 0;
char msg_buf[MAX_MSG_LENGTH];
char his_buf[MAX_MSG_LENGTH];
char* hp = his_buf;
if (line == 0) {
line = ++prev_line; // avoid several messages with 0 line number
} else {
prev_line = 0;
}
parameter[0] = file.as_asciz();
parameter[1] = (void*)line;
if (history) {
hp += sprintf(hp, "%s", msg_table[code].name);
}
char const* src = msg_table[code].format;
char* dst = msg_buf;
if (code == msg_done) {
// do not output location prefix for termination message
src += strlen(MSG_LOCATION_PREFIX);
parameter[0] = (void*)n_messages;
}
while (*src != 0) {
if (*src == '%') {
int index;
int pos;
int n = sscanf(++src, "%d%n", &index, &pos);
assert(n == 1);
assert(index < MAX_MSG_PARAMETERS);
while (index >= n_parameters) {
parameter[n_parameters++] = va_arg(ap, void*);
}
src += pos;
char* save_dst = dst;
switch (*src++) {
case 's': // zero terminated string
dst += sprintf(dst, "%s", (char*)parameter[index]);
break;
case 'm': // method descriptor
dst += ((method_desc*)parameter[index])->
demangle_method_name(dst);
break;
case 'u': // utf8 string
dst += sprintf(dst, "%s",
((utf_string*)parameter[index])->as_asciz());
break;
case 'c': // class descriptor
dst += sprintf(dst, "%s", ((class_desc*)parameter[index])->
name.as_asciz());
break;
case 'd': // integer
dst += sprintf(dst, "%d", (int)parameter[index]);
break;
default:
assert(false/*bad message parameter format*/);
}
if (history) {
// append parameeter to history buffer
if ((index >= 2 || msg_table[code].position_dependent)
&& (code != msg_sync_loop || index > 2)
&& (code != msg_loop || index > 3))
{
// Do not inlude loop number in history message
hp += sprintf(hp, ":%.*s", dst - save_dst, save_dst);
}
}
} else {
*dst++ = *src++;
}
}
*dst++ = '.';
*dst = 0;
if (history != NULL) {
if (compound_message != NULL) {
char* new_msg = new char[strlen(compound_message)
+strlen(his_buf)+2];
sprintf(new_msg, "%s;%s", compound_message, his_buf);
last = last->next = new message_node(msg_buf);
delete[] compound_message;
compound_message = new_msg;
} else {
if (code == msg_loop || code == msg_sync_loop
|| code == msg_wait)
{
compound_message = strdup(his_buf);
first = last = new message_node(msg_buf);
if (code != msg_wait) {
loop_id = (int)parameter[2];
}
} else if (!message_node::find(his_buf)) {
fprintf(stdout, "%s\n", msg_buf);
if (code != msg_done) {
fprintf(history, "%s\n", his_buf);
n_messages += 1;
}
}
}
} else {
fprintf(stdout, "%s\n", msg_buf);
if (code != msg_done) {
n_messages += 1;
}
}
}
}
graph_vertex* graph_vertex::graph;
int graph_vertex::n_vertexes;
//
// Methods of class_desc class
//
class_desc* class_desc::hash_table[class_hash_table_size];
class_desc* class_desc::chain;
int class_desc::n_classes;
void set_class_source_path(class_desc* cls)
{
if (source_file_path_len != 0) {
const char* class_file_name = cls->source_file.as_asciz();
if (!source_path_redefined) {
char* dirend = strrchr(class_file_name, '/');
if (dirend != NULL) {
int dirlen = dirend - class_file_name;
if (dirlen <= source_file_path_len
&& memcmp(class_file_name,
source_file_path + source_file_path_len - dirlen,
dirlen) == 0)
{
class_file_name = dirend+1;
}
}
}
char file_name_buf[MAX_MSG_LENGTH];
int len = sprintf(file_name_buf, "%.*s%c%s",
source_file_path_len, source_file_path,
FILE_SEP, class_file_name);
cls->source_file = utf_string(len, (byte*)file_name_buf);
}
}
bool parse_class_file(byte* fp)
{
int i;
unsigned magic = unpack4(fp);
fp += 4;
if (magic != 0xCAFEBABE) return false;
int minor_version = unpack2(fp);
fp += 2;
int major_version = unpack2(fp);
fp += 2;
int constant_pool_count = unpack2(fp);
fp += 2;
constant** constant_pool = new constant*[constant_pool_count];
memset(constant_pool, 0, sizeof(constant*)*constant_pool_count);
int name_index = 0;
for (i = 1; i < constant_pool_count; i++) {
constant* cp = NULL;
int n_extra_cells = 0;
switch (*fp) {
case c_utf8:
cp = new const_utf8(fp);
if (!strcmp(((const_utf8*)cp)->as_asciz(), "this")) {
name_index = i;
}
break;
case c_integer:
cp = new const_int(fp);
break;
case c_float:
cp = new const_float(fp);
break;
case c_long:
cp = new const_long(fp);
n_extra_cells += 1;
break;
case c_double:
cp = new const_double(fp);
n_extra_cells += 1;
break;
case c_class:
cp = new const_class(fp);
break;
case c_string:
cp = new const_string(fp);
break;
case c_field_ref:
case c_method_ref:
case c_interface_method_ref:
cp = new const_ref(fp);
break;
case c_name_and_type:
cp = new const_name_and_type(fp);
break;
}
assert(cp != NULL);
fp += cp->length();
constant_pool[i] = cp;
i += n_extra_cells;
}
int access_flags = unpack2(fp);
fp += 2;
int this_class_name = unpack2(fp);
fp += 2;
int super_class_name = unpack2(fp);
fp += 2;
int interfaces_count = unpack2(fp);
fp += 2;
class_desc* this_class =
class_desc::get(*(const_utf8*)constant_pool
[((const_class*)constant_pool[this_class_name])->name]);
set_class_source_path(this_class);
// init. is_this
field_desc* is_this = new field_desc(utf_string("<this>"), this_class, NULL);
// assign name_and_type for is_this - find name entry "this" and obj. type
is_this->name_and_type =
new const_name_and_type(name_index,
((const_class*)constant_pool[this_class_name])->name);
is_this->equals = is_this;
is_this->cls = this_class;
// init. is_const
field_desc* is_const = new field_desc(utf_string("<const>"), NULL, NULL);
this_class->attr = access_flags;
if (super_class_name == 0) { // Object class
assert(interfaces_count == 0);
this_class->n_bases = 0;
} else {
this_class->n_bases = interfaces_count + 1;
this_class->bases = new class_desc*[interfaces_count + 1];
this_class->bases[0] =
class_desc::get(*(const_utf8*)constant_pool
[((const_class*)constant_pool[super_class_name])->name]);
for (i = 1; i <= interfaces_count; i++) {
int interface_name = unpack2(fp);
fp += 2;
this_class->bases[i] =
class_desc::get(*(const_utf8*)constant_pool
[((const_class*)constant_pool[interface_name])->name]);
}
}
int fields_count = unpack2(fp);
fp += 2;
while (--fields_count >= 0) {
int access_flags = unpack2(fp); fp += 2;
int name_index = unpack2(fp); fp += 2;
int desc_index = unpack2(fp); fp += 2;
int attr_count = unpack2(fp); fp += 2;
field_desc* field =
this_class->get_field(*(const_utf8*)constant_pool[name_index]);
field->attr |= access_flags;
while (--attr_count >= 0) {
int attr_len = unpack4(fp+2);
fp += 6 + attr_len;
}
}
int methods_count = unpack2(fp);
fp += 2;
//
// We need "SourceFile" attribute first, so remember position and
// skip methods definitions
//
byte* method_part = fp;
for (i = 0; i < methods_count; i++) {
int attr_count = unpack2(fp+6);
fp += 8;
while (--attr_count >= 0) {
int attr_len = unpack4(fp+2);
fp += 6 + attr_len;
}
}
int attr_count = unpack2(fp); fp += 2;
while (--attr_count >= 0) {
int attr_name = unpack2(fp); fp += 2;
int attr_len = unpack4(fp); fp += 4;
utf_string attr(*(const_utf8*)constant_pool[attr_name]);
if (attr == "SourceFile") {
int source_name = unpack2(fp); fp += 2;
int file_name_idx = this_class->source_file.rindex(FILE_SEP);
utf_string* file_name = (const_utf8*)constant_pool[source_name];
if (file_name_idx >= 0) {
this_class->source_file.append(file_name_idx+1, *file_name);
} else {
this_class->source_file = *file_name;
}
} else {
fp += attr_len;
}
}
fp = method_part;
while (--methods_count >= 0) {
int access_flags = unpack2(fp); fp += 2;
int name_index = unpack2(fp); fp += 2;
int desc_index = unpack2(fp); fp += 2;
int attr_count = unpack2(fp); fp += 2;
const_utf8* mth_name = (const_utf8*)constant_pool[name_index];
const_utf8* mth_desc = (const_utf8*)constant_pool[desc_index];
method_desc* method = this_class->get_method(*mth_name, *mth_desc);
method->attr |= access_flags;
method->vars = NULL;
while (--attr_count >= 0) {
int attr_name = unpack2(fp); fp += 2;
int attr_len = unpack4(fp); fp += 4;
utf_string attr(*(const_utf8*)constant_pool[attr_name]);
if (attr == "Code") {
int max_stack = unpack2(fp); fp += 2;
int max_locals = unpack2(fp); fp += 2;
int code_length = unpack4(fp); fp += 4;
method->code = fp;
method->code_length = code_length;
fp += code_length;
method->n_vars = max_locals;
method->vars = max_locals > 0 ? new var_desc[max_locals] : NULL;
method->line_table = new word[code_length];
memset(method->line_table, 0, sizeof(word)*code_length);
method->context = new local_context*[code_length+1];
memset(method->context, 0,
sizeof(local_context*)*(code_length+1));
int exception_table_length = unpack2(fp); fp += 2;
while (--exception_table_length >= 0) {
int handler_pc = unpack2(fp+4);
new ctx_entry_point(&method->context[handler_pc]);
fp += 8;
}
int method_attr_count = unpack2(fp); fp += 2;
bool line_table_present = false;
bool local_var_table_present = false;
while (--method_attr_count >= 0) {
int mth_attr_name = unpack2(fp); fp += 2;
int mth_attr_len = unpack4(fp); fp += 4;
utf_string* attr =
(const_utf8*)constant_pool[mth_attr_name];
if (*attr == "LineNumberTable") {
int table_length = unpack2(fp); fp += 2;
while (--table_length >= 0) {
int start_pc = unpack2(fp); fp += 2;
int line_no = unpack2(fp); fp += 2;
method->line_table[start_pc] = line_no;
}
method->first_line = method->line_table[0];
if (method->first_line != 0) method->first_line -= 1;
line_table_present = true;
} else if (*attr == "LocalVariableTable") {
method->local_variable_table_present = true;
int table_length = unpack2(fp); fp += 2;
while (--table_length >= 0) {
int start_pc = unpack2(fp); fp += 2;
int length = unpack2(fp); fp += 2;
int name = unpack2(fp); fp += 2;
int desc = unpack2(fp); fp += 2;
int index = unpack2(fp); fp += 2;
int type = (index == 0 &&
!(access_flags & method_desc::m_static))
? tp_self
: get_type(*(const_utf8*)constant_pool[desc]);
new ctx_push_var(&method->context[start_pc],
(const_utf8*)constant_pool[name],
type, index, start_pc);
new ctx_pop_var(&method->context[start_pc+length],
index);
}
local_var_table_present = true;
} else {
fp += mth_attr_len;
}
}
if (verbose && !(access_flags & method_desc::m_native)) {
char buf[MAX_MSG_LENGTH];
method->demangle_method_name(buf);
if (!line_table_present) {
fprintf(stderr, "No line table present "
"for method %s\n", buf);
}
if (!local_var_table_present) {
fprintf(stderr, "No local variable table present "
"for method %s\n", buf);
}
}
method->parse_code(constant_pool, is_this);
} else {
fp += attr_len;
}
}
}
for (i = 1; i < constant_pool_count; i++) {
if (constant_pool[i] != NULL)
delete constant_pool[i];
}
/*
for (method_desc* m = this_class->methods; m != NULL; m = m->next) {
m->locksAtEntry.clear();
}
*/
delete[] constant_pool;
delete is_this->name_and_type;
//delete is_this;
delete is_const;
monitor_stack::const_iterator it;
for (it = this_class->usedLocks.begin();
it != this_class->usedLocks.end(); it++) {
if (!((*it)->writes.empty())) {
vector<int>::const_iterator i;
for (i = (*it)->writes.begin(); i != (*it)->writes.end(); i++) {
message_at(msg_lock_assign, this_class->source_file, *i, *it);
// *i: line number; *it = field_desc*
}
}
}
return true;
}
//
// Determine type of file and extract Java class description from the file
//
inline int stricmp(const char* p, const char* q)
{
do {
while (*p == '_') p += 1; // ignore '_'
while (*q == '_') q += 1; // ignore '_'
int diff = toupper(*(byte*)p) - toupper(*(byte*)q);
if (diff != 0) {
return diff;
}
q += 1;
} while (*p++ != 0);
return 0;
}
void proceed_file(char* file_name, bool recursive = false)
{
#ifdef _WIN32
HANDLE dir;
char dir_path[MAX_PATH];
WIN32_FIND_DATA file_data;
if (recursive != 0) {
sprintf(dir_path, "%s\\*", file_name);
if ((dir=FindFirstFile(dir_path, &file_data)) != INVALID_HANDLE_VALUE)
{
file_name = dir_path;
}
} else {
if (strcmp(file_name, "..") == 0 || strcmp(file_name, ".") == 0) {
proceed_file(file_name, 1);
return;
}
if ((dir=FindFirstFile(file_name, &file_data)) == INVALID_HANDLE_VALUE)
{
fprintf(stderr, "Failed to open file '%s'\n", file_name);
return;
}
}
if (dir != INVALID_HANDLE_VALUE) {
do {
if (!recursive || *file_data.cFileName != '.') {
char file_path[MAX_PATH];
char* file_dir = strrchr(file_name, '\\');
char* file_name_with_path;
if (file_dir != NULL) {
int dir_len = file_dir - file_name + 1;
memcpy(file_path, file_name, dir_len);
strcpy(file_path+dir_len, file_data.cFileName);
file_name_with_path = file_path;
} else {
file_name_with_path = file_data.cFileName;
}
proceed_file(file_name_with_path, recursive+1);
}
} while (FindNextFile(dir, &file_data));
FindClose(dir);
return;
}
#else
DIR* dir = opendir(file_name);
if (dir != NULL) {
struct dirent* entry;
while ((entry = readdir(dir)) != NULL) {
if (*entry->d_name != '.') {
char full_name[MAX_PATH];
sprintf(full_name, "%s/%s", file_name, entry->d_name);
proceed_file(full_name, 2);
}
}
closedir(dir);
return;
}
#endif
if (recursive >= 2) {
int len = strlen(file_name);
if (len < 6 || stricmp(file_name + len - 6, ".class") != 0) {
return;
}
}
FILE* f = fopen(file_name, "rb");
if (f == NULL) {
fprintf(stderr, "Failed to open file '%s'\n", file_name);
return;
}
char* suf = file_name + strlen(file_name) - 4;
if (suf > file_name && (stricmp(suf,".zip")==0 || stricmp(suf,".jar")==0)){
// extract files from ZIP archive
byte hdr[ECREC_SIZE+4];
if (fseek(f, 0, SEEK_END) != 0
|| ftell(f) < ECREC_SIZE+4
|| fseek(f, -(ECREC_SIZE+4), SEEK_CUR) != 0
|| fread(hdr, ECREC_SIZE+4, 1, f) != 1)
{
fprintf(stderr, "Bad format of ZIP file '%s'\n", file_name);
return;
}
int count = unpack2_le(&hdr[TOTAL_ENTRIES_CENTRAL_DIR]);
int dir_size = unpack4_le(&hdr[SIZE_CENTRAL_DIRECTORY]);
byte* directory = new byte[dir_size];
if (fseek(f, -(dir_size+ECREC_SIZE+4), SEEK_CUR) != 0
|| fread(directory, dir_size, 1, f) != 1)
{
fprintf(stderr, "Bad format of ZIP file '%s'\n", file_name);
delete[] directory;
return;
}
byte* dp = directory;
while (--count >= 0) {
int uncompressed_size = unpack4_le(&dp[4+C_UNCOMPRESSED_SIZE]);
int filename_length = unpack2_le(&dp[4+C_FILENAME_LENGTH]);
int cextra_length = unpack2_le(&dp[4+C_EXTRA_FIELD_LENGTH]);
if ((dp-directory)+filename_length+CREC_SIZE+4 > dir_size) {
fprintf(stderr, "Bad format of ZIP file '%s'\n", file_name);
break;
}
byte local_rec_buffer[LREC_SIZE+4];
int local_header_offset =
unpack4_le(&dp[4+C_RELATIVE_OFFSET_LOCAL_HEADER]);
if (fseek(f, local_header_offset, SEEK_SET) != 0
|| fread(local_rec_buffer, sizeof local_rec_buffer, 1, f) != 1
|| memcmp(&local_rec_buffer[1], LOCAL_HDR_SIG, 3) != 0)
{
fprintf(stderr, "Bad format of ZIP file '%s'\n", file_name);
break;
}
int file_start = local_header_offset + (LREC_SIZE+4) +
unpack2_le(&local_rec_buffer[4+L_FILENAME_LENGTH]) +
unpack2_le(&local_rec_buffer[4+L_EXTRA_FIELD_LENGTH]);
int filename_offset = CREC_SIZE+4;
if (uncompressed_size != 0) {
byte* buffer = new byte[uncompressed_size];
if (fseek(f, file_start, SEEK_SET) != 0
|| fread(buffer, uncompressed_size, 1, f) != 1)
{
fprintf(stderr, "Failed to read ZIP file '%s'\n",
file_name);
return;
} else {
if (verbose) {
fprintf(stderr, "Verify file '%.*s'\n",
filename_length, dp + filename_offset);
}
if (!parse_class_file(buffer)) {
fprintf(stderr, "File '%.*s' isn't correct Java class "
"file\n", filename_length, dp+filename_offset);
}
}
delete[] buffer;
}
dp += filename_offset + filename_length + cextra_length;
}
delete[] directory;
} else {
fseek(f, 0, SEEK_END);
size_t file_size = ftell(f);
fseek(f, 0, SEEK_SET);
byte* buffer = new byte[file_size];
if (fread(buffer, file_size, 1, f) != 1) {
fprintf(stderr, "Failed to read file '%s'\n", file_name);
} else {
if (verbose) {
fprintf(stderr, "Verify file '%s'\n", file_name);
}
if (!parse_class_file(buffer)) {
fprintf(stderr, "File '%s' isn't correct Java class file\n",
file_name);
}
}
delete[] buffer;
}
fclose(f);
}
//
// Main function: parse command line
//
msg_select_category_option msg_category_option[] = {
{cat_synchronization, "synchronization",
"detect synchronizational problems"},
{cat_deadlock, "deadlock",
"possible deadlock detection"},
{cat_race_condition, "race_condition",
"possible race condition detection"},
{cat_wait_nosync, "wait_nosync",
"wait or notify is called from non-synchroize method"},
{cat_inheritance, "inheritance",
"detect problems with class/interface inheritance"},
{cat_super_finalize, "super_finalize",
"super.finalize() is not called from finalize method"},
{cat_not_overridden, "not_overridden",
"methods with the same names and diffent profiles"},
{cat_field_redefined, "field_redefined",
"instance or static variable is redifined in derived class"},
{cat_shadow_local, "shadow_local",
"local variable shadow one in exterior scope"},
{cat_data_flow, "data_flow",
"perform data flow analysis to detect suspicious operations"},
{cat_null_reference, "null_reference",
"detect access to variables with possible NULL value"},
{cat_zero_operand, "zero_operand",
"one of operands of binary integer operation is zero"},
{cat_zero_result, "zero_result",
"result of operation is always zero"},
{cat_redundant, "redundant",
"operations always produce the same result"},
{cat_overflow, "overflow",
"detect incorrect handling of operation overflow"},
{cat_incomp_case, "incomp_case",
"switch case label can't be produced by switch expression"},
{cat_short_char_cmp, "short_char_cmp",
"expresion of char type is compared with one of short type"},
{cat_string_cmp, "string_cmp",
"two strings are compared as object references"},
{cat_weak_cmp, "weak_cmp",
"using of inequality comparison where equality can be checked"},
{cat_domain, "domain",
"operands doesn't belong to the domain of operator"},
{cat_truncation, "truncation",
"data can be lost as a result of type conversion"},
{cat_bounds, "bounds",
"array index or length is out of range"},
{cat_done, "done",
"notification about verification completion"},
{cat_all, "all",
"produce messages of all categories"}
};
void usage()
{
fprintf(stderr, "\
Usage: jlint {option|java_class_file}\n\
Options:\n\
-help : print this text\n\
-source <source_path> : path to directory with .java files\n\
-history <file_name> : use history file to avoid repeated messages\n\
-max_shown_paths <number> : max. shown paths between two sync. methods\n\
(+|-)verbose : output more/less information about program activity\n\
(+|-)message_category : select categories of messages to be reported\n\
(+|-)message_code : enable/disable concrete message\n\
Message categories:\n");
message_category group = msg_category_option[0].msg_cat;
for (unsigned i = 0; i < items(msg_category_option); i++) {
message_category msg_cat = msg_category_option[i].msg_cat;
if ((msg_cat & ~group) != 0) {
group = msg_cat;
fprintf(stderr, "\n");
}
fprintf(stderr, " %s : %s\n",
msg_category_option[i].cat_name,
msg_category_option[i].cat_desc);
}
if (verbose) {
fprintf(stderr, "\nMessages: (category:code: \"text\")\n");
for (int i = 0; i < msg_last; i++) {
for (unsigned j = 0; j < items(msg_category_option); j++) {
if (msg_table[i].category == msg_category_option[j].msg_cat) {
fprintf(stderr, " %s:%s: \"%s\"\n",
msg_category_option[j].cat_name,
msg_table[i].name, msg_table[i].format);
break;
}
}
}
}
}
int main(int argc, char* argv[])
{
int i, j;
if (argc == 1) {
usage();
return EXIT_FAILURE;
}
for (i = 1; i < argc; i++) {
if (*argv[i] == '-' || *argv[i] == '+') {
bool enabled = *argv[i] == '+';
char* option = &argv[i][1];
int n_cat = items(msg_category_option);
msg_select_category_option* msg = msg_category_option;
if (stricmp(option, "source") == 0 && i+1 < argc) {
source_file_path = argv[++i];
source_file_path_len = strlen(source_file_path);
if (source_file_path[source_file_path_len] == FILE_SEP) {
source_file_path_len -= 1;
}
source_path_redefined = true;
continue;
}
if (stricmp(option, "history") == 0 && i+1 < argc) {
history = fopen(argv[++i], "a+");
if (history != NULL) {
char his_buf[MAX_MSG_LENGTH];
fseek(history, 0, SEEK_SET);
while (fgets(his_buf, sizeof his_buf, history)) {
int len = strlen(his_buf);
if (len > 0) {
his_buf[len-1] = '\0';
message_node::add_to_hash(his_buf);
}
}
fseek(history, 0, SEEK_END);
} else {
fprintf(stderr, "Failed to open history file '%s'\n",
argv[i]);
}
continue;
}
if (stricmp(option, "max_shown_paths") == 0 && i+1 < argc) {
if (sscanf(argv[++i], "%d", &max_shown_paths) != 1) {
fprintf(stderr, "Bad parameter value for -max_shown_paths "
"option: '%s'\n", argv[i]);
}
continue;
}
if (stricmp(option, "verbose") == 0) {
verbose = enabled;
if (verbose) {
fprintf(stderr,
"Jlint - program correctness verifier for Java"
" version %3.2f ("__DATE__")\n", VERSION);
}
continue;
}
if (stricmp(option, "help") == 0) {
usage();
return EXIT_SUCCESS;
}
for (j = 0; j < n_cat; j++) {
if (stricmp(msg[j].cat_name, option) == 0) {
if (enabled) {
reported_message_mask |= msg[j].msg_cat;
} else {
reported_message_mask &= ~msg[j].msg_cat;
}
break;
}
}
if (j == n_cat) {
for (j = 0; j < msg_last; j++) {
if (stricmp(msg_table[j].name, option) == 0) {
msg_table[j].enabled = enabled;
break;
}
}
if (j == msg_last) {
fprintf(stderr, "Unrecognized option %s\n", argv[i]);
usage();
}
return EXIT_FAILURE;
}
} else {
char* file_name = argv[i];
if (!source_path_redefined) {
source_file_path = file_name;
char* dirend = strrchr(source_file_path, FILE_SEP);
source_file_path_len = (dirend != NULL)
? dirend - source_file_path : 0;
}
proceed_file(file_name);
}
}
class_desc::global_analysis();
message_at(msg_done, utf_string(""), 0, (void*)n_messages);
return EXIT_SUCCESS;
}