How to get javac flags from a compiled class - java

Is it possible to get javac flags that was set when a class was compiled? I mean I have a 3rd party library and I can't debug its class in Eclipse because line: not available.
I would like to see what flags was used when the library was compiled.
Here is a similar problem I have. Its author writes:
Looking at javac compile flags, I see the -g:none flag.
So is it possible to see those flags?

You can do it with javap util. Run it with -v flag. If src was compiled with -g:source option javap will print file path in the first line of the output
Classfile /D:/workspace/x/target/classes/test/Test2.class
...
if -g:vars was used then you will see var names in LocalVariableTable
LocalVariableTable:
Start Length Slot Name Signature
0 31 0 args [Ljava/lang/String;
...
if -g:lines was used then javap will print LineNumberTable
LineNumberTable:
line 12: 0
...
You can also read these things programmatically with eg BCEL:
JavaClass jc = Repository.lookupClass(Test2.class);
String sf = jc.getSourceFileName();
Method m = jc.getMethods()[0];
LineNumberTable ln = m.getLineNumberTable();
LocalVariableTable lvt = m.getLocalVariableTable();

Related

Why does a Java class compile differently with a blank line?

I have the following Java class
public class HelloWorld {
public static void main(String []args) {
}
}
When I compile this file and run a sha256 on the resulting class file I get
9c8d09e27ea78319ddb85fcf4f8085aa7762b0ab36dc5ba5fd000dccb63960ff HelloWorld.class
Next I modified the class and added a blank line like this:
public class HelloWorld {
public static void main(String []args) {
}
}
Again I ran a sha256 on the output expecting to get the same result but instead I got
11f7ad3ad03eb9e0bb7bfa3b97bbe0f17d31194d8d92cc683cfbd7852e2d189f HelloWorld.class
I have read on this TutorialsPoint article that:
A line containing only white space, possibly with a comment, is known as a blank line, and Java totally ignores it.
So my question is, since Java ignores blank lines why is the compiled bytecode different for both programs?
Namely the difference in that in HelloWorld.class a 0x03 byte is replaced by a 0x04 byte.
Basically, line numbers are kept for debugging, so if you change your source code the way you did, your method starts at a different line and the compiled class reflects the difference.
You can see the change by using javap -v which will output verbose information. Like other already mentioned the difference will be in line numbers:
$ javap -v HelloWorld.class > with-line.txt
$ javap -v HelloWorld.class > no-line.txt
$ diff -C 1 no-line.txt with-line.txt
*** no-line.txt 2018-10-03 11:43:32.719400000 +0100
--- with-line.txt 2018-10-03 11:43:04.378500000 +0100
***************
*** 2,4 ****
Last modified 03-Oct-2018; size 373 bytes
! MD5 checksum 058baea07fb787bdd81c3fb3f9c586bc
Compiled from "HelloWorld.java"
--- 2,4 ----
Last modified 03-Oct-2018; size 373 bytes
! MD5 checksum 435dbce605c21f84dda48de1a76e961f
Compiled from "HelloWorld.java"
***************
*** 50,52 ****
LineNumberTable:
! line 3: 0
LocalVariableTable:
--- 50,52 ----
LineNumberTable:
! line 4: 0
LocalVariableTable:
More precisely the class file differs in the LineNumberTable section:
The LineNumberTable attribute is an optional variable-length attribute in the attributes table of a Code attribute (ยง4.7.3). It may be used by debuggers to determine which part of the code array corresponds to a given line number in the original source file.
If multiple LineNumberTable attributes are present in the attributes table of a Code attribute, then they may appear in any order.
There may be more than one LineNumberTable attribute per line of a source file in the attributes table of a Code attribute. That is, LineNumberTable attributes may together represent a given line of a source file, and need not be one-to-one with source lines.
The assumption that "Java ignores blank lines" is wrong. Here is a code snippet that behaves differently depending on the number of empty lines before the method main:
class NewlineDependent {
public static void main(String[] args) {
int i = Thread.currentThread().getStackTrace()[1].getLineNumber();
System.out.println((new String[]{"foo", "bar"})[((i % 2) + 2) % 2]);
}
}
If there are no empty lines before main, it prints "foo", but with one empty line before main, it prints "bar".
Since the runtime behavior is different, the .class files must be different, regardless of any timestamps or other metadata.
This holds for every language that has access to the stack frames with line numbers, not only for Java.
Note: if it's compiled with -g:none (without any debugging information), then the line numbers will not be included, getLineNumber() always returns -1, and the program always prints "bar", regardless of the number of line breaks.
As well as any line number details for debugging, your manifest may also store the build time and date. This will naturally be different every time you compile.

Losing java main args when using getopts in launch script

I have a script with 5 mandatory parameters (5 paths) and 3 options (-d for debug, -l for log4j override, -s for another override).
I'm managing it with getopts. The following script is simplified :
LOG4J_FILE=$DEFAULT_LOG4J_FILE
S_FILE=$DEFAULT_S_FILE
ECLIPSE_PROPS=
while getopts "l:s:d" flag; do
case "$flag" in
l) LOG4J_FILE="$OPTARG";;
s) S_FILE="$OPTARG";;
d) ECLIPSE_PROPS="-Xdebug ...";;
:) usage;;
?) usage;;
esac
done
shift $((OPTIND-1))
OPTIND=1
...
echo_and_eval $JAVA $ECLIPSE_PROPS -Dlog4.configuration=$LOG4J_FILE -Ds.file=$S_FILE -cp $CLASSPATH $MAIN_CLASS $ARGS
If I just put the 5 parameters, it works.
If I add one or two optional with parameters (l or s), it works.
If I add the -d option, I have no args in the Java main method.
Any clue ? This is driving me crazy.
It's OPTARG not OPTARGS -- http://www.gnu.org/software/bash/manual/bashref.html#index-getopts
l) LOG4J_FILE="$OPTARG";;
I would encourage you to get into the habit of quoting ALL your variables, that will protect any that contain whitespace or globbing chars:
java "$ECLIPSE_PROPS" -Dlog4.configuration="$LOG4J_FILE" -Ds.file="$S_FILE" -cp "$CLASSPATH" Main "$1" "$2" "$3" "$4" "$5"
Simply using echo to output the command line isn't going to show you how it gets parsed into individual arguments. foo 'bar baz' and foo bar baz are two very different commands, but they look the same when echoed.
Clearly, something in the actual value of the ECLIPSE_PROPS variable (that is, the argument to -d) is preventing the later arguments from being passed to the Java main method. If you supplied the actual code and actual values, we might be able to help you determine what that is.

java undefined reference to `main' collect2: ld returned 1 exit status

I have a problem compiling a java file.
My command for compile: javac -g HelloWorld.java
I wrote a simple hello world program:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World");
}
}
I'm using putty, and I'm connecting with ssh. I am getting this error:
/usr/lib64/gcc/x86_64-suse-linux/4.6/../../../../lib64/crt1.o: In function `_start':
/home/abuild/rpmbuild/BUILD/glibc-2.14.1/csu/../sysdeps/x86_64/elf/start.S:109: undefined reference to `main'
collect2: ld returned 1 exit status
As I see, you are using the gcc javac compiler, which doesn't work as the SUN/Oracle implementation.
I guess you need to add --main=HelloWorld to the command. Since several classes may have a 'main' method, the linker needs to be told which one to use.
Also, you might want to add -o , otherwise, you'll get a 'a.out' file, which is the default executable name for the GNU compilers (and any C compiler actually).
Hope this helps!

Error: Could not find or load main class at one call and no error at another call in the same script

I am working on an implementation of ESA, I changed one of the java files, compiled it using the command
javac -cp lib/*:esalib.jar ./src/clldsystem/esa/ESAAnalyzer.java
and pasted the .class file to the corresponding directory(esalib.jar/clldsystem/esa) in the .jar file. Also i changed the name the original corresponding .class file.
Next, i have a python script which uses a command
java -cp lib/*:esalib.jar clldsystem.esa.ESAAnalyzer param1 param2
but on running the python script, the command runs fine when used as:
x='java -cp "lib/*:esalib.jar" clldsystem.esa.ESAAnalyzer computer apple'
args=shlex.split(x)
p=subprocess.Popen(args)
p.wait()
But gives an Error: Could not find or load main class when used as:
x='java -cp "lib/*:esalib.jar" clldsystem.esa.ESAAnalyzer word1 word2'
args=x.split()
p=subprocess.Popen(args)
p.wait()
in the same script. I am reading the variables word1 and word2 from a file.
Why would it work fine at one place and give an error at other? I have checked using print statements that i am reading from the file correctly, so that must not be an issue.
Thanks
here are the details of what happened:
nishant#nishant-Inspiron-1545:~/esalib$ python test.py
['java', '-cp', 'lib/*:esalib.jar', 'clldsystem.esa.ESAAnalyzer', 'bottle', 'apple']
index loaded to memory
bottl
appl
vector 1 dimensions: 1782
vector 2 dimensions: 2766
0.024397644631615697
beach
['people', 'sand', 'desert', 'snow']
['java', '-cp', '"lib/*:esalib.jar"', 'clldsystem.esa.ESAAnalyzer', 'word1', 'word2']
Error: Could not find or load main class clldsystem.esa.ESAAnalyzer
beach
people
['java', '-cp', '"lib/*:esalib.jar"', 'clldsystem.esa.ESAAnalyzer', 'word1', 'word2']
Error: Could not find or load main class clldsystem.esa.ESAAnalyzer
beach
sand
and so on for every iteration
this works:
x='java -cp "lib/*:esalib.jar" clldsystem.esa.ESAAnalyzer %s %s' % (word1, word2)
args=shlex.split(x)
print args
p=subprocess.Popen(args)
p.wait()

Space in Java command-line arguments

In my Java command-line arguments, any characters after space get ignored. For example,
java test.AskGetCampaignByName "Dummy books"
I get the first argument (args[0]) as "Dummy" only. Single quotes also do not help.
Is there a workaround/fix for this? Could it be because of my terminal settings?
My $TERM is xterm, and $LANG is "en_IN".
The arguments are handled by the shell (I assume you are using Bash under Linux?), so any terminal settings should not affect this.
Since you already have quoted the argument, it ought to work. The only possible explanation I can think of is if your java command is a wrapper script and messes up the escaping of the arguments when passing on to the real program. This is easy to do, or perhaps a bit hard to do correctly.
A correct wrapper script should pass all its arguments on as ${1+"$#"}, and any other version is most likely a bug with regards to being able to handle embedded spaces properly. This is not uncommon to do properly, however also any occurrences of $2 or similar are troublesome and must be written as "$2" (or possibly ${2+"$2"}) in order to handle embedded spaces properly, and this is sinned against a lot.
The reason for the not-so-intuitive syntax ${1+"$#"} is that the original $* joined all arguments as "$1 $2 $3 ..." which did not work for embedded spaces. Then "$#" was introduced that (correctly) expanded to "$1" "$2" "$3" ... for all parameters and if no parameters are given it should expand to nothing. Unfortunately some Unix vendor messed up and made "$#" expand to "" even in case of no arguments, and to work around this the clever (but not so readable) hack of writing ${1+"$#"} was invented, making "$#" only expand if parameter $1 is set (i.e. avoiding expansion in case of no arguments).
If my wrapper assumption is wrong you could try to debug with strace:
strace -o outfile -f -ff -F java test.AskGetCampaignByName "Dummy books"
and find out what arguments are passed to execve. Example from running "strace /bin/echo '1 2' 3":
execve("/bin/echo", ["/bin/echo", "1 2", "3"], [/* 93 vars */]) = 0
brk(0) = 0x2400000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f420075b000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f420075a000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/usr/lib64/alliance/lib/tls/x86_64/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat("/usr/lib64/alliance/lib/tls/x86_64", 0x7fff08757cd0) = -1 ENOENT (No such file or directory)
open("/usr/lib64/alliance/lib/tls/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
...
In case your program needs more than positional arguments (= when the command line usage is important), you should consider options and switches. Apache Commons has a great library for this.
You just have to escape the spaces like this:
normal String: "Hello World!"
escaped String: "Hello" "World!"
That worked for me.
My environment:
23:39:19 Zarathustra#thora:/Users/Zarathustra~$bash -version
GNU bash, version 3.2.48(1)-release (x86_64-apple-darwin11)
Copyright (C) 2007 Free Software Foundation, Inc.
It sounds like you are using a operating system distribution where the java command available to the user is a wrapper which finds the right JVM "somewhere" and invokes it accordingly.
If so, it most likely does not escape the arguments properly when invoking the actual java executable.
What distribution do you use?
Just reassemble the arguments in your Java program:
StringBuilder allArgs = new StringBuilder();
for (int i=0; i < args.length; i++)
{
//System.out.println("arg"+i+": "+args[i]);
allArgs.append(args[i]+" ");
}
// Parse out the args the way you wish using allArgs

Categories