Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Clarify inner exception and aggregate exception ToString()#27333

Closed
danmoseley wants to merge 9 commits intodotnet:masterfrom
danmoseley:exceptionsv2
Closed

Clarify inner exception and aggregate exception ToString()#27333
danmoseley wants to merge 9 commits intodotnet:masterfrom
danmoseley:exceptionsv2

Conversation

@danmoseley
Copy link
Copy Markdown
Member

@danmoseley danmoseley commented Oct 21, 2019

Continuation of the part of #25045 that was deferred from 3.0.

Using this test program - the SSL case is real, albeit the stacks are not --

It's easiest to open in separate tabs and switch between. I think the boxes make it considerably easier to match up which exception goes with which stack, and which exception is nested inside which other exception. Thoughts? Other suggestions?

Note that the non-aggregate non-nested exception case (top example) is not affected.

BTW also made some minor fixes to pretty some exceptions I noticed as I was doing this

  • aae660d (one bad IL message was missing path)
  • 7b57069 (non absolute path message was missing path)
  • 54336a7 (2 blank lines caused by runtime passing empty string for fusion log: I assume fusion log is not relevant anymore)
  • Only append the file name if it's not already in the exception message. It looks like the runtime pretty much always puts it in the message if it's available, in which case text line File name: 'c:\some\file' is an unnecessary line.

// when it's retrieved.
var sb = new StringBuilder(256);
new StackTrace(fNeedFileInfo: true).ToString(System.Diagnostics.StackTrace.TraceFormat.TrailingNewLine, sb);
sb.AppendLine(SR.Exception_EndStackTraceFromPreviousThrow);
Copy link
Copy Markdown
Member Author

@danmoseley danmoseley Oct 21, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed this line (and thus the string) because elsewhere where remote stack trace and stack trace are combined, they are concatenated without a separator. Eg.,

_remoteStackTraceString += _stackTraceString;

return remoteStackTraceString + stackTraceString;

return remoteStackTraceString + GetStackTrace(this);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please put it back. Have you looked at what the stack traces look like without it?

Comment thread src/vm/assemblynative.cpp Outdated
@danmoseley danmoseley requested a review from davidsh October 21, 2019 05:26
return "";

/* Format something like this to clearly separate from the containing exception
________________________________________________________________________________________________
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that the ascii art part of the change has significant risk of breaking tools that analyze exception stacktraces, and it may be controversial (e.g. my personal preference would be a more compact format without the ascii art, but I am happy to be overruled.)

If you would like to pursue it, I think it should have its own issue and PR. The issue should be marked as breaking change and the stakeholders should be included to comment on it:

  • The community projects that are in the business of formatting and understanding exception stacktraces (e.g. Ben.Demystifier)
  • Our diagnostic and TI teams

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

breaking tools that analyze exception stacktraces

e.g. some of our own corefx tests are failing because of this problem

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really just wanted to start a discussion. Any suggestions for an alternative? Maybe the arrows? (Is there general agreement the existing format is hard to correlate?)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll close this and open an issue to discuss that...

foreach (Exception ex in m_innerExceptions)
{
if (m_innerExceptions[i] == InnerException)
if (object.ReferenceEquals(ex, InnerException))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is == inappropriate here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to filter out the exact same object - figured it was clearer. Also a subclass could have overridden the equals operator.

Copy link
Copy Markdown
Member

@stephentoub stephentoub Oct 21, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also a subclass could have overridden the equals operator.

That's actually not how == works. Unless the strongly-typed variable being used defines an operator == (and Exception doesn't), this will be emitted with the exact same behavior as ReferenceEquals. It doesn't use Equals. e.g. this will output false:

using System;

class Program
{
    static void Main() => Console.WriteLine(new Program() == new Program());

    public override bool Equals(object obj) => true;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had forgotten this. In the case where this is known to be true (as here), do we prefer to use == for brevity, or O.RE to be explicit about intent? I assume the former as we don't seem to use O.RE that much.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It often ends up being personal preference.

Copy link
Copy Markdown
Member

@jkotas jkotas Oct 21, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roslyn compiles ReferenceEquals into the same IL as what you get from ==, so it is just a style difference. Similar to == null vs. is null.

text.Append(m_innerExceptions[i].ToString());
text.Append("<---");
text.AppendLine();
text.Append(InnerExceptionToString(ex));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This helper ends up allocating it's own string builder, formatting to it, getting its string, and then appending that string to this builder. Why not just pass this builder in? If the concern is reusing it in places that don't use a builder, you can make the helper create the builder only if one want passed in, and return a its ToString only if one was.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure.

string s = GetType().ToString() + ": " + Message;

if (!string.IsNullOrEmpty(_fileName))
if (!string.IsNullOrEmpty(_fileName) && !Message.Contains(_fileName))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this all that meaningful? While we discourage parsing, the current format is much more parsable for the filename.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming the filename is set and not just in the message. Ok, guess I need feedback on this one.

Apart from reducing noise, another possible advantage of reducing line count is that the parts of the output are closer together in a log produced from concurrent Interleaving outputs eg a build log or test run.

{
sb.AppendLine();
sb.Append(SR.Exception_EndStackTraceFromPreviousThrow);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this being removed?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea was that the boundary could be inferred - this was in the previous PR I think.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe that's easy to do in many/all cases. We explicitly skip this for async because that case generally is easier, but for other cases, you're generally not nearly as linear, and you end up with stack traces that look impossible, which is much more confusing and time consuming to analyze than when they're clearly demarcated.

@danmoseley danmoseley closed this Oct 21, 2019
@jkotas
Copy link
Copy Markdown
Member

jkotas commented Oct 21, 2019

Some of the changes in the PR like including the filename for bad image format exceptions are good and non-contentious. They can be submitted separately from the more profound changes.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants