[flutter_tools] fix crash if grouped doctor validator crashes (#60658)

diff --git a/packages/flutter_tools/lib/src/reporting/events.dart b/packages/flutter_tools/lib/src/reporting/events.dart
index 310b55e..d357bd9 100644
--- a/packages/flutter_tools/lib/src/reporting/events.dart
+++ b/packages/flutter_tools/lib/src/reporting/events.dart
@@ -103,11 +103,12 @@
   DoctorResultEvent({
     @required this.validator,
     @required this.result,
+    Usage flutterUsage,
   }) : super(
     'doctor-result',
     '${validator.runtimeType}',
     label: result.typeStr,
-    flutterUsage: globals.flutterUsage,
+    flutterUsage: flutterUsage ?? globals.flutterUsage,
   );
 
   final DoctorValidator validator;
@@ -120,10 +121,15 @@
       return;
     }
     final GroupedValidator group = validator as GroupedValidator;
+    // The validator crashed.
+    if (group.subResults == null) {
+      flutterUsage.sendEvent(category, parameter, label: label);
+      return;
+    }
     for (int i = 0; i < group.subValidators.length; i++) {
       final DoctorValidator v = group.subValidators[i];
       final ValidationResult r = group.subResults[i];
-      DoctorResultEvent(validator: v, result: r).send();
+      DoctorResultEvent(validator: v, result: r, flutterUsage: flutterUsage).send();
     }
   }
 }
diff --git a/packages/flutter_tools/test/general.shard/reporting/events_test.dart b/packages/flutter_tools/test/general.shard/reporting/events_test.dart
new file mode 100644
index 0000000..f12d2d3
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/reporting/events_test.dart
@@ -0,0 +1,68 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter_tools/src/doctor.dart';
+import 'package:flutter_tools/src/reporting/reporting.dart';
+import 'package:mockito/mockito.dart';
+
+import '../../src/common.dart';
+
+void main() {
+  testWithoutContext('DoctorResultEvent sends usage event for each sub validator', () async {
+    final Usage usage = MockUsage();
+    final GroupedValidator groupedValidator = FakeGroupedValidator(<DoctorValidator>[
+      FakeDoctorValidator('a'),
+      FakeDoctorValidator('b'),
+      FakeDoctorValidator('c'),
+    ]);
+    final ValidationResult result = await groupedValidator.validate();
+
+    final DoctorResultEvent doctorResultEvent = DoctorResultEvent(
+      validator: groupedValidator,
+      result: result,
+      flutterUsage: usage,
+    );
+
+    expect(() => doctorResultEvent.send(), returnsNormally);
+
+    verify(usage.sendEvent('doctor-result', any, label: anyNamed('label'))).called(3);
+  });
+
+  testWithoutContext('DoctorResultEvent does not crash if a synthetic crash result was used instead'
+    ' of validation. This happens when a grouped validator throws an exception, causing subResults to never '
+    ' be instantiated.', () async {
+    final Usage usage = MockUsage();
+    final GroupedValidator groupedValidator = FakeGroupedValidator(<DoctorValidator>[
+      FakeDoctorValidator('a'),
+      FakeDoctorValidator('b'),
+      FakeDoctorValidator('c'),
+    ]);
+    final ValidationResult result = ValidationResult.crash(Object());
+
+    final DoctorResultEvent doctorResultEvent = DoctorResultEvent(
+      validator: groupedValidator,
+      result: result,
+      flutterUsage: usage,
+    );
+
+    expect(() => doctorResultEvent.send(), returnsNormally);
+
+    verify(usage.sendEvent('doctor-result', any, label: anyNamed('label'))).called(1);
+  });
+}
+
+class FakeGroupedValidator extends GroupedValidator {
+  FakeGroupedValidator(List<DoctorValidator> subValidators) : super(subValidators);
+}
+
+class FakeDoctorValidator extends DoctorValidator {
+  FakeDoctorValidator(String title) : super(title);
+
+  @override
+  Future<ValidationResult> validate() async {
+    return ValidationResult.crash(Object());
+  }
+}
+
+class MockUsage extends Mock implements Usage {}