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

StackTraceFormatting{Options}#14650

Closed
benaadams wants to merge 3 commits intodotnet:masterfrom
benaadams:stacktrace2
Closed

StackTraceFormatting{Options}#14650
benaadams wants to merge 3 commits intodotnet:masterfrom
benaadams:stacktrace2

Conversation

@benaadams
Copy link
Member

@benaadams benaadams commented Oct 22, 2017

Clean follow up to #14608

Adds extra .ctor overload + param to DebuggerNonUserCodeAttribute

[Flags]
internal enum StackTraceFormattingOptions
{
    None                   = 0,
    StackTraceHidden       = 1 << 0
}
public partial class DebuggerNonUserCodeAttribute
{
    /// <summary>Initializes the <see cref="DebuggerNonUserCodeAttribute"/>.</summary>
    /// <param name="stackTraceOptions">The <see cref="StackTraceFormattingOptions"/> hint.</param>
    internal DebuggerNonUserCodeAttribute(StackTraceFormattingOptions stackTraceOptions)
    {
        StackTraceOptions = stackTraceOptions;
    }

    /// <summary>Gets the <see cref="StackTraceFormattingOptions"/>.</summary>
    internal StackTraceFormattingOptions StackTraceOptions { get; }
}

Adds StackTrace configuration overrides (for back-compat/runtime switching)

[Flags]
internal enum StackTraceFormatting
{
    None = 0,
    ExcludeStackTraceHidden   = 1 << 0,
    ExcludeDispatchBoundaries = 1 << 1
}
public partial class StackTrace
{
    internal static StackTraceFormatting FormattingOptions { get; set; }
            = StackTraceFormatting.ExcludeStackTraceHidden |
              StackTraceFormatting.ExcludeDispatchBoundaries;
}

As shown in #14564 similar can be achieved by altering the call chain and the Jit inlines sometimes and doesn't others so what's seen varies - so would only be breaking change if a dependency was taken on the names of private functions in coreclr and the order in which they were called; and then depending on it via Exception.ToString() - at which point pretty much all changes (i.e. internal implementation changes) become breaking?

/cc @tmat @JamesNK @aelij @stephentoub @noahfalk @jkotas PTAL


/// <summary>Initializes the <see cref="DebuggerNonUserCodeAttribute"/>.</summary>
/// <param name="stackTraceOptions">The <see cref="StackTraceFormattingOptions"/> hint.</param>
internal DebuggerNonUserCodeAttribute(StackTraceFormattingOptions stackTraceOptions)
Copy link
Member Author

@benaadams benaadams Oct 22, 2017

Choose a reason for hiding this comment

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

Would need corresponding change in corert as not in shared partition (or moving to shared partition and type in corert removed)

@benaadams
Copy link
Member Author

benaadams commented Oct 22, 2017

More terse stack trace output

System.Exception: Inner1 ---> System.Exception: Inner2 ---> System.Exception: Inner3 ---> System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
   at System.Collections.Generic.List`1.Enumerator.MoveNextRare() 
   at System.Collections.Generic.List`1.Enumerator.MoveNext() 
   at Program.FirstException(Int32 value, Int32 index)
   at Program.<>c.<LinqIteration>b__4_0(Int32 x, Int32 i)
   at System.Linq.Enumerable.<SkipWhileIterator>d__179`1.MoveNext()
   at System.Collections.Generic.LargeArrayBuilder`1.AddRange(IEnumerable`1 items)
   at System.Collections.Generic.EnumerableHelpers.ToArray[T](IEnumerable`1 source)
   at Program.LinqIteration(Int32 count)
   at Program.<Iterator>d__5.MoveNext()
   at Program.InnerException3()
   --- End of inner exception stack trace ---
   at Program.InnerException3()
   at Program.InnerType`1.InnerException2(Int32 count)
   --- End of inner exception stack trace ---
   at Program.InnerType`1.InnerException2(Int32 count)
   at Program.InnerException1[T](Int32 count)
   --- End of inner exception stack trace ---
   at Program.InnerException1[T](Int32 count)
   at Program.NonAsyncValueTask(Int32 count, String name)
   at Program.<ValueTaskAsync>d__10.MoveNext()
   at Program.<TaskTAsync>d__12.MoveNext()
   at Program.<MethodAsync>d__15.MoveNext()
   at Program.<ConfiguredAsync>d__14.MoveNext()
   at Program.<MethodAsync>d__15.MoveNext()
   at Program.<ConfiguredAsync>d__14.MoveNext()
   at Program.<MethodAsync>d__15.MoveNext()
   at Program.<Main>d__1.MoveNext()

Rather than the less terse current version

@benaadams
Copy link
Member Author

Added api request to make the changes public and for wider use https://github.com/dotnet/corefx/issues/24796

@benaadams
Copy link
Member Author

Corert change for moving (some) Debugger attributes to shared partition dotnet/corert#4774

@tmat
Copy link
Member

tmat commented Oct 22, 2017

A couple of things I'm not fan of:

  • the formatting options in a static field
  • extra parameter to DebuggerNonUserCodeAttribute

I'd prefer the following API:

namespace System.Diagnostics
{
  [Flags]
  public enum StackTraceFormat
  {
     Default = 0,
     ExcludeNonUserCode = 1 << 0,
     ExcludeDispatchBoundaries = 1 << 1    
  }

  public class StackTrace
  {
     // New overload:
     public string ToString(StackTraceFormat format) { ... }

     // Existing overload - no change in behavior
     public string ToString() => ToString(StackTraceFormat.Default);
  }
}

and maybe:

namespace System
{
  public class Exception
  {
     string ToString(StackTraceFormat format);
  }
}

Although one can get a stack trace from exception like so: new StackTrace(e).ToString(), the list of inner exception messages would need to be included manually.

@aelij
Copy link

aelij commented Oct 22, 2017

@tmat

the formatting options in a static field

But then there wouldn't be a way to set the default, which is used by Exception.ToString(), and during exception serialization (stack trace is serialized as a string).

@benaadams
Copy link
Member Author

I'd prefer the following API:

With

ToString() => ToString(StackTraceFormat.ExcludeNonUserCode | StackTraceFormat.ExcludeDispatchBoundaries) 

Then would be a breaking(ish) change on DebuggerNonUserCodeAttribute

If its only for the string overload; then it would need an additional Exception.ToString(StackTraceFormat format); also be opt-in - so back to not being effective for the 95% case; so the only people that it will be effective for are those people probably already processing their stack traces and your common or garden dev will still be thinking wtf when looking at their logs.

@tmat
Copy link
Member

tmat commented Oct 22, 2017

@benaadams I'm not proposing that. My proposal is: ToString() => ToString(StackTraceFormat.Default)

@tmat
Copy link
Member

tmat commented Oct 22, 2017

@aelij I have updated my comment to add Exception.ToString(format). For serialization -- not sure, but perhaps we need something serialization specific. But I'd rather avoid global state that affects everything.

@tmat
Copy link
Member

tmat commented Oct 22, 2017

@benaadams Updated my proposal to make it clear.

@benaadams
Copy link
Member Author

@tmat doesn't that render this change largely pointless? The aim is to change the default behaviour

@tmat
Copy link
Member

tmat commented Oct 22, 2017

@benaadams No it's not pointless. I don't think we can change the default behavior without breaking things. Frameworks that communicate errors to users that include stack traces can use the new API (e.g. test frameworks, loggers, etc.).

@tmat
Copy link
Member

tmat commented Oct 22, 2017

Also, we can start with explicit API and then add global App.config switch that allows users to opt-in (change the global default) without changing their app code.

@stephentoub
Copy link
Member

I don't think we can change the default behavior without breaking things.

What are you concerned about breaking? Relying on the exact frames that show up in a stack trace string from an exception is super brittle, changes based on optimizations applied, etc.

@benaadams
Copy link
Member Author

benaadams commented Oct 22, 2017

I don't think we can change the default behavior without breaking things.

As shown in #14564 I can erase all these stack traces anyway by changing the code where the exception is thrown (rather than the stack traces). So saying it can't be changed means no new private functions can be renamed or way they call each other changed.

As a lot of the functions flow have already changed; using span and memory overloads internally, those would then also be breaking changes by this definition.

@tmat
Copy link
Member

tmat commented Oct 22, 2017

Someone will be broken for sure. If we decide we don't care about those cases that's fine. The value of the default is an orthogonal discussion. Let's add an explicit API first then we can discuss what the default should be.

@stephentoub
Copy link
Member

stephentoub commented Oct 22, 2017

Someone will be broken for sure.

More so than from various refactorings? Why?

I believe the answer is "no", given that Ben already demonstrated he can achieve the same output just by refactoring.

Let's add an explicit API first then we can discuss what the default should be.

Why? As you say, they're orthogonal. I don't see why we need to hold up one over the other.

@tmat
Copy link
Member

tmat commented Oct 22, 2017

More so than from various refactorings? Why?

Because the attribute is not applied only on internal helpers we change, but users may have it on other code, including public API and methods marked with no inlining as well.

Why? As you say, they're orthogonal. I don't see why we need to hold up one over the other.

Fair point. We can indeed discuss in parallel, just wanted to separate one from the other. Explicit API seems to be useful and less controversial to me.

@stephentoub
Copy link
Member

Because the attribute is not applied only on internal helpers we change

Not if we use the internal only attribute for now, like the previous version had. That's the one we should go with for now, and stop complicating this. If public API is exposed, a different attribute can be used then.

@tmat
Copy link
Member

tmat commented Oct 22, 2017

@stephentoub OK, I guess I missed the point of this change. If you just want to hide a few internal Framework methods then we can do whatever. I though we are designing something applicable more generally. If that's not the case then I don't think why we need to have any formatting options. Just put the attribute on the helpers and filter them out without any options.

@benaadams
Copy link
Member Author

benaadams commented Oct 22, 2017

Because the attribute is not applied only on internal helpers we change, but users may have it on other code, including public API and methods marked with no inlining as well.

Which is why it should either be a different attribute or an additional parameter.

The issue isn't non-user code stack frames; its stack frames that are not part of the error.

This code isn't run until after the exception is thrown; its nothing to do with the error - it helps nothing with diagnostics - it is literally noise:

   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() 
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 
   at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task) 
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult() 

This code is after the error, its just factored out into a separate throw helper to avoid bloating the generic code

   at System.ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion()

Is all post-error frames which are being filtered out

@tmat
Copy link
Member

tmat commented Oct 22, 2017

@benaadams If you are only trying to address the issue with async then why do we need an attribute at all? Can't we just hide all frames from TaskAwaiter type?

@benaadams
Copy link
Member Author

benaadams commented Oct 22, 2017

Also hiding

System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() 

and anything in the ThrowHelper class

@benaadams
Copy link
Member Author

Not if we use the internal only attribute for now, like the previous version had. That's the one we should go with for now, and stop complicating this.

Will revert

@tmat
Copy link
Member

tmat commented Oct 22, 2017

The same thing. That can be all easily hard coded in a helper method that checks for 3 declaring types.

@benaadams
Copy link
Member Author

benaadams commented Oct 22, 2017

8 types

  • ConfiguredValueTaskAwaitable<TResult>.ConfiguredValueTaskAwaiter
  • ValueTaskAwaiter<TResult>
  • TaskAwaiter
  • TaskAwaiter<TResult>
  • ConfiguredTaskAwaitable.ConfiguredTaskAwaiter
  • ConfiguredTaskAwaitable<TResult>.ConfiguredTaskAwaiter
  • ExceptionDispatchInfo
  • ThrowHelper

Also for the awaiters its only certain methods (the ones that happen post-exception) that should be filtered.

BTW: http://source.roslyn.io/#Microsoft.CodeAnalysis.Scripting/Hosting/CommonMemberFilter.cs

Its already missing some for ValueTask; attribute is less fragile

@tmat
Copy link
Member

tmat commented Oct 22, 2017

@benaadams Fair enough, internal attribute distinct from DebuggerNonUserCodeAttribute is a fine solution that addresses the very specific task of removing these helper frames from stack trace strings.
However, it won't help anyone who needs to do their stack frame processing, nor it will help the debugger to identify these members and hide them when JMC is on.

Perhaps we can mark the methods/types by both attributes.

I was hoping though that we could add public API on StackTrace that would remove all "non user code" frames, so that this stack clean up isn't limited only to CoreFX libraries. I guess we can pursue that another time.

@tmat
Copy link
Member

tmat commented Oct 22, 2017

Its already missing some; attribute is less fragile

It's missing those that were added after this code was written. Having the special attribute won't fix it for scripting, since this code is not in CoreFX thus can't use an internal attribute.

@benaadams
Copy link
Member Author

I was hoping though that we could add public API on StackTrace that would remove all "non user code" frames, so that this stack clean up isn't limited only to CoreFX libraries. I guess we can pursue that another time.

API request might be a good place to follow up https://github.com/dotnet/corefx/issues/24796

@benaadams
Copy link
Member Author

StackTraceHidden PR #14652

@benaadams benaadams closed this Oct 22, 2017
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.

5 participants