How to use sun.reflect package in jdk9/java-9? - java

I am using jdk-9 and I want to use sun.reflect.* package in my code but I am getting the below exception
Exception in thread 'main' java.lang.IllegalAccessError : class Test (in moudle: Unnamed Module) cannot access class sun.reflect.Reflaction (in module:java.base), sun.reflect is not exported to Unnamed module
when I run below sample code using JDK-9
public static void main(String args[]){
System.out.println(Reflection.getCallerClass(3));
}

These sun.* packages were never part of the official API and not guaranteed to be present, even in JVMs before Java 9. Be prepared for them to vanish completely in the future, not even be recoverable via some options. Thankfully, there is an official API covering this functionality, eliminating the need for inofficial APIs.
Get the immediate caller class
Class<?> c = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE)
.getCallerClass();
Get the n’th caller on the stack (e.g. third, like in your example):
Class<?> c = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).walk(s ->
s.map(StackWalker.StackFrame::getDeclaringClass).skip(3).findFirst().orElse(null));

Works fine with newer OpenJDK 9 EA builds. For example:
$ java -version
java version "9-ea"
Java(TM) SE Runtime Environment (build 9-ea+138)
Java HotSpot(TM) 64-Bit Server VM (build 9-ea+138, mixed mode)
$ javac Test.java
Test.java:5: warning: Reflection is internal proprietary API and may be removed in a future release
System.out.println(Reflection.getCallerClass(3));
^
Note: Test.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
1 warning
$ java Test
null
Seems that it was fixed in 9-ea+115 build as a part of JDK-8137058. So probably you are using older EA build. In general #Holger is right: there are big chances that this API will disappear completely in future Java versions, so consider migrating to StackWalker API.

THIS ANSWER IS OUTDATED - CHECK THIS ONE INSTEAD!
A feature of the module system is that it allows library developers to strongly encapsulate implementation details due to the new accessibility rules. In a nutshell, most types in sun.* and com.sun.* packages will no longer be accessible. This is in line with Sun and later Oracle stating that these packages are not meant for public consumption.
A workaround is to export these packages at compile and launch time with a command line flag:
--add-exports java.base/sun.reflect=ALL-UNNAMED
This exports the package sun.reflect from the module java.base to all modules including the unnamed module, which is the one that collects all classes on the class path.

java -cp classes -XaddExports:java.base/sun.reflect Test
Jigsaw (java-9) has modularity concept in which they have designed java.base package for compact-1 and they have encapsulated sun.reflect.*. so sun.reflect.* can not be accessible outside.
Due to that reason it giving the exception
Exception in thread 'main' java.lang.IllegalAccessError : class Test (in moudle: Unnamed Module) cannot access class sun.reflect.Reflaction (in module:java.base), sun.reflect is not exported to Unnamed module
Still to provide backward compatibility, they have provided the way to use that package like below.
java -cp classes -XaddExports:java.base/sun.reflect Test

Updating the thread with the latest release and changes brought in as mentioned in the Migration documentation as well. Appropriately correctly pointed out by #Holger already though.
The APIs in sun.reflect packages that remain accessible in JDK 9 are:
sun.reflect.Reflection::getCallerClass(int) Instead, use the
stack-walking API, see JEP 259: Stack-Walking
API.
sun.reflect.ReflectionFactory.newConstructorForSerialization
These APIs are accessible by default at run time. They have been moved to the jdk.unsupported module, which is present in the JRE and JDK images. Modules that need these APIs must declare a dependency upon the jdk.unsupported module.
The remaining internal APIs in the sun.misc and sun.reflect packages
have been moved, since they should not be accessible. If you need to use one of these internal APIs, you can break encapsulation using the --add-exports command line option. (similar to as answered by
#NIrav).
Though as suggested in the docs, this option should only be used as a temporary aid to migration.

Related

How to modify the AST using Java 9+ [duplicate]

This question already has answers here:
Accessing com.sun.tools.javac.util from Java 9
(2 answers)
Closed 3 years ago.
I've been trying to modify the AST using annotation processors. I tried extending Lombok, but that seemed too hard, so I decided to use things from com.sun.source.* and com.sun.tools.javac.* However, I am using java 11, and the document I was learning from, "The Hacker's Guide to Javac" http://scg.unibe.ch/archive/projects/Erni08b.pdf, uses Java 6. The api they used is now internal and my module cannot read it.
In IntelliJ, it gave me a few errors, but I clicked on the suggestions (which said things like "Add --Xxx-xxx to xxx" without paying attention to them. When I tried compiling with Maven, it failed, since the module does not read the internals of jdk.compiler.
These are some of my imports:
import com.sun.source.util.Trees;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.TreeTranslator;
import com.sun.tools.javac.util.Context;
My module-info file contains
requires jdk.compiler;
requires java.compiler;
I got messages like "[ERROR]package com.sun.tools.javac.util is declared in module jdk.compiler, which does not export it to module OtherAnnot" and "[ERROR] (package com.sun.tools.javac.tree is declared in module jdk.compiler, which does not export it to module OtherAnnot)"
Edit: I guess this is a duplicate, but I wanted to know if there was some alternative API for AST transformations in java 9.
With the introduction of Project Jigsaw, the JDK has been modularized, allowing users to create their own modules as well. These modules allows you to export packages of yours, allowing programs that require your module (in their module-info.java) to use the exported packages.
Ideally, you'd be prohibited from using classes that reside in packages that are not exported. However, to not break backwards compatibility, VM flags were introduced that allow you to forcefully export packages (that don't belong to you) to your module.
Given your error message, the respective VM flag to add is:
--add-exports jdk.compiler/com.sun.tools.javac.tree=OtherAnnot
The pattern here is:
--add-exports THEIR_MODULE/THEIR_PACKAGE=YOUR_MODULE
If the compiler complains that packages aren't exported to the unnamed module, then you can use the following:
--add-exports THEIR_MODULE/THEIR_PACKAGE=ALL-UNNAMED

Where has sun.misc.Perf moved now that tools.jar was decomposed in Java 9

sun.misc.Perf was in tools.jar and in Java 9 this was removed and restructured based on the modules concept, so the question is how do you access it in newer Java?
I need to know which module now contains this code.
The implementation has been moved under the jdk.internal.perf package within the java.base module.
As the name already suggests, the package has not been exported from the module and hence if you still want to explicitly make use of the classes within this package, you can make use of the VM option:
--add-exports java.base/jdk.internal.perf=<your-module-name>
Do note though, this is an unreliable way of making use of such classes and a better solution would always be to migrate for the specific use cases without depending on the (internal) sun.misc.* classes.

com.sun.javafx.collections in Java 9

I need to recompile java applicattion, written on Java 8. App use com.sun.javafx.collections.ObservableListWrapper class, but when compiling for java 9 error occurs:
Error:(3, 22) java: package com.sun.javafx.collections is not visible
(package com.sun.javafx.collections is declared in module javafx.base, which does not export it to the unnamed module)
Which class I can use instead of ObservableListWrapper? Or how to bypass this problem?
Since Java9, most of the com.sun.* APIs are unsupported, JDK-internal APIs and they might go away at any time. Also as described in the noteworthy column -
You should plan to move to using the javafx.collections.FXCollections instead.
Use exposed class since Java 8: ModifiableObservableListBase.
That should handle all your needs.

JavaLangAccess and SharedSecrets in Java 9

It seems like the SharedSecrets and JavaLangAccess classes from the sun.misc package were removed in Java 9.
Are there any replacements in Java 9 for the functionality provided by these classes?
Both the above classes are packaged in jdk.internal.misc package.
One way you can try and access them is by using the option
--add-exports <source-module>/<package>=<target-module>(,<target-module>)*
for your use case as :
--add-exports java.base/jdk.internal.misc=your.module
Note:- Disclaimer from JEP-261:Module System -
The --add-exports and --add-opens options must be used with great
care. You can use them to gain access to an internal API of a library
module, or even of the JDK itself, but you do so at your own risk: If
that internal API is changed or removed then your library or
application will fail.
According to Bug#JDK-8137056
In preparation for JEP 160, SharedSecrets and friend interfaces should
be moved out of 'sun.misc' and located in a truly private package
And they are now available at jdk.internal.misc
Move SharedSecrets and friends to jdk.internal.misc

Replace a class within the Java class library with a custom version

The class BasicLabelUI in javax/swing/plaf/basic is affected by a confirmed bug.
In my application I need functionality provided by the fixed version (filed for v9).
Due to both legal and technical reasons, I'm still bound to the affected JDK version.
My approach was to create a package javax/swing/plaf/basic inside my project, containing the fixed version.
How can I force my project to favor my included version of the class over the defective class in the installed JDK?
This has to be somewhat portable as the fixed class also has to be working on customer side and the defective class in the JDK installation has to be disregarded. Therefore, I dont want to modify the JDK, but rather bypass this particular class.
As mentioned by the other answers, you could in theory of course unzip your JVM's rt.jar file and replace the file with a compatible bugfixed version.
Any classes of the Java Class library such as those of Swing are loaded by the bootstrap class loader which looks up its classes from this rt.jar. You can generally not prepend classes to this classpath without adding them to this file. There is a (non-standard) VM option
-Xbootclasspath/jarWithPatchedClass.jar:path
where you would prepend a jar file that includes the patched version, but this does not necessarily work on any Java virtual machine. Also, it is illegal to deploy an application that changes this hehavior! As it is stated in the official documentation:
Do not deploy applications that use this option to override a class in
rt.jar because this violates the Java Runtime Environment binary code
license.
If you however appended a class to the bootstrap class loader (what is possible without using non-standard APIs by using the instrumentation API), the runtime would still load the original class as the bootstrap class loader in this case searches the rt.jar first. It is therefore impossible to "shadow" the broken class without modifying this file.
Finally, it is always illegal to distribute a VM with a patched file, i.e. putting it into a production system for a customer. The license agreement states clearly that you need to
[...] distribute the [Java runtime] complete and unmodified and only bundled as part of your applets and applications
Changing the VM that you distribute is therefore not recommended as you might face legal consequences when this is ever uncovered.
Of course, you can in theory build your own version of the OpenJDK but you could not call the binary Java anymore when you distribute it and I assume that your customer would not allow for this by what you suggest in your answer. By experience, many secure environments compute hashes of binaries before execution what would prohibit both approaches of tweaking the executing VM.
The easiest solution for you would probably be the creation of a Java agent that you you add to your VM process on startup. In the end, this is very similar to adding a library as a class path dependency:
java -javaagent:bugFixAgent.jar -jar myApp.jar
A Java agent is capable of replacing a class's binary representation when the application is started and can therefore change the implementation of the buggy method.
In your case, an agent would look something like the following where you need to include the patched class file as a ressource:
public static class BugFixAgent {
public static void premain(String args, Instrumentation inst) {
inst.addClassFileTransformer(new ClassFileTransformer() {
#Override
public byte[] transform(ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) {
if (className.equals("javax/swing/plaf/basic/BasicLabelUI")) {
return patchedClassFile; // as found in the repository
// Consider removing the transformer for future class loading
} else {
return null; // skips instrumentation for other classes
}
}
});
}
}
The javadoc java.lang.instrumentation package offers a detail description of how to build and implement a Java agent. Using this approach, you can use the fixed version of the class in question without breaking the license agreement.
From experience, Java agents are a great way for fixing temporary bugs in third party libraries and in the Java Class Library without needing to deploy changes in your code or even being required to deploy a new version for a customer. As a matter of fact, this is a typical use case for using a Java agent.
How can I force my project to favor my included version of the class over the defective class in the installed JDK?
Simple answer - you can't. At least, not while strictly obeying the constraint that you should use the affected Java version.
Assuming that you can identify an appropriate version in the OpenJDK source repos, it would be possible to build your own flavor of the Java libraries with a bug patched. However, that won't be real Java. Certainly, it won't qualify as "the affected Java version" that you are constrained to use. (And besides, you are committing yourself to an endless cycle of reapplying your patch to each new patch release of the current version of Java ...)
It is also possible in theory to put a modified version of some Java standard library class into a JAR and prepend it to the JVM's bootstrap classpath using the -Xbootclasspath command line option. But that is tantamount to changing "the affected Java version" too.
Doing it by using a Java agent to use a patched version of the class is breaking the rules too. And it is more complicated. (If you are going to break your rules, do it the easy way ...)
If you and your customers do decide that tweaking the JVM is an acceptable solution, then doing it via the bootstrap classpath is probably the simplest and cleanest approach. And it is DEFINITELY legal1.
However, I'd recommend that you find a workaround for the bug until a version of Java 9 with your fix is released.
1 - Actually, even the build-from-modified-source approach is legal, because the Oracle Binary license does not apply to that. The Binary license is about distributing a modified version of an Oracle binary. The other possible issue is that you may be violating the terms for using the Java trademark(s) if you distribute a version that is incompatible with "true" Java, and call your distro "Java". The solution to that is ... don't call it "Java"!
However, don't just follow my advice. Ask a lawyer. Better yet, don't do it at all. It is unnecessarily complicated.

Categories