Wednesday, June 6, 2018

Wrong order of execution in Resolution Center crash report

Leave a Comment

My app got rejected from the app store because there is a button that causes it to crash when pressed on an iPad. I don't have an iPad, and the bug is unreproducible on any of the iPad simulators. Here's the crash report the Resolution Center sent me:

{"app_name":"xxx","timestamp":"2018-05-23 14:18:37.70 -0700","app_version":"0.4.5","slice_uuid":"f7e0571c-a2a9-313c-ab7b-9a9109670ff2","adam_id":1387669409,"build_version":"1","bundleID":"com.b.xxx","share_with_app_devs":false,"is_first_party":false,"bug_type":"109","os_version":"iPhone OS 11.3.1 (15E302)","incident_id":"D5ADA937-9FAF-479B-A925-7B127B5E0C44","name":"xxx"} Incident Identifier: D5ADA937-9FAF-479B-A925-7B127B5E0C44 CrashReporter Key:   cac3035154ad4b589b77fd30cfea229fc2cfaf20 Hardware Model:      xxx Process:             xxx [1949] Path:                /private/var/containers/Bundle/Application/FD86FF84-2DDF-4DFA-B23D-703D4A08316B/xxx.app/xxx Identifier:          com.b.xxx Version:             1 (0.4.5) Code Type:           ARM-64 (Native) Role:                Foreground Parent Process:      launchd [1] Coalition:           com.b.xxx [1153]   Date/Time:           2018-05-23 14:18:37.5753 -0700 Launch Time:         2018-05-23 14:18:31.9754 -0700 OS Version:          iPhone OS 11.3.1 (15E302) Baseband Version:    6.55.00 Report Version:      104  Exception Type:  EXC_BREAKPOINT (SIGTRAP) Exception Codes: 0x0000000000000001, 0x0000000180b03644 Termination Signal: Trace/BPT trap: 5 Termination Reason: Namespace SIGNAL, Code 0x5 Terminating Process: exc handler [0] Triggered by Thread:  0  Application Specific Information: BUG IN CLIENT OF LIBDISPATCH: trying to lock recursively  Filtered syslog: None found  Thread 0 name:  Dispatch queue: com.apple.main-thread Thread 0 Crashed: 0   libdispatch.dylib               0x0000000180b03644 _dispatch_gate_wait_slow$VARIANT$mp + 180 1   libdispatch.dylib               0x0000000180b04334 dispatch_once_f$VARIANT$mp + 132 2   libdispatch.dylib               0x0000000180b04334 dispatch_once_f$VARIANT$mp + 132 3   xxx                             0x00000001006cc6ec specialized static TextFieldFactory.manufacture(for:with:) + 2868972 (TextFieldFactory.swift:13) 4   xxx                             0x00000001006535ac _T09xxx22GRADEDISPLAY_MAX_WIDTH12CoreGraphics7CGFloatVvpfiAEycfU_ + 2373036 (DimensionConstants.swift:0) 5   xxx                             0x00000001006532b8 globalinit_33_D344213E1027291683078D92DF0D6E22_func214 + 2372280 (DimensionConstants.swift:220) 6   libdispatch.dylib               0x0000000180b00ae4 _dispatch_client_callout + 16 7   libdispatch.dylib               0x0000000180b042ec dispatch_once_f$VARIANT$mp + 60 8   xxx                             0x00000001006cc6ec specialized static TextFieldFactory.manufacture(for:with:) + 2868972 (TextFieldFactory.swift:13) 9   xxx                             0x00000001006cac10 static TextFieldFactory.manufacture(for:with:) + 2862096 (TextFieldFactory.swift:0) 10  xxx                             0x0000000100614f2c _T09xxx04EditA4ViewC5titleAA9TextFieldCvgAFycfU_ + 2117420 (EditClassView.swift:26) 11  xxx                             0x000000010060ff90 EditClassView.beginInit() + 2097040 (EditClassView.swift:24) 12  xxx                             0x00000001006bc970 MView.init(sizeConstraint:) + 2804080 (MView.swift:0) 13  xxx                             0x0000000100614934 EditClassView.init(cCreateSemester:clss:sizeConstraint:) + 2115892 (EditClassView.swift:237) 14  xxx                             0x00000001007184f8 specialized CCreateSemester.createCreateCourseView(_:) + 3179768 (CCreateSemester.swift:667) 15  xxx                             0x000000010071393c CCreateSemester.setupNewSemester() + 3160380 (CCreateSemester.swift:594) 16  xxx                             0x000000010071030c CCreateSemester.viewDidLoad() + 3146508 (CCreateSemester.swift:0) 17  xxx                             0x0000000100711674 @objc CCreateSemester.viewDidLoad+ 3151476 () + 28 18  UIKit                           0x000000018adeaee0 -[UIViewController loadViewIfRequired] + 1020 19  UIKit                           0x000000018ae98e98 -[UINavigationController _updateScrollViewFromViewController:toViewController:] + 76 20  UIKit                           0x000000018ae98354 -[UINavigationController _startTransition:fromViewController:toViewController:] + 172 21  UIKit                           0x000000018ae97c90 -[UINavigationController _startDeferredTransitionIfNeeded:] + 1164 22  UIKit                           0x000000018ae97720 -[UINavigationController __viewWillLayoutSubviews] + 164 23  UIKit                           0x000000018ae8b424 -[UILayoutContainerView layoutSubviews] + 188 24  UIKit                           0x000000018ade3770 -[UIView+ 309104 (CALayerDelegate) layoutSublayersOfLayer:] + 1420 25  QuartzCore                      0x000000018538525c -[CALayer layoutSublayers] + 184 26  QuartzCore                      0x00000001853893ec CA::Layer::layout_if_needed+ 1209324 (CA::Transaction*) + 324 27  QuartzCore                      0x00000001852f5aa0 CA::Context::commit_transaction+ 604832 (CA::Transaction*) + 320 28  QuartzCore                      0x000000018531d5d0 CA::Transaction::commit+ 767440 () + 580 29  QuartzCore                      0x000000018531e450 CA::Transaction::observer_callback+ 771152 (__CFRunLoopObserver*, unsigned long, void*) + 92 30  CoreFoundation                  0x00000001811b6910 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 32 31  CoreFoundation                  0x00000001811b4238 __CFRunLoopDoObservers + 412 32  CoreFoundation                  0x00000001811b4884 __CFRunLoopRun + 1436 33  CoreFoundation                  0x00000001810d4da8 CFRunLoopRunSpecific + 552 34  GraphicsServices                0x00000001830b7020 GSEventRunModal + 100 35  UIKit                           0x000000018b0b578c UIApplicationMain + 236 36  xxx                             0x00000001004179b8 main + 31160 (PercentBasedUnequalField.swift:12) 37  libdyld.dylib                   0x0000000180b65fc0 start + 4  Thread 1: 0   libsystem_kernel.dylib          0x0000000180c95d84 __workq_kernreturn + 8 1   libsystem_pthread.dylib         0x0000000180e33eb4 _pthread_wqthread + 928 2   libsystem_pthread.dylib         0x0000000180e33b08 start_wqthread + 4  Thread 2: 0   libsystem_kernel.dylib          0x0000000180c95d84 __workq_kernreturn + 8 1   libsystem_pthread.dylib         0x0000000180e33eb4 _pthread_wqthread + 928 2   libsystem_pthread.dylib         0x0000000180e33b08 start_wqthread + 4  Thread 3: 0   libsystem_kernel.dylib          0x0000000180c95d84 __workq_kernreturn + 8 1   libsystem_pthread.dylib         0x0000000180e340a0 _pthread_wqthread + 1420 2   libsystem_pthread.dylib         0x0000000180e33b08 start_wqthread + 4  Thread 4: 0   libsystem_pthread.dylib         0x0000000180e33b04 start_wqthread + 0  Thread 5 name:  com.apple.uikit.eventfetch-thread Thread 5: 0   libsystem_kernel.dylib          0x0000000180c73e08 mach_msg_trap + 8 1   libsystem_kernel.dylib          0x0000000180c73c80 mach_msg + 72 2   CoreFoundation                  0x00000001811b6e40 __CFRunLoopServiceMachPort + 196 3   CoreFoundation                  0x00000001811b4908 __CFRunLoopRun + 1568 4   CoreFoundation                  0x00000001810d4da8 CFRunLoopRunSpecific + 552 5   Foundation                      0x0000000181b49674 -[NSRunLoop+ 34420 (NSRunLoop) runMode:beforeDate:] + 304 6   Foundation                      0x0000000181b4951c -[NSRunLoop+ 34076 (NSRunLoop) runUntilDate:] + 148 7   UIKit                           0x000000018ad9a7e4 -[UIEventFetcher threadMain] + 136 8   Foundation                      0x0000000181c59efc __NSThread__start__ + 1040 9   libsystem_pthread.dylib         0x0000000180e35220 _pthread_body + 272 10  libsystem_pthread.dylib         0x0000000180e35110 _pthread_body + 0 11  libsystem_pthread.dylib         0x0000000180e33b10 thread_start + 4  Thread 6: 0   libsystem_pthread.dylib         0x0000000180e33b04 start_wqthread + 0  Thread 7 name:  RLMRealm notification listener Thread 7: 0   libsystem_kernel.dylib          0x0000000180c7834c kevent + 8 1   Realm                           0x0000000100a05a80 realm::_impl::ExternalCommitHelper::listen() + 88704 (external_commit_helper.cpp:203) 2   Realm                           0x0000000100a063ec std::__1::__async_func<realm::_impl::ExternalCommitHelper::ExternalCommitHelper(realm::_impl::RealmCoordinator&)::$_0>::operator()() + 91116 (future:2323) 3   Realm                           0x0000000100a06374 std::__1::__async_assoc_state<void, std::__1::__async_func<realm::_impl::ExternalCommitHelper::ExternalCommitHelper(realm::_impl::RealmCoordinator&)::$_0> >::__execute() + 90996 (future:1041) 4   Realm                           0x0000000100a06524 std::__1::__thread_proxy<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, void (std::__1::__async_assoc_state<void, std::__1::__async_func<realm::_impl::ExternalCommitHelper::ExternalCommitHelper(realm::_impl::RealmCoordinator&)::$_0> >::*)(), std::__1::__async_assoc_state<void, std::__1::__async_func<realm::_impl::ExternalCommitHelper::ExternalCommitHelper(realm::_impl::RealmCoordinator&)::$_0> >*> >(void*, void*) + 91428 (thread:354) 5   libsystem_pthread.dylib         0x0000000180e35220 _pthread_body + 272 6   libsystem_pthread.dylib         0x0000000180e35110 _pthread_body + 0 7   libsystem_pthread.dylib         0x0000000180e33b10 thread_start + 4  Thread 8 name:  RLMRealm notification listener Thread 8: 0   libsystem_kernel.dylib          0x0000000180c7834c kevent + 8 1   Realm                           0x0000000100a05a80 realm::_impl::ExternalCommitHelper::listen() + 88704 (external_commit_helper.cpp:203) 2   Realm                           0x0000000100a063ec std::__1::__async_func<realm::_impl::ExternalCommitHelper::ExternalCommitHelper(realm::_impl::RealmCoordinator&)::$_0>::operator()() + 91116 (future:2323) 3   Realm                           0x0000000100a06374 std::__1::__async_assoc_state<void, std::__1::__async_func<realm::_impl::ExternalCommitHelper::ExternalCommitHelper(realm::_impl::RealmCoordinator&)::$_0> >::__execute() + 90996 (future:1041) 4   Realm                           0x0000000100a06524 std::__1::__thread_proxy<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, void (std::__1::__async_assoc_state<void, std::__1::__async_func<realm::_impl::ExternalCommitHelper::ExternalCommitHelper(realm::_impl::RealmCoordinator&)::$_0> >::*)(), std::__1::__async_assoc_state<void, std::__1::__async_func<realm::_impl::ExternalCommitHelper::ExternalCommitHelper(realm::_impl::RealmCoordinator&)::$_0> >*> >(void*, void*) + 91428 (thread:354) 5   libsystem_pthread.dylib         0x0000000180e35220 _pthread_body + 272 6   libsystem_pthread.dylib         0x0000000180e35110 _pthread_body + 0 7   libsystem_pthread.dylib         0x0000000180e33b10 thread_start + 4  Thread 0 crashed with ARM Thread State (64-bit):     x0: 0x00000001007cdd68   x1: 0x0000000000000301   x2: 0x0000000000000000   x3: 0x0000000000000000     x4: 0x00000001007654a0   x5: 0x0000000000000011   x6: 0x0000000000000000   x7: 0x00000001c8041ce0     x8: 0x0000000000000003   x9: 0x0000000000000301  x10: 0x0000000000000000  x11: 0x0000000000000000    x12: 0x0000000000000001  x13: 0x0000000000000000  x14: 0x0000000000000000  x15: 0x0000000000000001    x16: 0x0000000180b042b0  x17: 0x0000000000000000  x18: 0x0000000000000000  x19: 0x0000000000000000    x20: 0x00000000ffffffff  x21: 0x00000001007cdd68  x22: 0x0000000000000303  x23: 0x0000000000000001    x24: 0x0000000000000001  x25: 0x0000000101534280  x26: 0x0000000000000000  x27: 0x0000000000000001    x28: 0x0000000000000001   fp: 0x000000016f9eb140   lr: 0x0000000180b04334     sp: 0x000000016f9eb120   pc: 0x0000000180b03644 cpsr: 0x60000000 

Moving up from Line 11 Thread 0 here is the code execution:

Line 11: EditClassView.beginInit()

class EditClassView: MStackView {      // Parent implementation does nothing     override func beginInit() {          // First time `title` is referenced. It's lazy, so it's initialized here         title.text = clss.getTitle()          // Crashes before here         ...     } } 

Line 10: Initialization of local title property in EditClassView.title's lazy initializer:

lazy private(set) var title: TextField = {      let title = TF.manufacture(for: .allPurpose, with: TParams(textSize: 21, inputView: nil, placeholder: "Class Title"))      // Crashes before here     ... }() 

Line 9: I think this is just referring to the TextFieldFactory.manufacture(for:with:) reference, and not the actual execution of the function itself:

Line 8: I'm not sure what this is referring to. It appears to just be the TextFieldFactory.TextFieldParams typealias because that's what's on TextFieldFactory.swift:13:

typealias TParams = TextFieldFactory.TextFieldParams  // Here is the definition     class TextFieldParams {      static let DEFAULT_TEXT: String? = nil     static let DEFAULT_FONT_NAME = "poiretone-regular"     static let DEFAULT_TEXT_SIZE = MEDIUM_TEXT_SIZE     static let DEFAULT_TEXT_COLOR = UIColor.black     static let DEFAULT_TINT_COLOR = UIColor.black     static let DEFAULT_TEXT_ALIGNMENT = NSTextAlignment.center     static let DEFAULT_USES_AUTORESIZING_MASK = true     static let DEFAULT_INPUT_VIEW = TextFieldFactory.EMPTY_INPUT_VIEW     static let DEFAULT_PLACEHOLDER: String? = nil     static let DEFAULT_ADJUST_WIDTH_WITH_TEXT = false     static let DEFAULT_MIN_WIDTH: CGFloat? = nil     static let DEFAULT_MAX_WIDTH: CGFloat? = nil     static let DEFAULT_EMPTY_WIDTH: CGFloat? = nil      let text: String?     let fontName: String     let textSize: CGFloat     let textColor: UIColor     let tintColor: UIColor     let textAlignment: NSTextAlignment     let translatesAutoresizingMaskIntoConstraints: Bool     let inputView: UIView?     let placeholder: String?     let adjustWidthWithText: Bool     let minWidth: CGFloat?     let maxWidth: CGFloat?     let emptyWidth: CGFloat?      var font: UIFont {   return UIFont(name: fontName, size: textSize)!   }      init(text: String? = DEFAULT_TEXT,          fontName: String = DEFAULT_FONT_NAME,          textSize: CGFloat = DEFAULT_TEXT_SIZE,          textColor: UIColor = DEFAULT_TEXT_COLOR,          tintColor: UIColor = DEFAULT_TINT_COLOR,          textAlignment: NSTextAlignment = DEFAULT_TEXT_ALIGNMENT,          translatesAutoresizingMaskIntoConstraints: Bool = DEFAULT_USES_AUTORESIZING_MASK,          inputView: UIView? = DEFAULT_INPUT_VIEW,          placeholder: String? = DEFAULT_PLACEHOLDER,          adjustWidthWithText: Bool = DEFAULT_ADJUST_WIDTH_WITH_TEXT,          minWidth: CGFloat? = DEFAULT_MIN_WIDTH,          maxWidth: CGFloat? = DEFAULT_MAX_WIDTH,          emptyWidth: CGFloat? = DEFAULT_EMPTY_WIDTH         ) {          self.text = text         self.fontName = fontName         self.textSize = textSize         self.textColor = textColor         self.tintColor = tintColor         self.textAlignment = textAlignment         self.translatesAutoresizingMaskIntoConstraints = translatesAutoresizingMaskIntoConstraints         self.inputView = inputView         self.placeholder = placeholder         self.adjustWidthWithText = adjustWidthWithText         self.minWidth = minWidth         self.maxWidth = maxWidth         self.emptyWidth = emptyWidth     } } 

Now here's where I get lost. Line 7 and 6 don't refer to my code at all, so I'm not sure what it's doing but next thing you know one of my global constants --GRADEDISPLAY_MAX_WIDTH -- is getting initialized on Line 5. And bear in mind that it has nothing to do with EditClassView at all let alone EditClassView.title.

But before I show its initialization, there is a link. GRADEDISPLAY_MAX_WIDTH is referenced in TF.manufacture(for:with:), but you'll see that the function is just one big switch block, and GRADEDISPLAY_MAX_WIDTH is not referenced in the case we should be executing in which is odd. If you look back to Line 10, you'll see the relevant case is .allPurpose, all the way at the end of the function:

static func manufacture(for textFieldPurpose: TextFieldPurpose, with params: TextFieldParams = TextFieldParams()) -> TextField {      switch textFieldPurpose {      case .notifyingTimeField:          var ntf = makeNotifyingFocusableTimeField(with: params as! NotifyingFocusableTimeFieldParams)         ntf.frame.size.width = CGFloat.timeFieldWidth         ntf.underline()          return ntf      case .timeField:          var ftf = makeFocusableTimeField(with: params as! FocusableTimeFieldParams)         ftf.frame.size.width = CGFloat.timeFieldWidth         ftf.underline()          return ftf      case .gradingCategoryTitle:          var tf = makeUnderlineTextField(with: UnderlineTextFieldParams(edgeInsets: UIEdgeInsetsMake(0, AC_TITLE_DEFAULT_SIDE_INSET, UnderlineLabel.DEFAULT_BOTTOM_INSET, AC_TITLE_DEFAULT_SIDE_INSET),                                                                        text: params.text,                                                                        textSize: LARGE_TEXT_SIZE,                                                                        inputView: nil,                                                                        adjustWidthWithText: true,                                                                        maxWidth: params.maxWidth)         )         tf.underline()          return tf      case .whereTextField:          var tf = makeUnderlineTextField(with: UnderlineTextFieldParams(inputView: nil,                                                                        adjustWidthWithText: true,                                                                        minWidth: WHERE_TEXTFIELD_MIN_WIDTH,                                                                        maxWidth: params.maxWidth)         )         tf.underline()          return tf      case .notifyingDateField:          var ndf = makeNotifyingFocusableDateField(with: params as! ContextualFocusableParams)         ndf.frame.size.width = CGFloat.timeFieldWidth         ndf.underline()          return ndf      case .percentTotalField, .percentEachField, .percentUnequalField, .pointsTotalField, .pointsEachField, .pointsUnequalField, .percentExamTotalField, .percentEachExamField, .percentFinalField, .pointsExamTotalField, .pointsEachExamField, .pointsFinalField:          guard let params = params as? ContextualFocusableParams else {              fatalError("A ContextualFocusableParams must be passed in order to manufacture a FocusableWeightField")         }          var f = makeFocusableWeightField(for: textFieldPurpose, with: ContextualFocusableParams(context: params.context,                                                                                                 adjustWidthWithText: true,                                                                                                 minWidth: CGFloat.timeFieldWidth)         )         f.underline()          return f      case .customTitleField:          let edgeInsets = UIEdgeInsetsMake(0, CUSTOM_TITLE_SIDE_INSET, UnderlineLabel.DEFAULT_BOTTOM_INSET, CUSTOM_TITLE_SIDE_INSET)         var tf = makeUnderlineTextField(with: UnderlineTextFieldParams(edgeInsets: edgeInsets,                                                                        inputView: nil,                                                                        placeholder: params.placeholder,                                                                        adjustWidthWithText: true,                                                                        minWidth: params.minWidth,                                                                        maxWidth: params.maxWidth,                                                                        emptyWidth: params.emptyWidth)         )         tf.underline()          return tf      case .gradeField:          let tf = initializedTextField(textField: PercentSuffixedTextField(),                                       params: TParams(text: params.text,                                                       textSize: AV_GRADE_TEXT_SIZE,                                                       textColor: UIColor.defaultTextColor,                                                       inputView: nil)         )         tf.frame.size = CGSize(width: GRADEDISPLAY_MAX_WIDTH, height: GV_HEIGHT)         tf.isEnabled = false         tf.keyboardType = .numberPad          return tf      case .fractionField:          let tf = makeTextField(with: TParams(text: params.text,                                              textSize: AV_GRADE_TEXT_SIZE,                                              textColor: UIColor.defaultTextColor,                                              inputView: nil)         )         tf.frame.size.width = GRADEDISPLAY_MAX_WIDTH         tf.isEnabled = false         tf.keyboardType = .numberPad          return tf      case .assessmentTitle:          let tf = makeTextField(with: TParams(text: params.text,                                              textSize: AV_TITLE_TEXT_SIZE,                                              textColor: UIColor.defaultTextColor,                                              placeholder: String.classTitlePlaceholder,                                              adjustWidthWithText: true)         )         tf.isEnabled = false          return tf      case .weightField:          let tf = initializedTextField(textField: PercentSuffixedTextField(adjustWidthWithText: true, minWidth: params.minWidth),                                       params: TParams(text: params.text,                                                       textSize: params.textSize,                                                       textAlignment: .right,                                                       inputView: nil)         )         tf.keyboardType = .decimalPad         tf.performableActions = []          return tf      // `GRADEDISPLAY_MAX_WIDTH` is referenced in the `.gradeField`      // and `.fractionField` cases just above here     case .allPurpose: return makeTextField(with: params)      case .allPurposeInsetting: return makeInsetTextField(with: params as! ITParams)     } } 

Line 5: I think this is the initialization of GRADEDISPLAY_MAX_WIDTH because DimensionConstants.swift:220 is where its property definition begins

let GRADEDISPLAY_MAX_WIDTH: CGFloat = {      let gradeField = TF.manufacture(for: .allPurpose, with: TParams(text: "100%", textSize: 17))      // From the look of the crash report, it doesn't look      // like execution makes it this far     let trailingMargin: CGFloat = 4      return proportionedWidth(gradeField.frame.size.width + trailingMargin) }() 

I'm not sure what Line 4 is referring to. Could it be GRADEDISPLAY_MAX_WIDTH itself?

And finally the last reference to my code in the crash report.

Line 3: Which just appears to be the same as Line 8...

The priority here is fixing the crash, and I think it has something to do with what's going on on Lines 6 and 7 of the stack trace. I could get into my code base, but take my word for it that GRADEDISPLAY_MAX_WIDTH is not referenced anywhere in EditClassView or any code that is related to it, so there's no reason it should be seen anywhere remotely near EditClassView in any stack trace.

Can anyone shed some light on what's going on on Lines 6 and 7?

This may be another clue:

On Line 36 in Thread 0 PercentBasedUnequalField is referenced; specifically line 12 in it. PercentBasedUnequalField is actually not used anywhere within the executing code of the app. It's part of a new feature I'm working on. I left the file in the bundle because it would have been a hassle to remove it because the current source branch branched off that incomplete feature.

The similarity here is that like GRADEDISPLAY_MAX_WIDTH, PercentBasedUnequalField is also referenced in an unrelated switch case in TF.manufacture(for:with:).

Update

Found this similar issue where the poster indicates all the cases of a switch statement are being executed as a swift optimization. That would explain why GRADEDISPLAY_MAX_WIDTH is being initialized even though the case that accesses it isn't matched. I'll try adding the optimization disabling annotation mentioned in the link and update if it fixes the issue.

0 Answers

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment