Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow setting breakpoints on throw expressions #22015

Open
sharwell opened this issue Sep 10, 2017 · 12 comments
Open

Allow setting breakpoints on throw expressions #22015

sharwell opened this issue Sep 10, 2017 · 12 comments

Comments

@sharwell
Copy link
Member

sharwell commented Sep 10, 2017

Version Used: 15.2

Steps to Reproduce:

  1. Add the following code

    void Method(object a, object b)
    {
      if (a == null)
        throw new ArgumentNullException(nameof(a));
    
      var tmpA = a;
      var tmpB = b ?? throw new ArgumentNullException(nameof(b));
    }
  2. Add a breakpoint which breaks into the debugger immediately before a call to new ArgumentNullException("a")

  3. Add a breakpoint which breaks into the debugger immediately before a call to new ArgumentNullException("b")

Expected Behavior:

The debugging experience is not impaired by a decision to use throw expressions to simplify code.

Actual Behavior:

The debugging experience is substantially impaired:

  • Simplicity: The breakpoint for b being null requires a conditional breakpoint with a manually-typed condition. It used multiple UI elements, further disrupting the workflow of a user attempting to add it.
  • Performance: The conditional breakpoint for b being null is very slow to evaluate compared to the unconditional breakpoint used for a being null.
@svick
Copy link
Contributor

svick commented Sep 10, 2017

What makes the throw expression special? If you're going to allow setting breakpoint on the throw expression, why not also allow all the other kinds of expressions?

@AdamSpeight2008
Copy link
Contributor

@svick It's not special but it setting a breakpoint on InvocationExpression would cover a lot the use cases.

@tmat
Copy link
Member

tmat commented Sep 10, 2017

Since similar feature request have been made previously I have filed a more general issue to track feature of placing breakpoint on an arbitrary expression: #22016. I would not hold my breath though expecting this feature to be implemented any time soon.

@tmat tmat closed this as completed Sep 10, 2017
@sharwell
Copy link
Member Author

sharwell commented Sep 10, 2017

@svick The throw expression is the only point of direct return from a method on which a breakpoint cannot be set.

@tmat I'm not nearly as interested in the general feature. Setting breakpoints on throw statements has regularly proven to be specifically valuable in debugging scenarios. It was largely the motivation for this question and later the Java debugger I wrote. Regularly-encountered problems debugging these cases also formed by particularly strong code style stance against single-line if/throw argument validation checks in Java (where debuggers cannot distinguish two statements on the same line).

Long-standing debugging experience with argument validation and real-world projects leads me to classify throw expression breakpoints as a failure to reach feature parity, while other subexpression breakpoints would simply be a new feature request. I would request that this issue be reopened with notably higher priority than the general handling of expressions (which may end up rejected for other negative usability impact).

@sharwell
Copy link
Member Author

@tmat I'm reopening this. Exceptions represent a special case in flow control which result in leaving a method. We don't need the general ability to set breakpoints in subexpressions, but we need the ability to set breakpoints on a throw expression.

@CyrusNajmabadi
Copy link
Member

i agree with sam. It seems like we just need this on throw-expressions. I'm curious what level of hte stack would be blocking this. Is it just the IDE side? Or are we not creating hte right PDB information in order to actually do this properly in the debugger?

@tmat
Copy link
Member

tmat commented Nov 17, 2017

The PDB, EnC, maybe JIT, since C# has never emitted sequence points for non-statements, so it's not tested.

@CyrusNajmabadi
Copy link
Member

CyrusNajmabadi commented Nov 17, 2017

I'd be surprised if the pdb/jit cared. There's no actual difference at the IL/jit level for throw expressions/statements right? i mean, we're just going to convert x ?? throw y into:

if (x == null)
{
    throw y;
}

as far as the rest of the system is concerned.

Right?

@tmat
Copy link
Member

tmat commented Nov 17, 2017

@CyrusNajmabadi The stack might not be empty:

F(G(), x ? y : throw expr);

@CyrusNajmabadi
Copy link
Member

I don't quite understand. Isn't that just:

var __temp1 = G();
if (!x) { throw expr };
F(__temp1, y);

?

@tmat
Copy link
Member

tmat commented Nov 17, 2017

No, we don't do stack spilling.

class C
{
   static string F(string a, string b, string c) => a;

   public static void Main()
   {
      string a = "1";
      string b = "2";
      string c = "3";
   
      F(
         a ?? throw new Exception(),
         b ?? throw new Exception(),
         c ?? throw new Exception()
      );
   }
}

Compiles to

IL_0000:  nop
  IL_0001:  ldstr      "1"
  IL_0006:  stloc.0
  IL_0007:  ldstr      "2"
  IL_000c:  stloc.1
  IL_000d:  ldstr      "3"
  IL_0012:  stloc.2
  IL_0013:  ldloc.0
  IL_0014:  dup
  IL_0015:  brtrue.s   IL_001e
  IL_0017:  pop
  IL_0018:  newobj     instance void [mscorlib]System.Exception::.ctor()
  IL_001d:  throw
  IL_001e:  ldloc.1
  IL_001f:  dup
  IL_0020:  brtrue.s   IL_0029
  IL_0022:  pop
  IL_0023:  newobj     instance void [mscorlib]System.Exception::.ctor()
  IL_0028:  throw
  IL_0029:  ldloc.2
  IL_002a:  dup
  IL_002b:  brtrue.s   IL_0034
  IL_002d:  pop
  IL_002e:  newobj     instance void [mscorlib]System.Exception::.ctor()
  IL_0033:  throw
  IL_0034:  call       string C::F(string,
                                   string,
                                   string)
  IL_0039:  pop
  IL_003a:  ret

@jnm2
Copy link
Contributor

jnm2 commented Apr 5, 2020

Would be covered by #43092 which @gafter asked me to open.

@gafter gafter self-assigned this Apr 6, 2020
@gafter gafter modified the milestones: Backlog, Compiler.Net5 Apr 6, 2020
@gafter gafter removed this from the Compiler.Net5 milestone May 1, 2020
@gafter gafter added this to the Backlog milestone May 14, 2020
@gafter gafter removed their assignment Jun 26, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants