【问题标题】:Why can I "fake" the stack trace of an exception in Java?为什么我可以“伪造”Java 中异常的堆栈跟踪?
【发布时间】:2010-12-14 09:11:49
【问题描述】:

如果我运行以下测试,它会失败:

public class CrazyExceptions {
    private Exception exception;

    @Before
    public void setUp(){
        exception = new Exception();
    }

    @Test
    public void stackTraceMentionsTheLocationWhereTheExceptionWasThrown(){
        String thisMethod = new Exception().getStackTrace()[0].getMethodName();
        try {
            throw exception;
        }
        catch(Exception e) {
            assertEquals(thisMethod, e.getStackTrace()[0].getMethodName());
        }
    }
}

出现以下错误:

Expected :stackTraceMentionsTheLocationWhereTheExceptionWasThrown
Actual   :setUp

堆栈跟踪完全是在撒谎。

为什么抛出异常时不重写堆栈跟踪?我不是 Java 开发人员,也许我在这里遗漏了一些东西。

【问题讨论】:

  • 我不知道 6 年前我们是否可以设置异常的堆栈跟踪“原因”,但最好在创建新异常之后并在抛出它之前使用它:Exception.initCause(Throwable ),您可以在其中使用 setStackTrace() 配置原因堆栈跟踪。

标签: java exception callstack


【解决方案1】:

我认为假设是除非您正在抛出异常,否则您不会实例化异常,那么为什么要付出代价来获得两次堆栈跟踪呢?

在抛出堆栈跟踪时很难重新创建它,因为这只是将对象发送出去。

异常应该在抛出之前完全设置,因此部分实例化是获取堆栈跟踪。

更新:

您可以致电fillInStackTrace() 解决此问题。

【讨论】:

    【解决方案2】:

    异常中的堆栈跟踪对应的是“new”操作,没有别的。

    【讨论】:

      【解决方案3】:

      您不希望抛出异常来改变堆栈轨道,或者您无法安全地重新抛出异常。

      public void throwsException() {
          throw new RuntimeException();
      }
      
      public void logsException() {
          try {
              throwsException();
          } catch (RuntimeException e) {
              e.printStrackTrace();
              throw e; // doesn't alter the exception.
          }
      }
      
      @Test
      public void youCanSeeTheCauseOfAnException(){
          try {
              logsException();
          } catch(Exception e) {
              e.printStrackTrace(); // shows you the case of the exception, not where it was last re-thrown.
          }
      }
      

      【讨论】:

        【解决方案4】:

        堆栈跟踪是在实例化异常时创建的,而不是在抛出异常时创建的。这是Java Language Specification 的指定行为

        20.22.1  public Throwable()
        
        This constructor initializes a newly created Throwable object with null as
        its error message string. Also, the method fillInStackTrace (§20.22.5) is
        called for this object. 
        
        ....
        
        20.22.5  public Throwable fillInStackTrace()
        
        This method records within this Throwable object information about the
        current state of the stack frames for the current thread. 
        

        我不知道为什么他们这样做,但如果规范是这样定义的,那么至少在所有各种 Java VM 上都是一致的。

        但是,您可以通过手动调用exception.fillInStackTrace() 来刷新它。

        另请注意,您应该使用Thread.currentThread().getStackTrace() 而不是new Exception().getStackTrace()(不好的风格)。

        【讨论】:

        • 我没有使用Thread.currentThread().getStackTrace(),因为Thread.currentThread().getStackTrace()[0].getMethodName总是getStacktrace...
        • +1 用于指出手册 fillInStackTrace 的事情。在 C# 中,我们默认获得该行为,当我们需要在重新抛出时保留堆栈跟踪时,我们可以简单地 throw;
        • getStackTrace()[1],仅此而已。
        • @mhaller,不,[1] 不会为您提供 JDK 1.5 中的当前方法名称,它是 [2](有一个额外的内部方法调用)。所以 getStackTrace 非常适合查看堆栈,如果你关心你在哪里(谁说它不会在另一个版本中更改为 3 或返回到 1),这很糟糕。
        【解决方案5】:

        因为您没有要求重写该堆栈跟踪。它是在您在 setUp 方法中创建它时设置的,您从未对它进行任何更改。

        Exception 类没有给您任何设置方法名称的机会;它是不可变的。所以我不知道你可以在哪里重新设置方法名称,除非你想诉诸诸如反射之类的令人发指的东西。

        您的 @Test 注释不会告诉我您使用的是 JUnit 还是 TestNG,因为我看不到静态导入,但无论哪种情况,您都可以运行测试以查看是否通过使用引发了特定异常@Test 注释中的“预期”成员。

        【讨论】:

        • 我正在使用 jUnit,但我并没有尝试测试抛出的异常。我使用测试只是为了说明我的问题。
        • 我不确定问题是什么:“如何从堆栈跟踪中更改方法?”
        【解决方案6】:

        异常的堆栈跟踪是在创建异常时填写的。否则就不可能捕获异常、处理它并重新抛出它。原始的堆栈跟踪会丢失。

        如果你想强制这样做,你必须显式调用exception.fillInStackTrace()

        【讨论】:

        • 在 C# 中,我们默认获得该行为,当我们需要在重新抛出时保留堆栈跟踪时,我们可以简单地 throw;。这就是我感到困惑的原因。
        • -1:我们从 .NET 知道情况并非如此。捕获堆栈跟踪的时间肯定有一些理由,但这并不是因为当被throw e; 语句捕获时无法对其进行管理。
        • @280Z28,关键是在java中没有办法,因为它没有throw;这样的语言结构。
        • @Yishai:是的,但请记住,我们谈论的是语言设计本身,所以如果这是问题所在,他们本可以解决的。他们可能将其称为rethrow,但无论它变成什么,很明显他们需要能够在不更改堆栈跟踪的情况下重新引发异常。
        • @280Z28,问题不在于通过更改语言是否有办法解决它,而是为什么 java 在抛出它时没有设置堆栈跟踪。不是因为它没有重新抛出和保留旧堆栈跟踪的机制。相反,如果您需要重置堆栈跟踪,它具有 fillInStackTrace。这是一个不同的设计。
        猜你喜欢
        • 2021-11-02
        • 2011-01-05
        • 2012-07-11
        • 1970-01-01
        • 2012-09-04
        • 2017-08-06
        • 2010-09-13
        • 1970-01-01
        相关资源
        最近更新 更多