6

For better debugging, I would often like to have:

Exception 
  at com.example.blah.Something.method()
  at com.example.blah.Xyz.otherMethod()
  at com.example.hello.World.foo()
  at com.example.debug.version_3_8_0.debug_info_something.Hah.method() // synthetic method
  at com.example.x.A.wrappingMethod()

The debug stack frame as shown above would be dynamically generated, just like a java.lang.reflect.Proxy, except that I'd like to be in full control of the entire fully qualified method name that ends up on the proxy.

At the call site, I would do something silly and simple as this:

public void wrappingMethod() {
    run("com.example.debug.version_3_8_0.debug_info_something.Hah.method()", () -> {
        World.foo();
    });
}

As you can see, the wrappingMethod() is a real method that ends up on the stack trace, Hah.method() is a dynamically generated method, whereas World.foo() is again a real method.

Yes, I know this pollutes the already deep deep stack traces. Don't worry about it. I have my reasons.

Is there a (simple) way to do this or something similar as the above?

Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
  • 1
    I'm not hugely familiar with the area, but I know something like this can be done at the bytecode level, but I'm not sure if it's possible in the language. – Socratic Phoenix Sep 26 '16 at 21:33
  • @Meguy26: I'm fine with bytecode level solutions if they can be implemented with no special dependencies outside the JDK. Although, I'd also accept working solutions that use a dependency like Bytebuddy – Lukas Eder Sep 26 '16 at 21:35

1 Answers1

9

No need for code generation to solve this problem:

static void run(String name, Runnable runnable) {
  try {
    runnable.run();
  } catch (Throwable throwable) {
    StackTraceElement[] stackTraceElements = throwable.getStackTrace();
    StackTraceElement[] currentStackTrace = new Throwable().getStackTrace();
    if (stackTraceElements != null && currentStackTrace != null) { // if disabled
      int currentStackSize = currentStackStrace.length;
      int currentFrame = stackTraceElements.length - currentStackSize - 1;
      int methodIndex = name.lastIndexOf('.');
      int argumentIndex = name.indexOf('(');
      stackTraceElements[currentFrame] = new StackTraceElement(
          name.substring(0, methodIndex),
          name.substring(methodIndex + 1, argumentIndex),
          null, // file name is optional
          -1); // line number is optional
      throwable.setStackTrace(stackTraceElements);
    }
    throw throwable;
  }
}

With code generation, you could add a method with the name, redefine the call site within the method, unwind the frame and call the generated method but this would be much more work and would never be equally stable.

This strategy is a rather common approach in testing frameworks, we do it a lot in Mockito and also other utilities like JRebel do it to hide their magic by rewriting exception stack frames.

When Java 9 is used, it would be more efficient to do such manipulations using the Stack Walker API.

Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192
  • *"Stack Walker"*... you mean, rather than `Stream.of(stackTraceElements)` ? :) – Lukas Eder Sep 29 '16 at 05:36
  • 2
    One advantage of the stack walker API is its performance as it does not need to collect information about the entire stack what is rather costly. – Rafael Winterhalter Sep 29 '16 at 06:49
  • Is it so? [I've seen "walk" before in the `Files.walk()` API and it doesn't do what you might expect](https://blog.jooq.org/2014/01/24/java-8-friday-goodies-the-new-new-io-apis/) – Lukas Eder Sep 29 '16 at 20:20
  • @Lukas Eder: Unlike the file system, there is no hierarchy in the stack frames. So this issue is not relevant here. – Holger Oct 04 '16 at 22:07
  • @Holger: You're right, probably that's not an issue – Lukas Eder Oct 05 '16 at 12:24