Currently I'm making a mod for Vanilla Minecraft server, which I'm not able to modify the underlying code. Now I'm trying to alter the stack trace printed when I accidentally run into a bug with the re-obfuscated version (attached to the server.jar file downloaded directly from the official Mojang site), making it "deobfuscated", like what Minecraft Forge does. (I've tried finding what I need in the source of Forge, but I guess I'm not experienced enough to read it. :<)
Since the exceptions and the stack traces are sometimes thrown and printed by the server codes which I can't modify, I guess using custom Exception file may not help at all.
I've tried to run the main method using a custom Thread with custom getStackTrace() method, which of course doesn't work at all, since the stack trace generation is implemented in the native codes of the JVM.
The codes below are some examples:
Main.java (Cannot be modified and will be renamed during re-obfuscation)
package underlying.cannot.be.modified
public class Main
{
public static void main(String[] args)
{
...
new SomeClass().someMethod();
...
}
}
SomeClass.java (Cannot be modified and will be renamed during re-obfuscation)
package underlying.cannot.be.modified
public class SomeClass
{
...
public void someMethod()
{
...
try
{
...
}
catch(Exception e)
{
/*
* An example of an exception thrown in the underlying codes
* which cannot be modified.
*/
e.printStackTrace();
}
...
}
...
}
For instance, the following obfuscation mapping is applied:
underlying.cannot.be.modified.Main -> underlying.cannot.be.modified.Main
main(String[]) -> main
underlying.cannot.be.modified.SomeClass -> A
someMethod() -> a
and a stack trace may be like:
at A.a (?:?)
at underlying.cannot.be.modified.Main (?:?)
I'd like to make the stack trace printed as:
at underlying.cannot.be.modified.SomeClass.someMethod (?:?)
at underlying.cannot.be.modified.Main (?:?)
(Of course I hold the mapping which I can implement into my code, and the main() method above is called by my custom code instead of called by the JVM directly. :>)
Does anyone know how to modify the behavior of the getStackTrace() method, by either Java codes, reflection, or even through the custom native codes, with the least required external libraries possible? Thanks!
Related
I have a class with 2 methods.
In method1(), I create a local record called Abc. This local record is only available to method1() because it was defined in method1() (here are the rules on it according to Java).
In method2(), I create a local interface called ABC. Notice that there is a capitalization difference here. The local record was named Abc, but this local interface is named ABC.
Here is the class.
//package ..... //commenting out package information, as I sincerely doubt that is the cause
/** There seems to be a class loader error when running the below method in main(). */
public class ClassLoaderIssue
{
/** Method 1. */
private void method1()
{
record Abc(int a)
{
public static String iDontCare()
{
return "ignore this method";
}
}
System.out.println(Abc.iDontCare()); //error
}
/** Method 2. */
private void method2()
{
interface ABC
{
}
}
/**
*
* Main method.
*
* #param args commandline arguments that we don't care about for this example.
*
*/
public static void main(String[] args)
{
new ClassLoaderIssue().method1();
}
}
And here is the Exception that I get.
Exception in thread "main" java.lang.NoClassDefFoundError: ClassLoaderIssue$1ABC (wrong name: ClassLoaderIssue$1Abc)
at java.base/java.lang.ClassLoader.defineClass1(Native Method)
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1012)
at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:150)
at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:862)
at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:760)
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:681)
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:639)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
at ClassLoaderIssue.method1(ClassLoaderIssue.java:23)
at ClassLoaderIssue.main(ClassLoaderIssue.java:49)
And here is some relevant information.
Java compile command = C:\Program Files\Java\jdk-17.0.2\bin\javac.exe
Java run command = C:\Program Files\Java\jdk-17.0.2\bin\java.exe
Obviously, this is just a runnable example of the problem, but I have a situation where it is extremely convenient and helpful to have local class/enum/record to handle weird state at the very edges of my domain. Therefore, I use this pattern a lot, but this is the first time I have come across this issue.
Now obviously, there are many ways to go around this. The simplest being that I could just rename the enum for one. I already tried that and that worked. Same for the interface.
But why does this happen?
EDIT - I also tested this with Java 18 and got the same result. I used a brand new file, a brand new folder, and I copied and pasted the text (not the file), into an empty file with the same name. Same error. I can't really get a cleaner test than that.
On Windows, file names are case-insensitive. This means that files called Something$ABC.class and Something$Abc.class cannot be distinguished.
Meanwhile, of course, the language itself is case-sensitive, and expects the file name and the name of the class within the file to match.
Your only recourse seems to be to rename either the class or the interface.
I just compiled the code and ran it on my machine. It works without any problems.
Maybe try to delete all existing class files and recompile everything.
Otherwise you can try a different java version, I had similar problems related to that in the past.
I have this class
public class demo3 {
private static void sum()
{
}
}
when I tried to run this class, I expected the error to be java.lang.NoSuchMethodError main Exception in thread "main "
however, the output was a bit different and I got below message
Error: Main method not found in class demo3, please define the main method as:
public static void main(String[] args)
now this got my curiosity as to in which case I will get java.lang.NoSuchMethodError or in which case I will get the other error message.
You get the Main method not found message when public static void main(String[]) can't be found in the class that you've asked the JVM to start running. That is, the entry point of the overall program cannot be found.
You get the java.lang.NoSuchMethodError message if your (already running) code attempts to invoke a method on a class which was available at compile time, but not available in the version of the class you are using at runtime (for example, you compile against one version of the library, and then update the library jar without recompiling). This can occur at any point in the program.
There doesn't look to be anything in JLS which says that NoSuchMethodError can't be thrown, rather than the Main method not found; however, failing to write a main method (either entirely, or writing one with the wrong signature) is a far more common mistake than the "class changed after compilation" case, especially for beginners, for whom the NoSuchMethodError might be too cryptic. There is no harm in providing a more user-friendly message in this one case.
I would need help trying to understand why this is happening to me:
Using Java 1.8.0_131, I have a class such as this:
public class DynamicClassLoadingAppKO {
/*
* THIS VERSION DOES NOT WORK, A ClassNotFoundException IS THROWN BEFORE EVEN EXECUTING main()
*/
// If this method received ChildClassFromLibTwo, everything would work OK!
private static void showMessage(final ParentClassFromLibOne obj) {
System.out.println(obj.message());
}
public static void main(final String[] args) throws Throwable {
try {
final ChildClassFromLibTwo obj = new ChildClassFromLibTwo();
showMessage(obj);
} catch (final Throwable ignored) {
// ignored, we just wanted to use it if it was present
}
System.out.println("This should be displayed, but no :(");
}
}
Two other classes are being used up there: ParentClassFromLibOne and ChildClassFromLibTwo. The latter extends from the former.
There are two external libraries involved:
One library is called libone and contains the ParentClassFromLibOne class. The application includes this library in the classpath both for compiling and executing.
A second library is called libtwo and contains the ChildClassFromLibTwo class. The application includes this library in the classpath for compiling, but not for executing.
As far as I understand, the Java runtime should try to load the ChildClassFromLibTwo (which is not in the classpath at runtime) at this line:
final ChildClassFromLibTwo obj = new ChildClassFromLibTwo();
Given this class is not in the classpath, a ClassNotFoundException should be thrown, and given this line is inside a try...catch (Throwable), the System.out.println line at the end should be executed anyway.
However, what I get is the ClassNotFoundException thrown when the DynamicClassLoadingAppKO itself is loaded, apparently before the main() method is executed at all, and therefore not caught by the try...catch.
What seems more strange to me is that this behaviour disappears and everything works as I would expect if I change the signature of the showMessage() method so that instead of receiving an argument of the parent class, it is directly of the child class:
/*
* THIS VERSION WORKS OK, BECAUSE showMessage RECEIVES THE CHILD CLASS AS A PARAMETER
*/
private static void showMessage(final ChildClassFromLibTwo obj) {
System.out.println(obj.message());
}
How is this possible? What am I missing in the way class loading works?
For testing convenience, I have created a GitHub repository replicating this behaviour [1].
[1] https://github.com/danielfernandez/test-dynamic-class-loading/tree/20170504
OK, the details of why this happens are explained in this Spring Boot ticket [1] which I've been very lucky to be promptly pointed to by Andy Wilkinson. That was definitely a difficult one IMO.
Apparently, what happens in this case is that when the calling class itself is loaded, the verifier kicks in and sees that the showMessage() method receives an argument of type ParentClassFromLibOne. So far so good, and this would not provoke a ClassNotFoundException at this phase even if ParentClassFromLibOne was not in the classpath at runtime.
BUT apparently the verifier also scans method code and notes that there is a call in main() to that showMessage() method. A call that does not pass as an argument a ParentClassFromLibOne, but instead an object of a different class: ChildClassFromLibTwo.
So it is in this case that the verifier does try to load ChildClassFromLibTwo in order to be able to check if it really extends from ParentClassFromLibOne.
Interestingly this wouldn't happen if ParentClassFromLibOne was an interface, because interfaces are treated as Object for assignment.
Also, this does not happen if showMessage(...) directly asks for a ChildClassFromLibTwo as an argument because in such case the verifier does not need to load the child class to check it is compatible... with itself.
Daniel, I'm voting up your answer but I will not mark it as accepted because I consider it fails at explaining the real reason why this is happening at verify time (it's not the class in the signature of the method that's causing the ClassNotFoundException).
[1] https://github.com/spring-projects/spring-boot/issues/8181
This is a bit more complicated than you think. When a class is loaded, all functions are verified. During the verify phase also all referenced classes are loaded, because they are needed to calculated the exact types that are on the stack at any given location in the bytecode.
If you want that lazy behaviour, you have to pass the -noverify option to Java, which will delay the loading of classes until the functions that reference them are executed the first time. But don't use -noverify for security reasons when you don't have full control over the classes that will be loaded into the JVM.
I am learning the LibGDX engine in parallel to re-learning java, and have written a simple logging class that has one method with a string argument to be passed to the Gdx.app.log(). while this isn't needed I did so to practice importing and using custom methods and classes, as well as reducing the length of the line needed to send a message to the console. the method looks like so:
import com.badlogic.gdx.Gdx;
public class logging {
public static final String tag="Console";
//contains method for logging to the console during testing.
public void log(String message){
Gdx.app.log(tag, message);
}
}
Then in the class I am using it in, it is imported properly, and a public logging 'con' is created. Up to this point everything seems to work fine because when I type con. in eclipse I get the log(message) as an autocomplete option, however when it is actually called for instance in a screen, under the show() method. when the program tries to step through that point i get a java.lang.NullPointerException which is confusing the hell out of me since everything is written properly. as an example of its use:
con.log("this is a test");
is the exact usage I attempt which seems fine in eclipse before runtime. Is there some simple idea I am failing to grasp here? or a quirk to the Gdx.app.log()? Please no responses with "just use the Gdx.app.log(); where you need to write to the log" as this is not the point of the exercise for me. thank you in advance for all the help!
If you are getting a NullPointerException in this line:
con.log("this is a test");
The only thing that can be null is con. You are probably defining it, but not actually creating it.
Logging con;
and not
Logging con = new Logging();
I have the following code in Eclipse(Helios)/STS which runs and prints console output when doing a Run As> Java Application, in spite of obvious compilation issues
public interface ITest{
String func();
}
public static class Test implements ITest{
void printFunc(){
System.out.println("Inside Test Function");
}
}
public static void main(String[] args) {
Test test = new Test();
test.printFunc();
}
Can anyone pinpoint the reasoning behind this Eclipse functioning.
Note: Doing a javac externally obviously fails to compile.
Eclipse's Java compiler is designed to cope with flaky, non-compiling code. It will add whatever stuff is necessary to the code to get it to compile.
See this question What is the difference between javac and the Eclipse compiler?
It might have been that you have coded the class successfully before the errors. Eclipse auto-compiles your file while you are coding. Just then, you happen to have errors.. then you decide to run as Java Application, Eclipse will run the most recent compiled class.
I tried your code, implemented the necessary methods to remove the errors, then removed it again to put back the errors.. sure enough, it printed out "Inside Test Function". I also tried commenting out System.out.println("Inside Test Function"); and it still printed out.
In another try, I created another class, added your code, then run (without implementing the errors to avoid auto-compiling), then it printed out an error..
java.lang.NoSuchMethodError: main
Exception in thread "main"