Version 2.14.0-109.0.dev

Merge commit 'a22d5b98fde7e8cf548982a9d683e0ebf350d520' into 'dev'
diff --git a/runtime/vm/heap/pages.h b/runtime/vm/heap/pages.h
index 1add15e..7eef9f8 100644
--- a/runtime/vm/heap/pages.h
+++ b/runtime/vm/heap/pages.h
@@ -145,6 +145,17 @@
     ASSERT((index >= 0) && (index < card_table_size()));
     card_table_[index] = 1;
   }
+  bool IsCardRemembered(ObjectPtr const* slot) {
+    ASSERT(Contains(reinterpret_cast<uword>(slot)));
+    if (card_table_ == NULL) {
+      return false;
+    }
+    intptr_t offset =
+        reinterpret_cast<uword>(slot) - reinterpret_cast<uword>(this);
+    intptr_t index = offset >> kBytesPerCardLog2;
+    ASSERT((index >= 0) && (index < card_table_size()));
+    return card_table_[index] != 0;
+  }
 #if defined(DART_COMPRESSED_POINTERS)
   void RememberCard(CompressedObjectPtr const* slot) {
     ASSERT(Contains(reinterpret_cast<uword>(slot)));
@@ -158,6 +169,17 @@
     ASSERT((index >= 0) && (index < card_table_size()));
     card_table_[index] = 1;
   }
+  bool IsCardRemembered(CompressedObjectPtr const* slot) {
+    ASSERT(Contains(reinterpret_cast<uword>(slot)));
+    if (card_table_ == NULL) {
+      return false;
+    }
+    intptr_t offset =
+        reinterpret_cast<uword>(slot) - reinterpret_cast<uword>(this);
+    intptr_t index = offset >> kBytesPerCardLog2;
+    ASSERT((index >= 0) && (index < card_table_size()));
+    return card_table_[index] != 0;
+  }
 #endif
   void VisitRememberedCards(ObjectPointerVisitor* visitor);
 
diff --git a/runtime/vm/heap/scavenger.cc b/runtime/vm/heap/scavenger.cc
index ebdc4c0..94e834f 100644
--- a/runtime/vm/heap/scavenger.cc
+++ b/runtime/vm/heap/scavenger.cc
@@ -9,6 +9,7 @@
 #include "vm/dart_api_state.h"
 #include "vm/flag_list.h"
 #include "vm/heap/become.h"
+#include "vm/heap/pages.h"
 #include "vm/heap/pointer_block.h"
 #include "vm/heap/safepoint.h"
 #include "vm/heap/verifier.h"
@@ -867,17 +868,15 @@
     if (raw_obj->IsPseudoObject()) return;
     RELEASE_ASSERT(raw_obj->IsOldObject());
 
-    if (raw_obj->untag()->IsCardRemembered()) {
-      RELEASE_ASSERT(!raw_obj->untag()->IsRemembered());
-      // TODO(rmacnak): Verify card tables.
-      return;
-    }
-
     RELEASE_ASSERT(raw_obj->untag()->IsRemembered() ==
                    in_store_buffer_->Contains(raw_obj));
 
     visiting_ = raw_obj;
     is_remembered_ = raw_obj->untag()->IsRemembered();
+    is_card_remembered_ = raw_obj->untag()->IsCardRemembered();
+    if (is_card_remembered_) {
+      RELEASE_ASSERT(!is_remembered_);
+    }
     raw_obj->untag()->VisitPointers(this);
   }
 
@@ -885,12 +884,24 @@
     for (ObjectPtr* ptr = from; ptr <= to; ptr++) {
       ObjectPtr raw_obj = *ptr;
       if (raw_obj->IsHeapObject() && raw_obj->IsNewObject()) {
-        if (!is_remembered_) {
+        if (is_card_remembered_) {
+          if (!OldPage::Of(visiting_)->IsCardRemembered(ptr)) {
+            FATAL3(
+                "Old object %#" Px " references new object %#" Px
+                ", but the "
+                "slot's card is not remembered. Consider using rr to watch the "
+                "slot %p and reverse-continue to find the store with a missing "
+                "barrier.\n",
+                static_cast<uword>(visiting_), static_cast<uword>(raw_obj),
+                ptr);
+          }
+        } else if (!is_remembered_) {
           FATAL3(
               "Old object %#" Px " references new object %#" Px
-              ", but it is not"
-              " in any store buffer. Consider using rr to watch the slot %p and"
-              " reverse-continue to find the store with a missing barrier.\n",
+              ", but it is "
+              "not in any store buffer. Consider using rr to watch the "
+              "slot %p and reverse-continue to find the store with a missing "
+              "barrier.\n",
               static_cast<uword>(visiting_), static_cast<uword>(raw_obj), ptr);
         }
         RELEASE_ASSERT(to_->Contains(UntaggedObject::ToAddr(raw_obj)));
@@ -904,12 +915,24 @@
     for (CompressedObjectPtr* ptr = from; ptr <= to; ptr++) {
       ObjectPtr raw_obj = ptr->Decompress(heap_base);
       if (raw_obj->IsHeapObject() && raw_obj->IsNewObject()) {
-        if (!is_remembered_) {
+        if (is_card_remembered_) {
+          if (!OldPage::Of(visiting_)->IsCardRemembered(ptr)) {
+            FATAL3(
+                "Old object %#" Px " references new object %#" Px
+                ", but the "
+                "slot's card is not remembered. Consider using rr to watch the "
+                "slot %p and reverse-continue to find the store with a missing "
+                "barrier.\n",
+                static_cast<uword>(visiting_), static_cast<uword>(raw_obj),
+                ptr);
+          }
+        } else if (!is_remembered_) {
           FATAL3(
               "Old object %#" Px " references new object %#" Px
-              ", but it is not"
-              " in any store buffer. Consider using rr to watch the slot %p and"
-              " reverse-continue to find the store with a missing barrier.\n",
+              ", but it is "
+              "not in any store buffer. Consider using rr to watch the "
+              "slot %p and reverse-continue to find the store with a missing "
+              "barrier.\n",
               static_cast<uword>(visiting_), static_cast<uword>(raw_obj), ptr);
         }
         RELEASE_ASSERT(to_->Contains(UntaggedObject::ToAddr(raw_obj)));
@@ -922,6 +945,7 @@
   const SemiSpace* const to_;
   ObjectPtr visiting_;
   bool is_remembered_;
+  bool is_card_remembered_;
 };
 
 void Scavenger::VerifyStoreBuffers() {
diff --git a/tools/VERSION b/tools/VERSION
index acf070f..f71302f 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 14
 PATCH 0
-PRERELEASE 108
+PRERELEASE 109
 PRERELEASE_PATCH 0
\ No newline at end of file
diff --git a/tools/create_timestamp_file.py b/tools/create_timestamp_file.py
deleted file mode 100755
index bb48e7f..0000000
--- a/tools/create_timestamp_file.py
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
-# for details. All rights reserved. Use of this source code is governed by a
-# BSD-style license that can be found in the LICENSE file.
-
-import sys
-import os
-
-
-def main(args):
-    for file_name in args[1:]:
-        dir_name = os.path.dirname(file_name)
-        if not os.path.exists(dir_name):
-            os.mkdir(dir_name)
-        open(file_name, 'w').close()
-
-
-if __name__ == '__main__':
-    sys.exit(main(sys.argv))
diff --git a/tools/list_dart_files_as_depfile.py b/tools/list_dart_files_as_depfile.py
new file mode 100644
index 0000000..84eddee
--- /dev/null
+++ b/tools/list_dart_files_as_depfile.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python3
+# Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+"""Tool for listing Dart source files.
+
+If the first argument is 'relative', the script produces paths relative to the
+current working directory. If the first argument is 'absolute', the script
+produces absolute paths.
+
+Usage:
+  python3 tools/list_dart_files_as_depfile.py <depfile> <directory> <pattern>
+"""
+
+import os
+import re
+import sys
+
+
+def main(argv):
+    depfile = argv[1]
+    directory = argv[2]
+    if not os.path.isabs(directory):
+        directory = os.path.realpath(directory)
+
+    pattern = None
+    if len(argv) > 3:
+        pattern = re.compile(argv[3])
+
+    # Output a GN/Ninja depfile, whose format is a Makefile with one target.
+    out = open(depfile, 'w')
+    out.write(os.path.relpath(depfile))
+    out.write(":")
+
+    for root, directories, files in os.walk(directory):
+        # We only care about actual source files, not generated code or tests.
+        for skip_dir in ['.git', 'gen', 'test']:
+            if skip_dir in directories:
+                directories.remove(skip_dir)
+
+        # If we are looking at the root directory, filter the immediate
+        # subdirectories by the given pattern.
+        if pattern and root == directory:
+            directories[:] = filter(pattern.match, directories)
+
+        for filename in files:
+            if filename.endswith(
+                    '.dart') and not filename.endswith('_test.dart'):
+                fullname = os.path.join(directory, root, filename)
+                fullname = fullname.replace(os.sep, '/')
+                out.write(" \"")
+                out.write(fullname)
+                out.write("\"")
+
+    out.write("\n")
+    out.close()
+
+
+if __name__ == '__main__':
+    sys.exit(main(sys.argv))
diff --git a/utils/create_timestamp.gni b/utils/create_timestamp.gni
index 835700a..3b39299 100644
--- a/utils/create_timestamp.gni
+++ b/utils/create_timestamp.gni
@@ -10,16 +10,15 @@
   path = invoker.path
   output = invoker.output
   action(target_name) {
-    list_args = [ path ]
+    script = "$_dart_root/tools/list_dart_files_as_depfile.py"
+    args = [
+      rebase_path(output),
+      path,
+    ]
     if (defined(invoker.pattern)) {
-      list_args += [ invoker.pattern ]
+      args += [ invoker.pattern ]
     }
-    files = exec_script("$_dart_root/tools/list_dart_files.py",
-                        [ "absolute" ] + list_args,
-                        "list lines")
-    inputs = [ "$_dart_root/tools/list_dart_files.py" ] + files
+    depfile = output
     outputs = [ output ]
-    script = "$_dart_root/tools/create_timestamp_file.py"
-    args = [ rebase_path(output) ]
   }
 }