Uploaded image for project: 'Swift'
  1. Swift
  2. SR-905

precondition failure should show message in crash report

    Details

    • Type: Bug
    • Status: Open
    • Priority: Medium
    • Resolution: Unresolved
    • Component/s: Standard Library
    • Labels:
      None
    • Environment:

      OS X

      Description

      On OS X, if a precondition fails, the crash report contains no mention of the precondition. By contrast a fatalError("The message.") call, in the crash report does show:

      fatal error: The message.: file /path/to/file.swift, line 55

      precondition failures should do the same thing, just as fatalError() does and just as assert() does in C/Obj-C.

      It is far more useful to have this information in the crash report than not, and there's no reason it shouldn't appear. I consider this a bug as the message is simply lost in a release configuration even though the precondition is still checked.

        Attachments

          Activity

          Hide
          gribozavr Dmitri Gribenko added a comment -

          > Ok. I'm not sure if anyone else suggested otherwise, but at least I am certainly aware of that.

          So what is the point of including the message then, if you would still need to debug the app as usual probably in the majority of cases, especially given that including the message is not free? What problem does it solve?

          Show
          gribozavr Dmitri Gribenko added a comment - > Ok. I'm not sure if anyone else suggested otherwise, but at least I am certainly aware of that. So what is the point of including the message then, if you would still need to debug the app as usual probably in the majority of cases, especially given that including the message is not free? What problem does it solve?
          Hide
          swillits Seth Willits added a comment - - edited

          Hmm... I can't help but think that with that logic, it seems like you should be asking "what's the point of the message ever, even in debug builds, since you're just going to debug it anyway?"

          There are two major reasons:

          1) I can clearly see what condition caused the crash, and dynamic related info, in the release build's crash log without having to jump through umpteen different hoops:

          1. Copy the symbol address in the crash log
          2. Copy the stride offset for the correct library for that address
          3. Go into Xcode
          4. Open the organizer
          5. Find the right build archive
          6. Go through the hassle of revealing the archive in Finder and navigating through 4 layers of folders to find the correct dsym file
          7. Open Terminal
          8. Lookup what atos's command options are again
          9. Stick in the path to the dsym and the addresses
          10. Get the file and line number from atos
          11. Go to my SCM, Checkout the right build (because how else are line numbers going to line up?)
          12. Find the right file
          13. Jump to the given line number

          ... and do that for each crash report. That's an insanely inefficient workflow.

          (Tools can be made and do exist to make at least some of that simpler, but not everyone has the ability or the desire to make their own, or even use someone else's.)

          I want to see the message and file etc in the crash log itself, without having to do aaaaaall of that work, much like an assert does in C/Obj-C already. The precondition message combined with the stack frame will uniquely point to exactly which precondition/assert failed, which means the exact line number is irrelevant. Unlike having to use atos to convert the address into a line number (which I swear is very often wrong by one line number), and being required to use scm to checkout the old build just so I can be (almost) sure which precondition failed, if the message is in the crash log, it's simply plain as day and I can look at it in the current version of the code, and be absolutely 100% certain I'm looking at the precondition that failed. It's vastly more convenient.

          2) The second major reason is that the message parameter allows the message in the crash log to contain other related runtime values, which can help explain how to reproduce the bug. It's like focused site-of-the-crash logging. In Obj-C I've made very effective use of:

          if (!precondition) {
          	StickAStringInTheCrashLog([NSString stringWithFormat:@"....", a, b, c]);
          	assert(precondition);
          }
          

          where the string gets inserted into "Application Specific Info" via use of:

          static char *__crashreporter_info__ = nil;
          asm(".desc ___crashreporter_info__, 0x10");
          

          This has let me capture related values for hard-to-track assertion failures which give insight into how to recreate the conditions that led to the failure. In Obj-C it's necessary to use that entire if-statement because when an assert() fails the expression is printed literally in the log, meaning it's impossible to include actual values. In Swift, however, we are blessed with the behavior that precondition(), upon failure, will actually print the message parameter's string value, so that it can include dynamic values. eg:

          func foo(x: Int) {
          	precondition(x < 3, "x is \(x) -- expected < 3")
          	...
          }
          

          Without including the message in the crash log in release builds, I can't know what the value of x was, which could be really really important. Maybe I can only possibly imagine that the unexpected value is 4, but for a particular user I can see directly in the crash log that the value is 5, which turns out to be mind blowingly useful information. Generally speaking, without introducing a dedicated log file or asking the user to go dig around in Console.app ——— both of which requires actual contact info for and participation from my user ——— I may never get this info, and related to a specific file. But if the message is in the crash log, which even anonymously is submitted to me, now I have it.

          *Extremely* useful.

          Show
          swillits Seth Willits added a comment - - edited Hmm... I can't help but think that with that logic, it seems like you should be asking "what's the point of the message ever , even in debug builds, since you're just going to debug it anyway?" There are two major reasons: 1) I can clearly see what condition caused the crash, and dynamic related info, in the release build's crash log without having to jump through umpteen different hoops: Copy the symbol address in the crash log Copy the stride offset for the correct library for that address Go into Xcode Open the organizer Find the right build archive Go through the hassle of revealing the archive in Finder and navigating through 4 layers of folders to find the correct dsym file Open Terminal Lookup what atos's command options are again Stick in the path to the dsym and the addresses Get the file and line number from atos Go to my SCM, Checkout the right build (because how else are line numbers going to line up?) Find the right file Jump to the given line number ... and do that for each crash report . That's an insanely inefficient workflow. (Tools can be made and do exist to make at least some of that simpler, but not everyone has the ability or the desire to make their own, or even use someone else's.) I want to see the message and file etc in the crash log itself, without having to do aaaaaall of that work, much like an assert does in C/Obj-C already. The precondition message combined with the stack frame will uniquely point to exactly which precondition/assert failed, which means the exact line number is irrelevant. Unlike having to use atos to convert the address into a line number (which I swear is very often wrong by one line number), and being required to use scm to checkout the old build just so I can be (almost) sure which precondition failed, if the message is in the crash log, it's simply plain as day and I can look at it in the current version of the code, and be absolutely 100% certain I'm looking at the precondition that failed. It's vastly more convenient. 2) The second major reason is that the message parameter allows the message in the crash log to contain other related runtime values, which can help explain how to reproduce the bug. It's like focused site-of-the-crash logging. In Obj-C I've made very effective use of: if (!precondition) { StickAStringInTheCrashLog([NSString stringWithFormat:@ "...." , a, b, c]); assert (precondition); } where the string gets inserted into "Application Specific Info" via use of: static char *__crashreporter_info__ = nil; asm( ".desc ___crashreporter_info__, 0x10" ); This has let me capture related values for hard-to-track assertion failures which give insight into how to recreate the conditions that led to the failure. In Obj-C it's necessary to use that entire if-statement because when an assert() fails the expression is printed literally in the log, meaning it's impossible to include actual values. In Swift, however, we are blessed with the behavior that precondition(), upon failure, will actually print the message parameter's string value, so that it can include dynamic values. eg: func foo(x: Int) { precondition(x < 3, "x is \(x) -- expected < 3" ) ... } Without including the message in the crash log in release builds, I can't know what the value of x was, which could be really really important. Maybe I can only possibly imagine that the unexpected value is 4, but for a particular user I can see directly in the crash log that the value is 5, which turns out to be mind blowingly useful information. Generally speaking, without introducing a dedicated log file or asking the user to go dig around in Console.app ——— both of which requires actual contact info for and participation from my user ——— I may never get this info, and related to a specific file. But if the message is in the crash log, which even anonymously is submitted to me, now I have it. * Extremely * useful.
          Hide
          robertjpayne Robert Payne added a comment - - edited

          As the author of SnapKit which has quite a few users…

          I typically use preconditions to avoid developer programmer errors. Things like you're calling this on an invalid/wrong thread, the supported arguments are invalid even though it wasn't user input etc…

          The issue as a framework developer is we can't use these to ensure developers know they are making programmer errors because the information is lost in a release build of the framework.

          The argument that you would 'have to debug it anyways' is sort of a scapegoat. Think about how many exceptions Apple's frameworks in Objective-C fire when you make a programmer error and then imagine the developer experience if those exceptions contained no information and because you don't have the dsyms you don't even see where it's coming from. That is essentially what preconditions are like for developers using frameworks that were written in Swift with preconditions, granted in most cases you do get the dsyms and have access to at least some information.

          For now what we have to do in SnapKit is use fatalError because the message is preserved, either way we want the app to terminate and stop executing immediately. We'd prefer to use a precondition because logically it makes more sense.

          Show
          robertjpayne Robert Payne added a comment - - edited As the author of SnapKit which has quite a few users… I typically use preconditions to avoid developer programmer errors. Things like you're calling this on an invalid/wrong thread, the supported arguments are invalid even though it wasn't user input etc… The issue as a framework developer is we can't use these to ensure developers know they are making programmer errors because the information is lost in a release build of the framework. The argument that you would 'have to debug it anyways' is sort of a scapegoat. Think about how many exceptions Apple's frameworks in Objective-C fire when you make a programmer error and then imagine the developer experience if those exceptions contained no information and because you don't have the dsyms you don't even see where it's coming from. That is essentially what preconditions are like for developers using frameworks that were written in Swift with preconditions, granted in most cases you do get the dsyms and have access to at least some information. For now what we have to do in SnapKit is use fatalError because the message is preserved, either way we want the app to terminate and stop executing immediately. We'd prefer to use a precondition because logically it makes more sense.
          Hide
          groue Gwendal Roué added a comment - - edited

          We'd prefer to use a precondition because logically it makes more sense.

          More: precondition is documented to help the compiler assume some truth in unchecked builds. With all preconditions replaced with explicit checks and fatalErrors, those optimizations will not apply: unchecked builds are less efficient. Library developers are punished for helping out their users with explicit error messages...

          Show
          groue Gwendal Roué added a comment - - edited We'd prefer to use a precondition because logically it makes more sense. More: precondition is documented to help the compiler assume some truth in unchecked builds. With all preconditions replaced with explicit checks and fatalErrors, those optimizations will not apply: unchecked builds are less efficient. Library developers are punished for helping out their users with explicit error messages...
          Hide
          gribozavr Dmitri Gribenko added a comment -

          > Think about how many exceptions Apple's frameworks in Objective-C fire when you make a programmer error and then imagine the developer experience if those exceptions contained no information and because you don't have the dsyms you don't even see where it's coming from.

          This is actually a completely different argument and it is much more convincing than anything that was said on this thread. This case is completely different since we are talking about the case when no source information is available.

          Show
          gribozavr Dmitri Gribenko added a comment - > Think about how many exceptions Apple's frameworks in Objective-C fire when you make a programmer error and then imagine the developer experience if those exceptions contained no information and because you don't have the dsyms you don't even see where it's coming from. This is actually a completely different argument and it is much more convincing than anything that was said on this thread. This case is completely different since we are talking about the case when no source information is available.

            People

            • Assignee:
              Unassigned
              Reporter:
              swillits Seth Willits
            • Votes:
              2 Vote for this issue
              Watchers:
              5 Start watching this issue

              Dates

              • Created:
                Updated: