[cfe] Mark enhanced enum toString method as synthetic

This fixes a bug where this method is showing up in coverage reports.

Also needed some plumbing in the VM for this flag, because we were
ignoring it before.

Change-Id: I9200a16dab488f5f880b9797243bd2f39de993b3
Fixes: https://github.com/dart-lang/coverage/issues/386
TEST=Added a source_report_test
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/245041
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Commit-Queue: Liam Appelbe <liama@google.com>
diff --git a/pkg/front_end/lib/src/fasta/source/source_enum_builder.dart b/pkg/front_end/lib/src/fasta/source/source_enum_builder.dart
index f2812d0..43f0251 100644
--- a/pkg/front_end/lib/src/fasta/source/source_enum_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_enum_builder.dart
@@ -388,7 +388,8 @@
           AsyncMarker.Sync,
           procedureNameScheme,
           isExtensionMember: false,
-          isInstanceMember: true);
+          isInstanceMember: true,
+          isSynthetic: true);
       members["toString"] = toStringBuilder;
     }
     String className = name;
diff --git a/pkg/front_end/lib/src/fasta/source/source_procedure_builder.dart b/pkg/front_end/lib/src/fasta/source/source_procedure_builder.dart
index 1a7fe1f..2c412dd 100644
--- a/pkg/front_end/lib/src/fasta/source/source_procedure_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_procedure_builder.dart
@@ -76,7 +76,8 @@
       NameScheme nameScheme,
       {required bool isExtensionMember,
       required bool isInstanceMember,
-      String? nativeMethodName})
+      String? nativeMethodName,
+      bool isSynthetic: false})
       // ignore: unnecessary_null_comparison
       : assert(isExtensionMember != null),
         // ignore: unnecessary_null_comparison
@@ -90,7 +91,8 @@
         isExtensionInstanceMember ? ProcedureKind.Method : kind,
         new FunctionNode(null),
         fileUri: libraryBuilder.fileUri,
-        reference: procedureReference)
+        reference: procedureReference,
+        isSynthetic: isSynthetic)
       ..startFileOffset = startCharOffset
       ..fileOffset = charOffset
       ..fileEndOffset = charEndOffset
diff --git a/pkg/kernel/binary.md b/pkg/kernel/binary.md
index 4499a08..63f765a 100644
--- a/pkg/kernel/binary.md
+++ b/pkg/kernel/binary.md
@@ -437,7 +437,7 @@
   Byte stubKind; // Index into the ProcedureStubKind enum above.
   UInt flags (isStatic, isAbstract, isExternal, isConst,
               isRedirectingFactory, isExtensionMember,
-              isNonNullableByDefault);
+              isNonNullableByDefault, isSynthetic);
   Name name;
   List<Expression> annotations;
   MemberReference stubTarget; // May be NullReference.
diff --git a/runtime/vm/compiler/frontend/kernel_translation_helper.h b/runtime/vm/compiler/frontend/kernel_translation_helper.h
index 046f874..65690a1 100644
--- a/runtime/vm/compiler/frontend/kernel_translation_helper.h
+++ b/runtime/vm/compiler/frontend/kernel_translation_helper.h
@@ -608,6 +608,7 @@
   bool IsAbstract() const { return (flags_ & kAbstract) != 0; }
   bool IsExternal() const { return (flags_ & kExternal) != 0; }
   bool IsConst() const { return (flags_ & kConst) != 0; }
+  bool IsSynthetic() const { return (flags_ & kSyntheticProcedure) != 0; }
   bool IsForwardingStub() const {
     return stub_kind_ == kAbstractForwardingStubKind ||
            stub_kind_ == kConcreteForwardingStubKind;
diff --git a/runtime/vm/kernel_loader.cc b/runtime/vm/kernel_loader.cc
index a9835fa..5cb7d4f 100644
--- a/runtime/vm/kernel_loader.cc
+++ b/runtime/vm/kernel_loader.cc
@@ -1977,6 +1977,7 @@
   bool is_abstract = procedure_helper.IsAbstract();
   bool is_external = procedure_helper.IsExternal();
   bool is_extension_member = procedure_helper.IsExtensionMember();
+  bool is_synthetic = procedure_helper.IsSynthetic();
   String& native_name = String::Handle(Z);
   bool scan_annotations_lazy;
   bool has_pragma_annotation;
@@ -2009,7 +2010,8 @@
   function.set_has_pragma(has_pragma_annotation);
   function.set_end_token_pos(procedure_helper.end_position_);
   function.set_is_synthetic(procedure_helper.IsNoSuchMethodForwarder() ||
-                            procedure_helper.IsMemberSignature());
+                            procedure_helper.IsMemberSignature() ||
+                            is_synthetic);
   function.set_is_visible(!is_invisible_function);
   if (register_function) {
     functions_.Add(&function);
diff --git a/runtime/vm/source_report_test.cc b/runtime/vm/source_report_test.cc
index f749b28..f445ff1 100644
--- a/runtime/vm/source_report_test.cc
+++ b/runtime/vm/source_report_test.cc
@@ -1073,6 +1073,56 @@
       buffer);
 }
 
+ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_IssueCov386_EnhancedEnums) {
+  // https://github.com/dart-lang/coverage/issues/386
+  // WARNING: This MUST be big enough for the serialised JSON string.
+  const int kBufferSize = 1024;
+  char buffer[kBufferSize];
+  const char* kScript =
+      "enum FoodType {\n"
+      "  candy();\n"
+      "  const FoodType();\n"
+      "}\n"
+      "void main() {\n"
+      "  final food = FoodType.candy;\n"
+      "}\n";
+
+  Library& lib = Library::Handle();
+  lib ^= ExecuteScript(kScript);
+  ASSERT(!lib.IsNull());
+  const Script& script =
+      Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));
+
+  SourceReport report(SourceReport::kCoverage, SourceReport::kForceCompile);
+  JSONStream js;
+  report.PrintJSON(&js, script);
+  const char* json_str = js.ToCString();
+  ASSERT(strlen(json_str) < kBufferSize);
+  ElideJSONSubstring("classes", json_str, buffer);
+  ElideJSONSubstring("libraries", buffer, buffer);
+  EXPECT_STREQ(
+      "{\"type\":\"SourceReport\",\"ranges\":["
+
+      // There are two ranges at the FoodType constructor. This one is missed,
+      // but the one below is hit, so it's ok. The point is that the synthetic
+      // toString method doesn't appear in this hitmap.
+      "{\"scriptIndex\":0,\"startPos\":29,\"endPos\":45,\"compiled\":true,"
+      "\"coverage\":{\"hits\":[],\"misses\":[29]}},"
+
+      // Main is hit.
+      "{\"scriptIndex\":0,\"startPos\":49,\"endPos\":94,\"compiled\":true,"
+      "\"coverage\":{\"hits\":[49],\"misses\":[]}},"
+
+      // FoodType constructor is hit.
+      "{\"scriptIndex\":0,\"compiled\":true,\"startPos\":29,\"endPos\":45,"
+      "\"coverage\":{\"hits\":[29],\"misses\":[]}}],"
+
+      // Only one script in the script table.
+      "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\","
+      "\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
+      buffer);
+}
+
 ISOLATE_UNIT_TEST_CASE(SourceReport_Regress95008_RedirectingFactory) {
   // WARNING: This MUST be big enough for the serialised JSON string.
   const int kBufferSize = 1024;