I'm trying to retransform classes with java instrumentation. The code below runs just fine, it prints Hello World!!.
public class Main {
public static void main(String[] args) throws Throwable {
var inst = MyAgent.getInstrumentation();
inst.addTransformer(new ClassFileTransformer() {
#Override
public byte[] transform(ClassLoader loader, String name, Class<?> c, ProtectionDomain pd, byte[] b) {
if (c != Main.class) return null;
var cr = new ClassReader(b);
var cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
cr.accept(new ClassVisitor(ASM9, cw) {
#Override
public MethodVisitor visitMethod(int acc, String name, String desc, String sig, String[] exc) {
var mv = super.visitMethod(acc, name, desc, sig, exc);
if ("foo".equals(name)) {
return new MethodVisitor(ASM9, mv) {
#Override
public void visitCode() {
super.visitCode();
visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
visitLdcInsn("Hello ");
visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V", false);
}
};
}
return mv;
}
}, 0);
return cw.toByteArray();
}
}, true);
inst.retransformClasses(Main.class);
foo();
}
public static void foo() {
System.out.println("World!!");
}
}
However, when the ClassFileTranformer returns invalid bytecode, or when it just returns new byte[0], the retransformation fails silently. No exceptions are thrown and no changes are done. According to the official documentation, if invalid bytes are provided, the method should throw a exception. The doc says:
The class file bytes are not checked, verified and installed until after the transformations have been applied, if the resultant bytes are in error this method will throw an exception.
Using the instrumentation object outside the java agent doesn't seem to be a problem, I tried to put the retransformation code in the agent and the behaviours are exactly the same. I'm using GraalVM EE (the version is shown below). I tried OpenJDK as well and it still fails silently.
java version "17.0.6" 2023-01-17 LTS
Java(TM) SE Runtime Environment GraalVM EE 22.3.1 (build 17.0.6+9-LTS-jvmci-22.3-b11)
Java HotSpot(TM) 64-Bit Server VM GraalVM EE 22.3.1 (build 17.0.6+9-LTS-jvmci-22.3-b11, mixed mode, sharing)
Why it fails silently? Is it because I'm using instrumentation in a wrong way? How can I get a exception when the bytecode is malformed? Thanks in advance.
(Thanks to Johannes Kuhn, corrected the unsupported use of ASM Api)
Related
Window.getWindows();
Doesn't get all the open windows, just the java ones. Is there a way to get all the windows the operating system has open. I'm making a java taskbar.
There is no solution using core Java, but the problem can be solved using JNI or for an easier time, JNA. As noted in the comments, no solution (that I know of) will be platform independent.
For instance, this demo program uses JNA to enumerate all Windows with title in a Windows platform, but will also include windows that are not top-level windows and even windows that aren't visible:
import java.util.ArrayList;
import java.util.List;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.win32.StdCallLibrary;
public class EnumAllWindowNames {
static interface User32 extends StdCallLibrary {
User32 INSTANCE = (User32) Native.loadLibrary("user32", User32.class);
interface WNDENUMPROC extends StdCallCallback {
boolean callback(Pointer hWnd, Pointer arg);
}
boolean EnumWindows(WNDENUMPROC lpEnumFunc, Pointer userData);
int GetWindowTextA(Pointer hWnd, byte[] lpString, int nMaxCount);
Pointer GetWindow(Pointer hWnd, int uCmd);
}
public static List<String> getAllWindowNames() {
final List<String> windowNames = new ArrayList<String>();
final User32 user32 = User32.INSTANCE;
user32 .EnumWindows(new User32.WNDENUMPROC() {
#Override
public boolean callback(Pointer hWnd, Pointer arg) {
byte[] windowText = new byte[512];
user32.GetWindowTextA(hWnd, windowText, 512);
String wText = Native.toString(windowText).trim();
if (!wText.isEmpty()) {
windowNames.add(wText);
}
return true;
}
}, null);
return windowNames;
}
public static void main(String[] args) {
List<String> winNameList = getAllWindowNames();
for (String winName : winNameList) {
System.out.println(winName);
}
}
}
Addition to #Hovercraft Full Of Eels, if you also have windows which are titled with unreadable characters and get "?" printed for those characters, try to:
convert byte[] to string by using different encoding (as I understood)
see below:
String wText = Native.toString(windowText, "windows-1254").trim();
Possible encodings instead of "windows-1254 (which worked well for Turkish characters)":
UTF-8
UTF-16
ASCII
ISO-8859-1
...
You can now call into native Windows APIs to read the titles of non-java windows with pure-Java code using Foreign Memory API and code bindings generated by a compatible jextract.
This example works with the preview code in JDK19 release and is the equivalent to Hovercraft Full Of Eels answer:
/**
* Run with Windows JDK19 and preview enabled:
* jdk-19\bin\java --enable-native-access=ALL-UNNAMED --enable-preview -cp your.jar EnumWindowsDemo
*/
public class EnumWindowsDemo {
// Wide to Java String
private static String toJavaString(MemorySegment wide, int len) {
final CharBuffer cb = wide.asByteBuffer().order(ByteOrder.nativeOrder()).asCharBuffer();
return cb.limit(len).toString();
}
public static void main(String[] args) {
// TODO: You could use someParam to detect specific hWnd inside the callback
long someParam = 0x1234;
final int isRunning = User32_h.TRUE();
final int maxChars = 1024;
System.out.format("EnumWindows Panama 19 Demo Java %s%n", System.getProperty("java.runtime.version"));
HashMap<Long,String> windowNames = new HashMap<>();
try(MemorySession arena = MemorySession.openConfined()) {
// JDK20 try(Arena arena = Arena.openConfined()) {
MemorySegment lpString = arena.allocateArray(JAVA_CHAR, maxChars);
// Windows callback WNDENUMPROC(MemorySegment hwnd, long lParam)
WNDENUMPROC callback = (hWnd, lParam) -> {
int used = User32_h.GetWindowTextW(hWnd, lpString, maxChars);
if (used > 0) {
String text = toJavaString(lpString, used);
System.out.format("hWnd:%x lParam:%x => `%s`%n", hWnd.toRawLongValue(), lParam, text);
// JDK20: System.out.format("hWnd:%x lParam:%x => `%s`%n", hWnd.address(), lParam, text);
if (used >= maxChars) {
System.out.format("*** Oops: increase maxChars > %d, or call GetWindowTextLength first ***%n", used);
}
windowNames.put(hWnd.toRawLongValue(), text);
// JDK20: windowNames.put(hWnd.address(), text);
}
return isRunning;
};
final MemorySegment ewProc = WNDENUMPROC.allocate(callback, arena);
// JDK20: final MemorySegment ewProc = WNDENUMPROC.allocate(callback, arena.scope());
// Note that someParam is passed to the callback as lParam value
int rc = User32_h.EnumWindows(ewProc, someParam);
System.out.format("EnumWindows rc:%s%n", rc == 0 ? "FAIL":"OK");
if (rc == 0) {
throw new RuntimeException("EnumWindows failed rc="+rc);
}
}
System.out.format("Found windowNames: %s%n", windowNames.toString());
}
}
The standalone jextract is used to generate bindings for calling native code. You need to create a header User32.h referencing suitable Windows API headers:
echo #include ^<shlobj_core.h^> > User32.h
Run jextract to generate bindings for a suitable set of Windows APIs:
set JEXTRACT_ARGS=--source -luser32 User32.h -t somepackage --output java
set JEXTRACT_SYMBOLS=--include-macro TRUE --include-function EnumWindows --include-function GetWindowTextW --include-function GetWindowTextLengthW --include-typedef WNDENUMPROC
jextract %JEXTRACT_ARGS% %JEXTRACT_SYMBOLS%
You can also save symbols to a file for easy editing, and re-generate by replacing use of %JEXTRACT_SYMBOLS% above by #your.symbols.
See also:
Holger's answer Windows wide string to Java String conversion.
EnumWindows
GetWindowTextW
Code (spring-web 5.1.2)
public static void main(String[] args) {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.AUTHORIZATION, "token");
HttpEntity<Object> requestEntity = new HttpEntity<>(headers);
ResponseEntity<Object> test = restTemplate.exchange(
"https://example.com",
HttpMethod.GET,
new HttpEntity<>(headers),
new ParameterizedTypeReference<>() { // fails here
});
}
OracleJDK 1.8 (expected output)
cannot infer type arguments for
org.springframework.core.ParameterizedTypeReference
reason: cannot use '<>' with anonymous inner classes
OracleJDK 11.0.2 (not expected output)
compiler message file broken: key=compiler.misc.msg.bug
arguments=11.0.2, {1}, {2}, {3}, {4}, {5}, {6}, {7}
java.lang.NullPointerException at
jdk.compiler/com.sun.tools.javac.comp.Flow$FlowAnalyzer.visitApply(Flow.java:1235)
at
jdk.compiler/com.sun.tools.javac.tree.JCTree$JCMethodInvocation.accept(JCTree.java:1634)
at
jdk.compiler/com.sun.tools.javac.tree.TreeScanner.scan(TreeScanner.java:49)
at
jdk.compiler/com.sun.tools.javac.comp.Flow$BaseAnalyzer.scan(Flow.java:398)
at
jdk.compiler/com.sun.tools.javac.comp.Flow$FlowAnalyzer.visitVarDef(Flow.java:989)
...
If I change diamond operator to explicit type
new ParameterizedTypeReference<>(){} to new ParameterizedTypeReference<Object>(){}
then the code compiles successfully on both JDKs.
Is it a known compiler bug?
Bug (JDK-8212586) has been already submitted and fixed in version 12.
Minimal, verifiable example:
public static void main(String[] args) {
new Bug<>(){};
}
static class Bug<T> {
Bug() {
test(new ParameterizedTypeReference<>(){});
}
void test(ParameterizedTypeReference<T> typeReference) {
}
}
Some Details.
Fix has been also backported to JDK 11 - https://bugs.openjdk.java.net/browse/JDK-8220578.
Available starting JDK 11.0.4.
I Had the same error,you need to create a function :
ParameterizedTypeReference<Object> createParameterizedTypeReference(){ return new ParameterizedTypeReference<>(){}; }
and call it :
ResponseEntity<Object> test = restTemplate.exchange(
"https://example.com",
HttpMethod.GET,
new HttpEntity<>(headers),
createParameterizedTypeReference() {
});
As pointed out in previous comments, the problem is with parametrized anonymous classes, e.g. when using TypeToken from Guava, this does NOT work:
public List<SomeClass> list() {
return getData(new TypeToken<>() { });
}
But this DOES work:
public List<SomeClass> list() {
return getData(new TypeToken<List<SomeClass>>() { });
}
I tried that in versions 11.0.3 - 11.0.7 versions and all contains the bug.
Java 11.0.7 has the same problem.
This changed from:
new ParameterizedTypeReference<>() {
})
to this:
new ParameterizedTypeReference<HashMap<String, MyClass>>() {
})
I've fixed this problem on my machine by upgrading to AdoptOpenJDK 11.0.8.
https://adoptopenjdk.net/installation.html#x64_mac-jdk
Most of the times, this error doesn't tell you the originating class so it's very hard to intentionally reproduce the problem. After trying a lot, I finally updated project JDK to 11.0.16 (from 11.0.7) and it worked!
I am having issues with the Wicket GAE sample application,
The issues is that some import cannot be resolved:
import org.apache.wicket.page.PersistentPageManager; // This one
import org.apache.wicket.util.io.IObjectStreamFactory; // and this cannot be resolved
#Override
public void init()
{
super.init();
getResourceSettings().setResourcePollFrequency(null);
WicketObjects.setObjectStreamFactory(new IObjectStreamFactory() {
#Override
public ObjectInputStream newObjectInputStream(InputStream in)
throws IOException {
return new ObjectInputStream(in);
}
#Override
public ObjectOutputStream newObjectOutputStream(OutputStream out)
throws IOException {
return new ObjectOutputStream(out);
}
});
setPageManagerProvider(new DefaultPageManagerProvider(this) {
public IPageManager get(IPageManagerContext pageManagerContext)
{
IDataStore dataStore = new HttpSessionDataStore(pageManagerContext, new PageNumberEvictionStrategy(10));
IPageStore pageStore = new DefaultPageStore(getName(), dataStore,
getCacheSize());
return new PersistentPageManager(getName(), pageStore, pageManagerContext);
}
});
// add your configuration here
}
Also, the getCacheSize() method cannot be found, I am using Wicket version, 1.5-SNAPSHOT as used in the demo app, but I tried other later versions too but still cannot be resolved, how can this be fixed?
This classes and methods were introduced only in RC versions (or earlier release versions) and removed from 1.5 release
So, you can download one of the RC packages or migrate to 1.5 version.
To migrate, you should:
Remove code WicketObjects.setObjectStreamFactory.... This will be replaced by defining serializer for pageStore. See this migration section.
Override DefaultPageManagerProvider's get method as follows:
#Override
public IPageManager get(IPageManagerContext context)
{
IDataStore dataStore = new HttpSessionDataStore(context,
new PageNumberEvictionStrategy(10));
IPageStore pageStore = new DefaultPageStore (
new JavaSerializer ( getName() ), dataStore,
getStoreSettings().getInmemoryCacheSize());
return new PageStoreManager(application.getName(), pageStore,
context);
}
For further migrations you should check wicket migration guides.
I'm trying to modify the jni4net sample code MyCSharpDemoCalc, and make the sample as a bridge between a .Net DLL and the Java layer. Here is the C# code:
using System;
using Dynamsoft.DotNet.TWAIN;
namespace MyCSharpDemoCalc
{
public interface ICalc
{
int MySuperSmartFunctionIDontHaveInJava(string question);
bool IsShowUI();
}
public class DemoCalc : ICalc
{
private readonly Random r = new Random();
private DynamicDotNetTwain dynamicDotNetTwain;
public DemoCalc()
{
dynamicDotNetTwain = new Dynamsoft.DotNet.TWAIN.DynamicDotNetTwain();
}
public int MySuperSmartFunctionIDontHaveInJava(string question)
{
if (question == "Answer to the Ultimate Question of Life, the Universe, and Everything")
{
return 42;
}
return r.Next();
}
public bool IsShowUI()
{
return dynamicDotNetTwain.IfShowUI;
}
}
}
In order to build it successfully, I added the following references:
System.Windows.Forms
DynamicDotNetTWAIN
Then typed in the command
proxygen.exe MyCSharpDemoCalc.dll -wd work
to generate MyCSharpDemoCalc.j4n.jarand MyCSharpDemoCalc.j4n.dll.
Now, I can import DynamicDotNetTWAIN.dll, MyCSharpDemoCalc.j4n.dll, jni4net.n.w64.v20-0.8.6.0.dll, jni4net.n-0.8.6.0.dll, jni4net.j-0.8.6.0.jar and MyCSharpDemoCalc.j4n.jar to Java project.
Java code:
import net.sf.jni4net.Bridge;
import java.io.IOException;
import mycsharpdemocalc.DemoCalc;
import mycsharpdemocalc.ICalc;
public class Program {
public static void main(String arsg[]) throws IOException {
Bridge.setClrVersion("v20");
Bridge.init();
Bridge.LoadAndRegisterAssemblyFrom(new java.io.File("DynamicDotNetTWAIN.dll"));
Bridge.LoadAndRegisterAssemblyFrom(new java.io.File("MyCSharpDemoCalc.j4n.dll")); // crashed
ICalc calc = new DemoCalc();
final int result = calc.MySuperSmartFunctionIDontHaveInJava("Answer to the Ultimate Question of Life, the Universe, and Everything");
System.out.printf("Answer to the Ultimate Question is : " + result);
System.out.printf("If show UI : " + calc.IsShowUI());
}
}
When I tried to run the application, it crashed at
Bridge.LoadAndRegisterAssemblyFrom(new
java.io.File("MyCSharpDemoCalc.j4n.dll"));
Exception in thread "main" System.Reflection.ReflectionTypeLoadException: Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information.
at System.Reflection.Module._GetTypesInternal(StackCrawlMark& stackMark)
at System.Reflection.Assembly.GetTypes()
at net.sf.jni4net.utils.Registry.RegisterAssembly(Assembly assembly, Boolean bindJVM, ClassLoader classLoader)
at net.sf.jni4net.Bridge.RegisterAssembly(Assembly assembly, ClassLoader classLoader)
at net.sf.jni4net.Bridge.LoadAndRegisterAssemblyFromClassLoader(File assemblyFile, ClassLoader classLoader)
at net.sf.jni4net.Bridge.LoadAndRegisterAssemblyFrom(File assemblyFile)
at net.sf.jni4net.__Bridge.LoadAndRegisterAssemblyFrom3(IntPtr __envp, JniLocalHandle __class, JniLocalHandle assemblyFile)
at net.sf.jni4net.Bridge.LoadAndRegisterAssemblyFrom(Native Method)
at com.main.Program.main(Program.java:68)
How can I fix it? Thanks!
The JNI4NET will attempt to load the image from where the JNI4NET libraries were located at, the only workaround (AFAIK) was to copy the whole libraries to your source directory, and compile your package using the copied libraries, then it will work.
Window.getWindows();
Doesn't get all the open windows, just the java ones. Is there a way to get all the windows the operating system has open. I'm making a java taskbar.
There is no solution using core Java, but the problem can be solved using JNI or for an easier time, JNA. As noted in the comments, no solution (that I know of) will be platform independent.
For instance, this demo program uses JNA to enumerate all Windows with title in a Windows platform, but will also include windows that are not top-level windows and even windows that aren't visible:
import java.util.ArrayList;
import java.util.List;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.win32.StdCallLibrary;
public class EnumAllWindowNames {
static interface User32 extends StdCallLibrary {
User32 INSTANCE = (User32) Native.loadLibrary("user32", User32.class);
interface WNDENUMPROC extends StdCallCallback {
boolean callback(Pointer hWnd, Pointer arg);
}
boolean EnumWindows(WNDENUMPROC lpEnumFunc, Pointer userData);
int GetWindowTextA(Pointer hWnd, byte[] lpString, int nMaxCount);
Pointer GetWindow(Pointer hWnd, int uCmd);
}
public static List<String> getAllWindowNames() {
final List<String> windowNames = new ArrayList<String>();
final User32 user32 = User32.INSTANCE;
user32 .EnumWindows(new User32.WNDENUMPROC() {
#Override
public boolean callback(Pointer hWnd, Pointer arg) {
byte[] windowText = new byte[512];
user32.GetWindowTextA(hWnd, windowText, 512);
String wText = Native.toString(windowText).trim();
if (!wText.isEmpty()) {
windowNames.add(wText);
}
return true;
}
}, null);
return windowNames;
}
public static void main(String[] args) {
List<String> winNameList = getAllWindowNames();
for (String winName : winNameList) {
System.out.println(winName);
}
}
}
Addition to #Hovercraft Full Of Eels, if you also have windows which are titled with unreadable characters and get "?" printed for those characters, try to:
convert byte[] to string by using different encoding (as I understood)
see below:
String wText = Native.toString(windowText, "windows-1254").trim();
Possible encodings instead of "windows-1254 (which worked well for Turkish characters)":
UTF-8
UTF-16
ASCII
ISO-8859-1
...
You can now call into native Windows APIs to read the titles of non-java windows with pure-Java code using Foreign Memory API and code bindings generated by a compatible jextract.
This example works with the preview code in JDK19 release and is the equivalent to Hovercraft Full Of Eels answer:
/**
* Run with Windows JDK19 and preview enabled:
* jdk-19\bin\java --enable-native-access=ALL-UNNAMED --enable-preview -cp your.jar EnumWindowsDemo
*/
public class EnumWindowsDemo {
// Wide to Java String
private static String toJavaString(MemorySegment wide, int len) {
final CharBuffer cb = wide.asByteBuffer().order(ByteOrder.nativeOrder()).asCharBuffer();
return cb.limit(len).toString();
}
public static void main(String[] args) {
// TODO: You could use someParam to detect specific hWnd inside the callback
long someParam = 0x1234;
final int isRunning = User32_h.TRUE();
final int maxChars = 1024;
System.out.format("EnumWindows Panama 19 Demo Java %s%n", System.getProperty("java.runtime.version"));
HashMap<Long,String> windowNames = new HashMap<>();
try(MemorySession arena = MemorySession.openConfined()) {
// JDK20 try(Arena arena = Arena.openConfined()) {
MemorySegment lpString = arena.allocateArray(JAVA_CHAR, maxChars);
// Windows callback WNDENUMPROC(MemorySegment hwnd, long lParam)
WNDENUMPROC callback = (hWnd, lParam) -> {
int used = User32_h.GetWindowTextW(hWnd, lpString, maxChars);
if (used > 0) {
String text = toJavaString(lpString, used);
System.out.format("hWnd:%x lParam:%x => `%s`%n", hWnd.toRawLongValue(), lParam, text);
// JDK20: System.out.format("hWnd:%x lParam:%x => `%s`%n", hWnd.address(), lParam, text);
if (used >= maxChars) {
System.out.format("*** Oops: increase maxChars > %d, or call GetWindowTextLength first ***%n", used);
}
windowNames.put(hWnd.toRawLongValue(), text);
// JDK20: windowNames.put(hWnd.address(), text);
}
return isRunning;
};
final MemorySegment ewProc = WNDENUMPROC.allocate(callback, arena);
// JDK20: final MemorySegment ewProc = WNDENUMPROC.allocate(callback, arena.scope());
// Note that someParam is passed to the callback as lParam value
int rc = User32_h.EnumWindows(ewProc, someParam);
System.out.format("EnumWindows rc:%s%n", rc == 0 ? "FAIL":"OK");
if (rc == 0) {
throw new RuntimeException("EnumWindows failed rc="+rc);
}
}
System.out.format("Found windowNames: %s%n", windowNames.toString());
}
}
The standalone jextract is used to generate bindings for calling native code. You need to create a header User32.h referencing suitable Windows API headers:
echo #include ^<shlobj_core.h^> > User32.h
Run jextract to generate bindings for a suitable set of Windows APIs:
set JEXTRACT_ARGS=--source -luser32 User32.h -t somepackage --output java
set JEXTRACT_SYMBOLS=--include-macro TRUE --include-function EnumWindows --include-function GetWindowTextW --include-function GetWindowTextLengthW --include-typedef WNDENUMPROC
jextract %JEXTRACT_ARGS% %JEXTRACT_SYMBOLS%
You can also save symbols to a file for easy editing, and re-generate by replacing use of %JEXTRACT_SYMBOLS% above by #your.symbols.
See also:
Holger's answer Windows wide string to Java String conversion.
EnumWindows
GetWindowTextW