I am trying to do the same thing in Java 11 that could be done with -Xbootclasspath/p:path in pre java 9.
As a simple example I modified one of the valueOf methods of java.lang.Integer and compiled the project with:
javac --module-source-path=src/java.base --patch-module java.base=src/java.base -d mods $(find src -name '*.java')
I then ran a simple sample using:
java --patch-module java.base=<pathToMyModifiedJavaBaseClasses> -p lib -m my.moduleA/my.moduleA.Main
That worked an I'm seeing the modifications displayed (a simple print out I did from valueOf).
When I try, however, to do the same thing with java.lang.ClassLoader I get the following error when executing the program (compile works):
Error occurred during initialization of boot layer
java.lang.LinkageError: loader 'bootstrap' attempted duplicate class definition for java.lang.invoke.SimpleMethodHandle.
I do not even need to make changes in java.lang.ClassLoader. The sheer existence of that class in my patch folder seems to be provocing this error. (I only wanted to add a field though at the bottom of the class)
NOTE: I just figured that it works when the ClassLoader class is compiled with Eclipse. One of the few differences I know is that the Eclipse compiler does not seem to follow JEP 280 yet. But there are invokedynamic instructions in the bytecode resulting from javac as well, so I doubt that this is the problem.
You did already point into the right direction. It works when you compile the class with your current version of Eclipse, because that compiler does not follow JEP 280 yet, so it doesn’t use invokedynamic for string concatenation.
This does not imply that using invokedynamic in ClassLoader is problematic in general. It is only problematic in certain critical code paths executed during the bootstrapping of the java.lang.invoke package and apparently, this class does use string concatenation on this code path.
In case of javac, you can force the use of the old string concatenation code via the option
-XDstringConcat=inline. Looking into the bytecode of the ClassLoader.class as shipped with the JDK, it seems this class has been compiled with this option. In fact, looking at some samples, it seems the entire java.base module has been compiled with that option, in contrast to, e.g. java.desktop, whose classes use invokedynamic for string concatenation.
So the conclusion is, to patch classes in the java.base module (in OpenJDK and derivatives), compile them using the -XDstringConcat=inline option when using javac.
Related
Motivation:
In our code we have a few places where some methods are run by their name. There are some big if-else-if blocks with each function name and call of the corresponding method (I use the term function to describe just names, for example function X01 might correspond to method SomeClass.functionX01). I've been looking into ways to improve that
Goal:
Write just methods that are annotated with some custom annotation, removing the need to update or even include if-else-if blocks in order to run specific function. Have access to any generated code if any code is generated.
What I did:
I've created first prove of concept using runtime annotations and it proved successful, but slower then if-else-if. Next attempt was with source annotation
I've followed this link for an example, however it did not seam to run in IntelliJ. What I wanted is to have - in this case PersonBuilder class generated, instead there was none. In some cases an error was raised Error:java: Bad service configuration file, or exception thrown while constructing Processor object: javax.annotation.processing.Processor: Provider BuilderProcessor not found
After some Googling and failing to find anything I've turned to book (Core Java, Volume II - Advanced Features - 9th Edition, Polish translation) and there was reccomended to run the following commands:
javac [AbstractProcessor implementation]
javac -processor [Compiled Processor] [other source files to compile]
This worked, however is unsatisfactory as it needs to happen inside IDE (NetBeans and IntelliJ to be specific) automatically during build. Code does not need to be generated on the fly, but programmer must have access to it after build (as in - be able to call methods of generated classes)
Question:
How to have and use generated code used in NetBeans and IntelliJ without the need of using external tools? Is it possible, or using reflection, runtime annotations or external tools is the only way?
Additional info (just in case):
Language level: Java 1.8
JVM versions: 12 and 13
IDEs: NetBeans and IntelliJ
I can't guess how can I specify class, which is entry-point of my program (therefore shouldn't be obfuscated), and my jar archive. Please show me an command-line example, how to use JBCO when I have /home/example/myJar.jar and within it com.example.EntryPoint class and my external dependency /home/example/dependencies/dependencyJar.jar.
Also, please, does anybody know if this project is still alive and what jdk it supports?
A lot of time have passed, but recently I have passed across the java transformation frameworks and find out that JBCO now is a part of soot framework, hosted on GitHub, but it is #deprecated as for now. There is a wiki where you can get more info about how to use soot/jbco (if you still want to, on your own risk, even though JBCO is deprecated and not under active development it still from time to time accepts PRs from contributors).
As for the command line options it might be:
java -cp .:/home/example/sootclasses-trunk-jar-with-dependencies.jar soot.jbco.Main -process-dir /home/example/compiled -output-dir /home/example/obfuscated -soot-class-path .:/home/example/myJar.jar -output-format class -app -main-class com.example.EntryPoint -t:9:wjtp.jbco_cr
Soot can process your compiled code as class files (then pass it to -process-dir option) or as jar (then pass it as part of soot-class-path) - soot can process many forms of bytecode (java/scala/.. bytecode, android bytecode, jasmin, jimple). There are also options to specify what is library classes and application or argument classes more precisely, for more info please refer to soot's wiki page.
I'm trying to look under the hood about java compilation. So I put my IDE away and started using MS-DOS command-line...
I created a simple project, as described in the tree below :
SampleApp
|____**src**
|_____pack
|______Sample.java
|____**classes**
This is the Sample.java source code :
public class Sample
{
private String s = new String("Hello, world");
public Sample(){
System.out.println(s);
}
}
I just want to compile this class, so I used the javac command :
prompt\SampleApp\src>javac -d ..\classes -sourcepath . pack\Sample.java
All works fine, but i didn't expect that because I deleted my CLASSPATH environment variable before compiling my Sample.java file. So I was expecting a compiler error due to the fact that the compiler would not be able to find the java.lang.String class file.
I read this article http://www.ibm.com/developerworks/java/library/j-classpath-windows/ which helped me understand many things. The article author says that the default classpath is the current working directory. But I don't understand why my source code compile without error. Could someone explain this to me?
So I was expecting a compiling error due to the fact that the compiler would not be able to find the java.lang.String class file.
The short answer is that the compiler knows where to find all of the standard Java SE library classes without you telling it.
The longer answer is that String class is being found on the bootclasspath. This is implicitly set by the javac command to refer to the relevant JARs in the JDK installation. The javac command searches the bootclasspath before it looks for stuff on the regular classpath.
The classpath variable doesn't do what you think. To cite the oracle documentation:
The CLASSPATH variable is one way to tell applications, including the
JDK tools, where to look for user classes. (Classes that are part of
the JRE, JDK platform, and extensions should be defined through other
means, such as the bootstrap class path or the extensions directory.)
Source: http://docs.oracle.com/javase/tutorial/essential/environment/paths.html
Basically since java.lang.* is part of the platform and delivered with the JDK/JRE, the compiler doesn't have to be told by you where to look for them.
I'm using AspectJ and the 'ajc' command line compiler. I specify aspectjrt.jar, aspectjtools.jar, and aspectjweaver.jar on the classpath ('-cp') during compilation, yet when I call the standard 'thisJoinPoint', an exception is thrown:
Compilation:
ajc -cp lib/aspectjrt.jar:lib/aspectjtools.jar:lib/aspectjweaver.jar -inpath work/src/ -outjar ./mynewjar.jar #work/source.lst
Code which causes exception:
before() : onCreateCall() {
System.out.println("[-] PC Info: " + thisJoinPoint.getSignature());
}
And the exception itself:
Could not find class 'org.aspectj.runtime.reflect.Factory', referenced from method com.test.WooAspects.ajc$preClinit
Of course, I've tried specifying the import with the following, but no luck:
import org.aspectj.runtime.reflect.Factory;
import org.aspectj.runtime.reflect.*;
Any ideas?
When compiling your code, if it references types in a separate library, that library (possibly packaged as a .jar) needs to be available on the compilation classpath (javac or ajc in this case).
When running your code, if it references types in a separate library, that library needs to be available on the runtime classpath (java or the alternative for aspectj).
Note that an import statement is unrelated to the classpath. All an import statement does is allow you to use a type's or member's short name instead of its fully qualified name.
The following things seem to be a little odd at first glance:
It looks as if you think that -inpath work/src actually is meant to include source files, but the inpath is actually meant to include class files. What you probably want is -sourceroots work/src.
Then you seem to use an argument file named work/source.lst which you have not shown us, so we do not know what is in there - maybe more command line switches, maybe more source files. I have no idea.
On your ajc classpath there are all three AspectJ libraries, but usually you only need aspetcjrt.jar. The other two are only needed for load-time weaving [LTW] (aspectjtools.jar) or if you want to use the AspectJ compiler and a few other tools during runtime (aspectjweaver.jar).
For a simple project in which Java and AspectJ code are in the same source directory, the following works for me (inserting line breaks for better readability, but it is all one line on the console):
ajc
-1.7
-cp lib/aspectjrt.jar
-sourceroots src
-outjar my.jar
Then you run the aspect-enhanced JAR like this (again one line on the console):
java
-cp lib/aspectjrt.jar;my.jar
de.scrum_master.app.Application
I.e. during runtime you also just need the runtime JAR on your classpath.
Maybe you want to use a build tool like Maven managing your dependencies and the build process. You can also use plugins like Maven Shade or One-JAR in order to produce a single über-JAR containing both the compiled Java + AspectJ code and the AspectJ runtime. Then you do not have any problems with classpaths during runtime, you just call
java -jar my_uber.jar
Update: You may want to read the ajc documentation for more info.
I am writing an eclipse plugin to support the Frege programming language.
I use the IMP meta tooling platform and Eclipse Indigo (3.7). The run time environment is Java 1.7.
The plugin uses the same code as the batch compiler for token parsing, syntax analysis, etc. However, I noticed differing behavior when run from the eclipse plugin and traced it down to the following method that reads a class file of a previously compiled module to get meta-information which is stored there in form of java annotations:
public static MD.Operator[] getOperators(ClassLoader loader, String pack)
throws ClassNotFoundException {
Class<?> cl = null;
cl = loader.loadClass(pack);
MD.FregePackage os = cl.getAnnotation(MD.FregePackage.class);
if (os == null) return null; // <-- no annotation present
return os.ops();
}
Note that the code creates its own instance of an URLClassLoader, which is passed as argument. If I do not set the class path correctly, the getOperators method correctly throws a ClassNotFoundException, therefore I think I can be sure that it loads the class.
A trace message tells me that the class loader is built with the following path (which is just the classpath by default):
mkClassLoader:[C:\opt\eclipse\plugins\org.eclipse.equinox.launcher_1.2.0.v20110502.jar, X:\dev\frege\build]
Because a class file not created by the frege compiler cannot normally have the MD.FregePackage annotation this usually indicates that the user tried to import a plain java class, and indeed I get the following message in the plugin:
X:/dev/runtime-EclipseApplication/TestJFrege/src/Neu.fr:1: `frege.prelude.Base` is not a frege package
Yet, from the command line I can compile this just fine. I included this here as proof that the annotations in question can indeed be loaded from the same location:
X:\dev\frege>java -cp ./build frege.compiler.Main X:/dev/runtimeEclipseApplication/TestJFrege/src/Neu.fr
mkClassLoader: [./build]
running: javac -cp ./build -d . -encoding UTF-8 ./Neu.java
Resuming the facts:
The code that is supposed to load the annotations works fine when the compiler is invoked via command line interface.
The code that is supposed to load the annotations does not know whether it is invoked from the plugin or the command line. In fact, the plugin didn't even exist until last week, while the command line interface used to work fine for months.
The annotations have, of course, RetentionPolicy.RUNTIME otherwise the command line compilation would not recognize them either. But it proovably does.
So the only conclusion I can draw is that Class.getAnnotation() somehow is not working correctly. This is very unfortunate, as this effectively destroys basic functionality I need for the module system.
If this matters anyhow: the Frege compiler code the plugin uses is itself written in Frege and the frege.prelude.Base class mentioned above is a basic library that is needed by every module, hence it must already have been loaded on activation of the plugin, though of course with a different class loader.
Does anybody have similar experiences? Is it possible to solve this and how? Any suggestions how to circumvent this are welcome.
Was the MD.FregePackage class loaded by the classloader used in your method? Possibly try loading that one first, since two classes aren't equal() if they were loaded by different class loaders. That could explain why it isn't being found.