Introduce dartanalyze into our build.

This creates a separate toplevel build group ("dartcheck"), and when invoked,
it will run dartanalyze on each source file in a dart_packaged_application
template in the gn files. Currently, we disable hints, warnings, and a
few specific hard errors so we can make progress here.

Long term I'd like to have this as part of the normal build, but currently
it takes roughly three and a half seconds to check each dart file for
errors.

BUG=459376
R=zra@google.com

Review URL: https://codereview.chromium.org/953953003
diff --git a/BUILD.gn b/BUILD.gn
index d23d33e..945e123 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -24,6 +24,24 @@
   }
 }
 
+# A group which invokes the dart checker on each dart package. This can take
+# multiple seconds per target, so we separate it out from the main building.
+# Eventually, we'd like this to be part of the default dart building, but for
+# now, to participate in checking, please add "{package_name}_analyze" to the
+# list of dependencies below.
+group("dartcheck") {
+  testonly = true
+  deps = [
+    "//examples/dart/console_example:console_example_analyze",
+    "//examples/dart/hello_world/hello:hello_analyze",
+    "//examples/dart/hello_world/world:world_analyze",
+    "//examples/dart/wget:wget_analyze",
+    "//services/dart/test/echo:echo_analyze",
+    "//services/dart/test/pingpong:pingpong_analyze",
+    "//services/dart/test/pingpong_target:pingpong_target_analyze",
+  ]
+}
+
 # Deprecated name for the default build target.
 group("root") {
   testonly = true
diff --git a/README.md b/README.md
index 744c165..7fcaffb 100644
--- a/README.md
+++ b/README.md
@@ -150,6 +150,14 @@
 If you see javac compile errors, make sure you have an up-to-date JDK:
 https://code.google.com/p/chromium/wiki/AndroidBuildInstructions#Install_Java_JDK
 
+## Dart Code
+
+Because the dart analyzer is a bit slow, we don't run it unless the user specifically asks for it. To run the dart analyzer against the list of dart targets in the toplevel BUILD.gn file, run:
+
+```
+$ mojo/tools/mojob.py dartcheck
+```
+
 ## Googlers
 
 If you're a Googler, you can use Goma, a distributed compiler service for open-source projects such as Chrome and Android. The instructions below assume that Goma is installed in the default location (~/goma).
diff --git a/mojo/public/dart/rules.gni b/mojo/public/dart/rules.gni
index 5845f2e..31cfd27 100644
--- a/mojo/public/dart/rules.gni
+++ b/mojo/public/dart/rules.gni
@@ -54,6 +54,7 @@
 # dart_package targets.
 template("dart_packaged_application") {
   package_name = "${target_name}_package"
+  package_analyze_sources = "${target_name}_analyze"
   package_output = "$target_out_dir/$package_name.dartzip"
 
   if (defined(invoker.output_name)) {
@@ -62,6 +63,39 @@
     mojo_output = "$root_out_dir/" + target_name + ".mojo"
   }
 
+  action_foreach(package_analyze_sources) {
+    sources = invoker.sources
+
+    script = rebase_path("mojo/public/tools/dart_analyze.py", ".", mojo_root)
+
+    args = [
+      rebase_path(root_gen_dir),
+      rebase_path("$target_gen_dir/{{source_name_part}}.stamp"),
+      "{{source}}",
+      "--no-hints",
+      "--url-mapping=mojo:application,/" +
+          rebase_path("mojo/public/dart/application.dart", "/", mojo_root),
+      "--url-mapping=mojo:bindings,/" +
+          rebase_path("mojo/public/dart/bindings.dart", "/", mojo_root),
+      "--url-mapping=mojo:builtin,/" +
+          rebase_path("mojo/dart/embedder/builtin.dart", "/", mojo_root),
+      "--url-mapping=mojo:core,/" +
+          rebase_path("mojo/public/dart/core.dart", "/", mojo_root),
+    ]
+
+    if (defined(invoker.deps)) {
+      deps = invoker.deps
+    }
+
+    if (defined(invoker.datadeps)) {
+      datadeps = invoker.datadeps
+    }
+
+    outputs = [
+      "$target_gen_dir/{{source_name_part}}.stamp",
+    ]
+  }
+
   dart_package(package_name) {
     sources = invoker.sources
     if (defined(invoker.deps)) {
@@ -89,6 +123,9 @@
     ]
 
     deps = [
+      # TODO(erg): When dartanalyze runs at an acceptable speed, add
+      # ":$package_analyze_sources" as a dependency here and remove the
+      # manual group("check") in the toplevel build file.
       ":$package_name",
     ]
     if (defined(invoker.deps)) {
diff --git a/mojo/public/dart/src/codec.dart b/mojo/public/dart/src/codec.dart
index 677dc10..b32cc81 100644
--- a/mojo/public/dart/src/codec.dart
+++ b/mojo/public/dart/src/codec.dart
@@ -132,7 +132,7 @@
 
   void encodeUint8(int value, int offset) {
     if (value < 0) {
-      throw new MojoCodecError('$kErrorUnsigned: $val');
+      throw new MojoCodecError('$kErrorUnsigned: $value');
     }
     _buffer.buffer.setUint8(_base + offset, value);
   }
@@ -142,7 +142,7 @@
 
   void encodeUint16(int value, int offset) {
     if (value < 0) {
-      throw new MojoCodecError('$kErrorUnsigned: $val');
+      throw new MojoCodecError('$kErrorUnsigned: $value');
     }
     _buffer.buffer.setUint16(_base + offset, value, Endianness.LITTLE_ENDIAN);
   }
@@ -152,7 +152,7 @@
 
   void encodeUint32(int value, int offset) {
     if (value < 0) {
-      throw new MojoCodecError('$kErrorUnsigned: $val');
+      throw new MojoCodecError('$kErrorUnsigned: $value');
     }
     _buffer.buffer.setUint32(_base + offset, value, Endianness.LITTLE_ENDIAN);
   }
@@ -162,7 +162,7 @@
 
   void encodeUint64(int value, int offset) {
     if (value < 0) {
-      throw new MojoCodecError('$kErrorUnsigned: $val');
+      throw new MojoCodecError('$kErrorUnsigned: $value');
     }
     _buffer.buffer.setUint64(_base + offset, value, Endianness.LITTLE_ENDIAN);
   }
@@ -483,10 +483,10 @@
   void appendUint64Array(List<int> value) =>
       appendBytes(new Uint8List.view(new Uint64List.fromList(value).buffer));
 
-  void appendFloatArray(List<int> value) =>
+  void appendFloatArray(List<double> value) =>
       appendBytes(new Uint8List.view(new Float32List.fromList(value).buffer));
 
-  void appendDoubleArray(List<int> value) =>
+  void appendDoubleArray(List<double> value) =>
       appendBytes(new Uint8List.view(new Float64List.fromList(value).buffer));
 
   Encoder encoderForMap(int offset) {
diff --git a/mojo/public/dart/src/data_pipe.dart b/mojo/public/dart/src/data_pipe.dart
index 2af863f..9739be5 100644
--- a/mojo/public/dart/src/data_pipe.dart
+++ b/mojo/public/dart/src/data_pipe.dart
@@ -107,7 +107,7 @@
   int read(ByteData data, [int numBytes = -1, int flags = 0]) {
     if (handle == null) {
       status = MojoResult.INVALID_ARGUMENT;
-      return status;
+      return 0;
     }
 
     int data_numBytes = (numBytes == -1) ? data.lengthInBytes : numBytes;
@@ -115,7 +115,7 @@
         handle.h, data, data_numBytes, flags);
     if (result == null) {
       status = MojoResult.INVALID_ARGUMENT;
-      return status;
+      return 0;
     }
     assert((result is List) && (result.length == 2));
     status = new MojoResult(result[0]);
diff --git a/mojo/public/tools/dart_analyze.py b/mojo/public/tools/dart_analyze.py
new file mode 100755
index 0000000..8b8117e
--- /dev/null
+++ b/mojo/public/tools/dart_analyze.py
@@ -0,0 +1,67 @@
+#!/usr/bin/python
+# Copyright 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# To integrate dartanalyze with out build system, we take an input file, run
+# the analyzer on it, and write a stamp file if it passed.
+#
+# The first argument to this script is a reference to this build's gen
+# directory, which we treat as the package root. The second is the stamp file
+# to touch if we succeed. The rest are passed to the analyzer verbatim.
+
+import os
+import subprocess
+import sys
+import re
+
+_ANALYZING_PATTERN = re.compile(r'^Analyzing \[')
+_FINAL_REPORT_PATTERN = re.compile(r'^[0-9]+ errors and [0-9]+ warnings found.')
+
+_NATIVE_ERROR_PATTERN = re.compile(
+  r'^\[error\] Native functions can only be declared in the SDK and code that '
+  r'is loaded through native extensions')
+_WARNING_PATTERN = re.compile(r'^\[warning\]')
+_THAT_ONE_BROKEN_CLOSE_IN_WEB_SOCKETS_PATTERN = re.compile(
+  r'^\[error\] The name \'close\' is already defined')
+
+def main(args):
+  cmd = [
+    "../../third_party/dart-sdk/dart-sdk/bin/dartanalyzer",
+  ]
+
+  gen_dir = args.pop(0)
+  stamp_file = args.pop(0)
+  cmd.extend(args)
+  cmd.append("--package-root=%s" % gen_dir)
+
+  passed = True
+  try:
+    subprocess.check_output(cmd, shell=False, stderr=subprocess.STDOUT)
+  except subprocess.CalledProcessError as e:
+    # Perform post processing on the output. Filter out non-error messages and
+    # known problem patterns that we're working on.
+    raw_lines = e.output.split('\n')
+    # Remove the last empty line
+    raw_lines.pop()
+    filtered_lines = [i for i in raw_lines if (
+      not re.match(_ANALYZING_PATTERN, i) and
+      not re.match(_FINAL_REPORT_PATTERN, i) and
+      # TODO(erg): Remove the rest of these as fixes land:
+      not re.match(_WARNING_PATTERN, i) and
+      not re.match(_NATIVE_ERROR_PATTERN, i) and
+      not re.match(_THAT_ONE_BROKEN_CLOSE_IN_WEB_SOCKETS_PATTERN, i))]
+    for line in filtered_lines:
+      passed = False
+      print >> sys.stderr, line
+
+  if passed:
+    # We passed cleanly, so touch the stamp file so that we don't run again.
+    with open(stamp_file, 'a'):
+      os.utime(stamp_file, None)
+    return 0
+  else:
+    return -2
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv[1:]))
diff --git a/mojo/tools/mojob.py b/mojo/tools/mojob.py
index fb9e144..7f60900 100755
--- a/mojo/tools/mojob.py
+++ b/mojo/tools/mojob.py
@@ -112,6 +112,14 @@
     return subprocess.call(['ninja', '-C', out_dir])
 
 
+def dartcheck(config):
+  """Runs the dart analyzer on code for the given config."""
+
+  out_dir = _get_out_dir(config)
+  print 'Checking dart code in %s ...' % out_dir
+  return subprocess.call(['ninja', '-C', out_dir, 'dartcheck'])
+
+
 def _run_tests(config, test_types):
   """Runs the tests of the given type(s) for the given config."""
 
@@ -244,6 +252,10 @@
       help='Run NaCl unit tests (does not build).')
   nacltest_parser.set_defaults(func=nacltest)
 
+  dartcheck_parser = subparsers.add_parser('dartcheck', parents=[parent_parser],
+      help='Run the dart source code analyzer to check for warnings.')
+  dartcheck_parser.set_defaults(func=dartcheck)
+
   args = parser.parse_args()
   config = _args_to_config(args)
   return args.func(config)