Merge "Remove WITH_SYNTAX_CHECK"
diff --git a/core/clang/tidy.mk b/core/clang/tidy.mk
index 7ec9378..c1d485d 100644
--- a/core/clang/tidy.mk
+++ b/core/clang/tidy.mk
@@ -78,6 +78,7 @@
 # Do not give warnings to external or vendor header files,
 # which contain too many warnings.
 DEFAULT_TIDY_HEADER_DIRS := \
+  $(subst $(space),, \
      art/ \
     |bionic/ \
     |bootable/ \
@@ -89,7 +90,8 @@
     |frameworks/ \
     |libcore/ \
     |libnativehelper/ \
-    |system/
+    |system/ \
+  )
 
 # Default filter contains current directory $1 and DEFAULT_TIDY_HEADER_DIRS.
 define default_tidy_header_filter
diff --git a/core/clear_vars.mk b/core/clear_vars.mk
index dfa497e..6614489 100644
--- a/core/clear_vars.mk
+++ b/core/clear_vars.mk
@@ -196,6 +196,7 @@
 LOCAL_DPI_FILE_STEM:=
 LOCAL_SANITIZE:=
 LOCAL_SANITIZE_RECOVER:=
+LOCAL_SANITIZE_DIAG:=
 LOCAL_NOSANITIZE:=
 LOCAL_DATA_BINDING:=
 LOCAL_DBUS_PROXY_PREFIX:=
diff --git a/core/config_sanitizers.mk b/core/config_sanitizers.mk
index ac3e4fc..2e14fef 100644
--- a/core/config_sanitizers.mk
+++ b/core/config_sanitizers.mk
@@ -133,6 +133,11 @@
   endif
 endif
 
+ifneq ($(filter cfi,$(my_sanitize)),)
+  my_cflags += -flto -fsanitize-cfi-cross-dso -fvisibility=default
+  my_ldflags += -flto -fsanitize-cfi-cross-dso -fsanitize=cfi -Wl,-plugin-opt,O1 -Wl,-export-dynamic-symbol=__cfi_check
+endif
+
 # If local or global modules need ASAN, add linker flags.
 ifneq ($(filter address,$(my_global_sanitize) $(my_sanitize)),)
   my_ldflags += $(ADDRESS_SANITIZER_CONFIG_EXTRA_LDFLAGS)
@@ -187,3 +192,13 @@
   recover_arg := $(subst $(space),$(comma),$(LOCAL_SANITIZE_RECOVER)),
   my_cflags += -fsanitize-recover=$(recover_arg)
 endif
+
+ifneq ($(strip $(LOCAL_SANITIZE_DIAG)),)
+  notrap_arg := $(subst $(space),$(comma),$(LOCAL_SANITIZE_DIAG)),
+  my_cflags += -fno-sanitize-trap=$(notrap_arg)
+  # Diagnostic requires a runtime library, unless ASan or TSan are also enabled.
+  ifeq ($(filter address thread,$(my_sanitize)),)
+    # Does not have to be the first DT_NEEDED unlike ASan.
+    my_shared_libraries += $($(LOCAL_2ND_ARCH_VAR_PREFIX)UBSAN_RUNTIME_LIBRARY)
+  endif
+endif
diff --git a/target/product/embedded.mk b/target/product/embedded.mk
index 55de3b9..5faf24f 100644
--- a/target/product/embedded.mk
+++ b/target/product/embedded.mk
@@ -32,6 +32,7 @@
     grep \
     gzip \
     healthd \
+    hwservicemanager \
     init \
     init.environ.rc \
     init.rc \
diff --git a/tools/warn.py b/tools/warn.py
index d8dbd1c..7d9ba65 100755
--- a/tools/warn.py
+++ b/tools/warn.py
@@ -1,17 +1,95 @@
-#!/usr/bin/env python
+#!/usr/bin/python
 # This file uses the following encoding: utf-8
 
+"""Grep warnings messages and output HTML tables or warning counts in CSV.
+
+Default is to output warnings in HTML tables grouped by warning severity.
+Use option --byproject to output tables grouped by source file projects.
+Use option --gencsv to output warning counts in CSV format.
+"""
+
+# List of important data structures and functions in this script.
+#
+# To parse and keep warning message in the input file:
+#   severity:                classification of message severity
+#   severity.range           [0, 1, ... last_severity_level]
+#   severity.colors          for header background
+#   severity.column_headers  for the warning count table
+#   severity.headers         for warning message tables
+#   warn_patterns:
+#   warn_patterns[w]['category']     tool that issued the warning, not used now
+#   warn_patterns[w]['description']  table heading
+#   warn_patterns[w]['members']      matched warnings from input
+#   warn_patterns[w]['option']       compiler flag to control the warning
+#   warn_patterns[w]['patterns']     regular expressions to match warnings
+#   warn_patterns[w]['projects'][p]  number of warnings of pattern w in p
+#   warn_patterns[w]['severity']     severity level
+#   project_list[p][0]               project name
+#   project_list[p][1]               regular expression to match a project path
+#   project_patterns[p]              re.compile(project_list[p][1])
+#   project_names[p]                 project_list[p][0]
+#   warning_messages     array of each warning message, without source url
+#   warning_records      array of [idx to warn_patterns,
+#                                  idx to project_names,
+#                                  idx to warning_messages]
+#   platform_version
+#   target_product
+#   target_variant
+#   compile_patterns, parse_input_file
+#
+# To emit html page of warning messages:
+#   flags: --byproject, --url, --separator
+# Old stuff for static html components:
+#   html_script_style:  static html scripts and styles
+#   htmlbig:
+#   dump_stats, dump_html_prologue, dump_html_epilogue:
+#   emit_buttons:
+#   dump_fixed
+#   sort_warnings:
+#   emit_stats_by_project:
+#   all_patterns,
+#   findproject, classify_warning
+#   dump_html
+#
+# New dynamic HTML page's static JavaScript data:
+#   Some data are copied from Python to JavaScript, to generate HTML elements.
+#   FlagURL                args.url
+#   FlagSeparator          args.separator
+#   SeverityColors:        severity.colors
+#   SeverityHeaders:       severity.headers
+#   SeverityColumnHeaders: severity.column_headers
+#   ProjectNames:          project_names, or project_list[*][0]
+#   WarnPatternsSeverity:     warn_patterns[*]['severity']
+#   WarnPatternsDescription:  warn_patterns[*]['description']
+#   WarnPatternsOption:       warn_patterns[*]['option']
+#   WarningMessages:          warning_messages
+#   Warnings:                 warning_records
+#   StatsHeader:           warning count table header row
+#   StatsRows:             array of warning count table rows
+#
+# New dynamic HTML page's dynamic JavaScript data:
+#
+# New dynamic HTML related function to emit data:
+#   escape_string, strip_escape_string, emit_warning_arrays
+#   emit_js_data():
+#
+# To emit csv files of warning message counts:
+#   flag --gencsv
+#   description_for_csv, string_for_csv:
+#   count_severity(sev, kind):
+#   dump_csv():
+
 import argparse
 import re
 
 parser = argparse.ArgumentParser(description='Convert a build log into HTML')
 parser.add_argument('--gencsv',
                     help='Generate a CSV file with number of various warnings',
-                    action="store_true",
+                    action='store_true',
                     default=False)
 parser.add_argument('--byproject',
                     help='Separate warnings in HTML output by project names',
-                    action="store_true",
+                    action='store_true',
                     default=False)
 parser.add_argument('--url',
                     help='Root URL of an Android source code tree prefixed '
@@ -23,33 +101,38 @@
                     help='Path to build.log file')
 args = parser.parse_args()
 
-# if you add another level, don't forget to give it a color below
-class severity:
-    UNKNOWN = 0
-    FIXMENOW = 1
-    HIGH = 2
-    MEDIUM = 3
-    LOW = 4
-    TIDY = 5
-    HARMLESS = 6
-    SKIP = 7
-    attributes = [
-        ['lightblue', 'Unknown',   'Unknown warnings'],
-        ['fuchsia',   'FixNow',    'Critical warnings, fix me now'],
-        ['red',       'High',      'High severity warnings'],
-        ['orange',    'Medium',    'Medium severity warnings'],
-        ['yellow',    'Low',       'Low severity warnings'],
-        ['peachpuff', 'Tidy',      'Clang-Tidy warnings'],
-        ['limegreen', 'Harmless',  'Harmless warnings'],
-        ['grey',      'Unhandled', 'Unhandled warnings']
-    ]
-    color = [a[0] for a in attributes]
-    columnheader = [a[1] for a in attributes]
-    header = [a[2] for a in attributes]
-    # order to dump by severity
-    kinds = [FIXMENOW, HIGH, MEDIUM, LOW, TIDY, HARMLESS, UNKNOWN, SKIP]
 
-warnpatterns = [
+class severity:  # pylint:disable=invalid-name,old-style-class
+  """Severity levels and attributes."""
+  # numbered by dump order
+  FIXMENOW = 0
+  HIGH = 1
+  MEDIUM = 2
+  LOW = 3
+  TIDY = 4
+  HARMLESS = 5
+  UNKNOWN = 6
+  SKIP = 7
+  range = range(8)
+  attributes = [
+      # pylint:disable=bad-whitespace
+      ['fuchsia',   'FixNow',    'Critical warnings, fix me now'],
+      ['red',       'High',      'High severity warnings'],
+      ['orange',    'Medium',    'Medium severity warnings'],
+      ['yellow',    'Low',       'Low severity warnings'],
+      ['peachpuff', 'Tidy',      'Clang-Tidy warnings'],
+      ['limegreen', 'Harmless',  'Harmless warnings'],
+      ['lightblue', 'Unknown',   'Unknown warnings'],
+      ['grey',      'Unhandled', 'Unhandled warnings']
+  ]
+  colors = [a[0] for a in attributes]
+  column_headers = [a[1] for a in attributes]
+  headers = [a[2] for a in attributes]
+
+warn_patterns = [
+    # TODO(chh): fix pylint space and indentation warnings
+    # pylint:disable=bad-whitespace,bad-continuation,
+    # pylint:disable=line-too-long,g-inconsistent-quotes
     { 'category':'make',    'severity':severity.MEDIUM,
         'description':'make: overriding commands/ignoring old commands',
         'patterns':[r".*: warning: overriding commands for target .+",
@@ -71,7 +154,7 @@
         'patterns':[r".*: warning: implicit declaration of function .+",
                     r".*: warning: implicitly declaring library function" ] },
     { 'category':'C/C++',   'severity':severity.SKIP,
-        'description':'',
+        'description':'skip, conflicting types for ...',
         'patterns':[r".*: warning: conflicting types for '.+'"] },
     { 'category':'C/C++',   'severity':severity.HIGH, 'option':'-Wtype-limits',
         'description':'Expression always evaluates to true or false',
@@ -146,7 +229,7 @@
         'description':'Need virtual destructor',
         'patterns':[r".*: warning: delete called .* has virtual functions but non-virtual destructor"] },
     { 'category':'cont.',   'severity':severity.SKIP,
-        'description':'',
+        'description':'skip, near initialization for ...',
         'patterns':[r".*: warning: \(near initialization for '.+'\)"] },
     { 'category':'C/C++',   'severity':severity.MEDIUM, 'option':'-Wdate-time',
         'description':'Expansion of data or time macro',
@@ -286,7 +369,7 @@
                     r".*: warning: Access to .+ results in a dereference of a null pointer",
                     r".*: warning: Null pointer argument in"] },
     { 'category':'cont.',   'severity':severity.SKIP,
-        'description':'',
+        'description':'skip, parameter name (without types) in function declaration',
         'patterns':[r".*: warning: parameter names \(without types\) in function declaration"] },
     { 'category':'C/C++',   'severity':severity.MEDIUM, 'option':'-Wstrict-aliasing',
         'description':'Dereferencing <foo> breaks strict aliasing rules',
@@ -302,7 +385,7 @@
         'description':'Symbol redefined',
         'patterns':[r".*: warning: "".+"" redefined"] },
     { 'category':'cont.',   'severity':severity.SKIP,
-        'description':'',
+        'description':'skip, ... location of the previous definition',
         'patterns':[r".*: warning: this is the location of the previous definition"] },
     { 'category':'ld',      'severity':severity.MEDIUM,
         'description':'ld: type and size of dynamic symbol are not defined',
@@ -338,7 +421,7 @@
         'description':'Redundant declaration',
         'patterns':[r".*: warning: redundant redeclaration of '.+'"] },
     { 'category':'cont.',   'severity':severity.SKIP,
-        'description':'',
+        'description':'skip, previous declaration ... was here',
         'patterns':[r".*: warning: previous declaration of '.+' was here"] },
     { 'category':'C/C++',   'severity':severity.MEDIUM, 'option':'-Wswitch-enum',
         'description':'Enum value not handled in switch',
@@ -1141,13 +1224,13 @@
         'patterns':[r".*: warning: '.+' will be initialized after",
                     r".*: warning: field .+ will be initialized after .+Wreorder"] },
     { 'category':'cont.',   'severity':severity.SKIP,
-        'description':'',
+        'description':'skip,   ....',
         'patterns':[r".*: warning:   '.+'"] },
     { 'category':'cont.',   'severity':severity.SKIP,
-        'description':'',
+        'description':'skip,   base ...',
         'patterns':[r".*: warning:   base '.+'"] },
     { 'category':'cont.',   'severity':severity.SKIP,
-        'description':'',
+        'description':'skip,   when initialized here',
         'patterns':[r".*: warning:   when initialized here"] },
     { 'category':'C/C++',   'severity':severity.MEDIUM, 'option':'-Wmissing-parameter-type',
         'description':'Parameter type not specified',
@@ -1158,6 +1241,8 @@
     { 'category':'C/C++',   'severity':severity.MEDIUM, 'option':'-Wmissing-noreturn',
         'description':'Missing noreturn',
         'patterns':[r".*: warning: function '.*' could be declared with attribute 'noreturn'"] },
+    # pylint:disable=anomalous-backslash-in-string
+    # TODO(chh): fix the backslash pylint warning.
     { 'category':'gcc',     'severity':severity.MEDIUM,
         'description':'Invalid option for C file',
         'patterns':[r".*: warning: command line option "".+"" is valid for C\+\+\/ObjC\+\+ but not for C"] },
@@ -1191,7 +1276,7 @@
         'description':'<foo> declared inside parameter list, scope limited to this definition',
         'patterns':[r".*: warning: '.+' declared inside parameter list"] },
     { 'category':'cont.',   'severity':severity.SKIP,
-        'description':'',
+        'description':'skip, its scope is only this ...',
         'patterns':[r".*: warning: its scope is only this definition or declaration, which is probably not what you want"] },
     { 'category':'C/C++',   'severity':severity.LOW, 'option':'-Wcomment',
         'description':'Line continuation inside comment',
@@ -1262,7 +1347,7 @@
         'description':'Overload resolution chose to promote from unsigned or enum to signed type' ,
         'patterns':[r".*: warning: passing '.+' chooses '.+' over '.+'.*Wsign-promo"] },
     { 'category':'cont.',   'severity':severity.SKIP,
-        'description':'',
+        'description':'skip,   in call to ...',
         'patterns':[r".*: warning:   in call to '.+'"] },
     { 'category':'C/C++',   'severity':severity.HIGH, 'option':'-Wextra',
         'description':'Base should be explicitly initialized in copy constructor',
@@ -1454,13 +1539,13 @@
 
     # these next ones are to deal with formatting problems resulting from the log being mixed up by 'make -j'
     { 'category':'C/C++',   'severity':severity.SKIP,
-        'description':'',
+        'description':'skip, ,',
         'patterns':[r".*: warning: ,$"] },
     { 'category':'C/C++',   'severity':severity.SKIP,
-        'description':'',
+        'description':'skip,',
         'patterns':[r".*: warning: $"] },
     { 'category':'C/C++',   'severity':severity.SKIP,
-        'description':'',
+        'description':'skip, In file included from ...',
         'patterns':[r".*: warning: In file included from .+,"] },
 
     # warnings from clang-tidy
@@ -1577,14 +1662,11 @@
         'patterns':[r".*: warning: .+"] },
 ]
 
-for w in warnpatterns:
-    w['members'] = []
-    if 'option' not in w:
-        w['option'] = ''
 
 # A list of [project_name, file_path_pattern].
 # project_name should not contain comma, to be used in CSV output.
-projectlist = [
+project_list = [
+    # pylint:disable=bad-whitespace,g-inconsistent-quotes,line-too-long
     ['art',                 r"(^|.*/)art/.*: warning:"],
     ['bionic',              r"(^|.*/)bionic/.*: warning:"],
     ['bootable',            r"(^|.*/)bootable/.*: warning:"],
@@ -1598,16 +1680,56 @@
     # match external/google* before external/
     ['external/google',     r"(^|.*/)external/google.*: warning:"],
     ['external/non-google', r"(^|.*/)external/.*: warning:"],
-    ['frameworks',          r"(^|.*/)frameworks/.*: warning:"],
-    ['hardware',            r"(^|.*/)hardware/.*: warning:"],
+    ['frameworks/av/camera',r"(^|.*/)frameworks/av/camera/.*: warning:"],
+    ['frameworks/av/cmds',  r"(^|.*/)frameworks/av/cmds/.*: warning:"],
+    ['frameworks/av/drm',   r"(^|.*/)frameworks/av/drm/.*: warning:"],
+    ['frameworks/av/include',r"(^|.*/)frameworks/av/include/.*: warning:"],
+    ['frameworks/av/media', r"(^|.*/)frameworks/av/media/.*: warning:"],
+    ['frameworks/av/radio', r"(^|.*/)frameworks/av/radio/.*: warning:"],
+    ['frameworks/av/services', r"(^|.*/)frameworks/av/services/.*: warning:"],
+    ['frameworks/av/Other', r"(^|.*/)frameworks/av/.*: warning:"],
+    ['frameworks/base',     r"(^|.*/)frameworks/base/.*: warning:"],
+    ['frameworks/compile',  r"(^|.*/)frameworks/compile/.*: warning:"],
+    ['frameworks/minikin',  r"(^|.*/)frameworks/minikin/.*: warning:"],
+    ['frameworks/native',   r"(^|.*/)frameworks/native/.*: warning:"],
+    ['frameworks/opt',      r"(^|.*/)frameworks/opt/.*: warning:"],
+    ['frameworks/rs',       r"(^|.*/)frameworks/rs/.*: warning:"],
+    ['frameworks/webview',  r"(^|.*/)frameworks/webview/.*: warning:"],
+    ['frameworks/wilhelm',  r"(^|.*/)frameworks/wilhelm/.*: warning:"],
+    ['frameworks/Other',    r"(^|.*/)frameworks/.*: warning:"],
+    ['hardware/akm',        r"(^|.*/)hardware/akm/.*: warning:"],
+    ['hardware/broadcom',   r"(^|.*/)hardware/broadcom/.*: warning:"],
+    ['hardware/google',     r"(^|.*/)hardware/google/.*: warning:"],
+    ['hardware/intel',      r"(^|.*/)hardware/intel/.*: warning:"],
+    ['hardware/interfaces', r"(^|.*/)hardware/interfaces/.*: warning:"],
+    ['hardware/libhardware',r"(^|.*/)hardware/libhardware/.*: warning:"],
+    ['hardware/libhardware_legacy',r"(^|.*/)hardware/libhardware_legacy/.*: warning:"],
+    ['hardware/qcom',       r"(^|.*/)hardware/qcom/.*: warning:"],
+    ['hardware/ril',        r"(^|.*/)hardware/ril/.*: warning:"],
+    ['hardware/Other',      r"(^|.*/)hardware/.*: warning:"],
     ['kernel',              r"(^|.*/)kernel/.*: warning:"],
     ['libcore',             r"(^|.*/)libcore/.*: warning:"],
-    ['libnativehelper',      r"(^|.*/)libnativehelper/.*: warning:"],
+    ['libnativehelper',     r"(^|.*/)libnativehelper/.*: warning:"],
     ['ndk',                 r"(^|.*/)ndk/.*: warning:"],
+    # match vendor/unbungled_google/packages before other packages
+    ['unbundled_google',    r"(^|.*/)unbundled_google/.*: warning:"],
     ['packages',            r"(^|.*/)packages/.*: warning:"],
     ['pdk',                 r"(^|.*/)pdk/.*: warning:"],
     ['prebuilts',           r"(^|.*/)prebuilts/.*: warning:"],
-    ['system',              r"(^|.*/)system/.*: warning:"],
+    ['system/bt',           r"(^|.*/)system/bt/.*: warning:"],
+    ['system/connectivity', r"(^|.*/)system/connectivity/.*: warning:"],
+    ['system/core',         r"(^|.*/)system/core/.*: warning:"],
+    ['system/extras',       r"(^|.*/)system/extras/.*: warning:"],
+    ['system/gatekeeper',   r"(^|.*/)system/gatekeeper/.*: warning:"],
+    ['system/keymaster',    r"(^|.*/)system/keymaster/.*: warning:"],
+    ['system/libhwbinder',  r"(^|.*/)system/libhwbinder/.*: warning:"],
+    ['system/media',        r"(^|.*/)system/media/.*: warning:"],
+    ['system/netd',         r"(^|.*/)system/netd/.*: warning:"],
+    ['system/security',     r"(^|.*/)system/security/.*: warning:"],
+    ['system/sepolicy',     r"(^|.*/)system/sepolicy/.*: warning:"],
+    ['system/tools',        r"(^|.*/)system/tools/.*: warning:"],
+    ['system/vold',         r"(^|.*/)system/vold/.*: warning:"],
+    ['system/Other',        r"(^|.*/)system/.*: warning:"],
     ['toolchain',           r"(^|.*/)toolchain/.*: warning:"],
     ['test',                r"(^|.*/)test/.*: warning:"],
     ['tools',               r"(^|.*/)tools/.*: warning:"],
@@ -1615,428 +1737,608 @@
     ['vendor/google',       r"(^|.*/)vendor/google.*: warning:"],
     ['vendor/non-google',   r"(^|.*/)vendor/.*: warning:"],
     # keep out/obj and other patterns at the end.
-    ['out/obj', r".*/(gen|obj[^/]*)/(include|EXECUTABLES|SHARED_LIBRARIES|STATIC_LIBRARIES)/.*: warning:"],
+    ['out/obj', r".*/(gen|obj[^/]*)/(include|EXECUTABLES|SHARED_LIBRARIES|STATIC_LIBRARIES|NATIVE_TESTS)/.*: warning:"],
     ['other',   r".*: warning:"],
 ]
 
-projectpatterns = []
-for p in projectlist:
-    projectpatterns.append({'description':p[0], 'members':[], 'pattern':re.compile(p[1])})
+project_patterns = []
+project_names = []
+warning_messages = []
+warning_records = []
 
-projectnames = [p[0] for p in projectlist]
 
-# Each warning pattern has 3 dictionaries:
-# (1) 'projects' maps a project name to number of warnings in that project.
-# (2) 'projectanchor' maps a project name to its anchor number for HTML.
-# (3) 'projectwarning' maps a project name to a list of warning of that project.
-for w in warnpatterns:
+def initialize_arrays():
+  """Complete global arrays before they are used."""
+  global project_names, project_patterns
+  project_names = [p[0] for p in project_list]
+  project_patterns = [re.compile(p[1]) for p in project_list]
+  for w in warn_patterns:
+    w['members'] = []
+    if 'option' not in w:
+      w['option'] = ''
+    # Each warning pattern has a 'projects' dictionary, that
+    # maps a project name to number of warnings in that project.
     w['projects'] = {}
-    w['projectanchor'] = {}
-    w['projectwarning'] = {}
 
-platformversion = 'unknown'
-targetproduct = 'unknown'
-targetvariant = 'unknown'
+
+initialize_arrays()
+
+
+platform_version = 'unknown'
+target_product = 'unknown'
+target_variant = 'unknown'
 
 
 ##### Data and functions to dump html file. ##################################
 
-anchor = 0
-cur_row_class = 0
-
-html_script_style = """\
-    <script type="text/javascript">
-    function expand(id) {
-      var e = document.getElementById(id);
+html_head_scripts = """\
+  <script type="text/javascript">
+  function expand(id) {
+    var e = document.getElementById(id);
+    var f = document.getElementById(id + "_mark");
+    if (e.style.display == 'block') {
+       e.style.display = 'none';
+       f.innerHTML = '&#x2295';
+    }
+    else {
+       e.style.display = 'block';
+       f.innerHTML = '&#x2296';
+    }
+  };
+  function expandCollapse(show) {
+    for (var id = 1; ; id++) {
+      var e = document.getElementById(id + "");
       var f = document.getElementById(id + "_mark");
-      if (e.style.display == 'block') {
-         e.style.display = 'none';
-         f.innerHTML = '&#x2295';
-      }
-      else {
-         e.style.display = 'block';
-         f.innerHTML = '&#x2296';
-      }
-    };
-    function expand_collapse(show) {
-      for (var id = 1; ; id++) {
-        var e = document.getElementById(id + "");
-        var f = document.getElementById(id + "_mark");
-        if (!e || !f) break;
-        e.style.display = (show ? 'block' : 'none');
-        f.innerHTML = (show ? '&#x2296' : '&#x2295');
-      }
-    };
-    </script>
-    <style type="text/css">
-    th,td{border-collapse:collapse; border:1px solid black;}
-    .button{color:blue;font-size:110%;font-weight:bolder;}
-    .bt{color:black;background-color:transparent;border:none;outline:none;
-        font-size:140%;font-weight:bolder;}
-    .c0{background-color:#e0e0e0;}
-    .c1{background-color:#d0d0d0;}
-    .t1{border-collapse:collapse; width:100%; border:1px solid black;}
-    </style>\n"""
+      if (!e || !f) break;
+      e.style.display = (show ? 'block' : 'none');
+      f.innerHTML = (show ? '&#x2296' : '&#x2295');
+    }
+  };
+  </script>
+  <style type="text/css">
+  th,td{border-collapse:collapse; border:1px solid black;}
+  .button{color:blue;font-size:110%;font-weight:bolder;}
+  .bt{color:black;background-color:transparent;border:none;outline:none;
+      font-size:140%;font-weight:bolder;}
+  .c0{background-color:#e0e0e0;}
+  .c1{background-color:#d0d0d0;}
+  .t1{border-collapse:collapse; width:100%; border:1px solid black;}
+  </style>
+  <script src="https://www.gstatic.com/charts/loader.js"></script>
+"""
 
 
-def output(text):
-    print text,
+def html_big(param):
+  return '<font size="+2">' + param + '</font>'
 
-def htmlbig(param):
-    return '<font size="+2">' + param + '</font>'
 
-def dumphtmlprologue(title):
-    output('<html>\n<head>\n')
-    output('<title>' + title + '</title>\n')
-    output(html_script_style)
-    output('</head>\n<body>\n')
-    output(htmlbig(title))
-    output('<p>\n')
+def dump_html_prologue(title):
+  print '<html>\n<head>'
+  print '<title>' + title + '</title>'
+  print html_head_scripts
+  emit_stats_by_project()
+  print '</head>\n<body>'
+  print html_big(title)
+  print '<p>'
 
-def dumphtmlepilogue():
-    output('</body>\n</head>\n</html>\n')
 
-def tablerow(text):
-    global cur_row_class
+def dump_html_epilogue():
+  print '</body>\n</head>\n</html>'
+
+
+def sort_warnings():
+  for i in warn_patterns:
+    i['members'] = sorted(set(i['members']))
+
+
+def emit_stats_by_project():
+  """Dump a google chart table of warnings per project and severity."""
+  # warnings[p][s] is number of warnings in project p of severity s.
+  warnings = {p: {s: 0 for s in severity.range} for p in project_names}
+  for i in warn_patterns:
+    s = i['severity']
+    for p in i['projects']:
+      warnings[p][s] += i['projects'][p]
+
+  # total_by_project[p] is number of warnings in project p.
+  total_by_project = {p: sum(warnings[p][s] for s in severity.range)
+                      for p in project_names}
+
+  # total_by_severity[s] is number of warnings of severity s.
+  total_by_severity = {s: sum(warnings[p][s] for p in project_names)
+                       for s in severity.range}
+
+  # emit table header
+  stats_header = ['Project']
+  for s in severity.range:
+    if total_by_severity[s]:
+      stats_header.append("<span style='background-color:{}'>{}</span>".
+                          format(severity.colors[s],
+                                 severity.column_headers[s]))
+  stats_header.append('TOTAL')
+
+  # emit a row of warning counts per project, skip no-warning projects
+  total_all_projects = 0
+  stats_rows = []
+  for p in project_names:
+    if total_by_project[p]:
+      one_row = [p]
+      for s in severity.range:
+        if total_by_severity[s]:
+          one_row.append(warnings[p][s])
+      one_row.append(total_by_project[p])
+      stats_rows.append(one_row)
+      total_all_projects += total_by_project[p]
+
+  # emit a row of warning counts per severity
+  total_all_severities = 0
+  one_row = ['<b>TOTAL</b>']
+  for s in severity.range:
+    if total_by_severity[s]:
+      one_row.append(total_by_severity[s])
+      total_all_severities += total_by_severity[s]
+  one_row.append(total_all_projects)
+  stats_rows.append(one_row)
+  print '<script>'
+  emit_const_string_array('StatsHeader', stats_header)
+  emit_const_object_array('StatsRows', stats_rows)
+  print draw_table_javascript
+  print '</script>'
+
+
+def dump_stats():
+  """Dump some stats about total number of warnings and such."""
+  known = 0
+  skipped = 0
+  unknown = 0
+  sort_warnings()
+  for i in warn_patterns:
+    if i['severity'] == severity.UNKNOWN:
+      unknown += len(i['members'])
+    elif i['severity'] == severity.SKIP:
+      skipped += len(i['members'])
+    else:
+      known += len(i['members'])
+  print 'Number of classified warnings: <b>' + str(known) + '</b><br>'
+  print 'Number of skipped warnings: <b>' + str(skipped) + '</b><br>'
+  print 'Number of unclassified warnings: <b>' + str(unknown) + '</b><br>'
+  total = unknown + known + skipped
+  extra_msg = ''
+  if total < 1000:
+    extra_msg = ' (low count may indicate incremental build)'
+  print 'Total number of warnings: <b>' + str(total) + '</b>' + extra_msg
+
+
+# New base table of warnings, [severity, warn_id, project, warning_message]
+# Need buttons to show warnings in different grouping options.
+# (1) Current, group by severity, id for each warning pattern
+#     sort by severity, warn_id, warning_message
+# (2) Current --byproject, group by severity,
+#     id for each warning pattern + project name
+#     sort by severity, warn_id, project, warning_message
+# (3) New, group by project + severity,
+#     id for each warning pattern
+#     sort by project, severity, warn_id, warning_message
+def emit_buttons():
+  print ('<button class="button" onclick="expandCollapse(1);">'
+         'Expand all warnings</button>\n'
+         '<button class="button" onclick="expandCollapse(0);">'
+         'Collapse all warnings</button>\n'
+         '<button class="button" onclick="groupBySeverity();">'
+         'Group warnings by severity</button>\n'
+         '<button class="button" onclick="groupByProject();">'
+         'Group warnings by project</button><br>')
+
+
+def all_patterns(category):
+  patterns = ''
+  for i in category['patterns']:
+    patterns += i
+    patterns += ' / '
+  return patterns
+
+
+def dump_fixed():
+  """Show which warnings no longer occur."""
+  anchor = 'fixed_warnings'
+  mark = anchor + '_mark'
+  print ('\n<br><p style="background-color:lightblue"><b>'
+         '<button id="' + mark + '" '
+         'class="bt" onclick="expand(\'' + anchor + '\');">'
+         '&#x2295</button> Fixed warnings. '
+         'No more occurrences. Please consider turning these into '
+         'errors if possible, before they are reintroduced in to the build'
+         ':</b></p>')
+  print '<blockquote>'
+  fixed_patterns = []
+  for i in warn_patterns:
+    if not i['members']:
+      fixed_patterns.append(i['description'] + ' (' +
+                            all_patterns(i) + ')')
+    if i['option']:
+      fixed_patterns.append(' ' + i['option'])
+  fixed_patterns.sort()
+  print '<div id="' + anchor + '" style="display:none;"><table>'
+  cur_row_class = 0
+  for text in fixed_patterns:
     cur_row_class = 1 - cur_row_class
     # remove last '\n'
     t = text[:-1] if text[-1] == '\n' else text
-    output('<tr><td class="c' + str(cur_row_class) + '">' + t + '</td></tr>\n')
+    print '<tr><td class="c' + str(cur_row_class) + '">' + t + '</td></tr>'
+  print '</table></div>'
+  print '</blockquote>'
 
-def sortwarnings():
-    for i in warnpatterns:
-        i['members'] = sorted(set(i['members']))
 
-# dump a table of warnings per project and severity
-def dumpstatsbyproject():
-    projects = set(projectnames)
-    severities = set(severity.kinds)
+def find_project_index(line):
+  for p in range(len(project_patterns)):
+    if project_patterns[p].match(line):
+      return p
+  return -1
 
-    # warnings[p][s] is number of warnings in project p of severity s.
-    warnings = {p:{s:0 for s in severity.kinds} for p in projectnames}
-    for i in warnpatterns:
-        s = i['severity']
-        for p in i['projects']:
-            warnings[p][s] += i['projects'][p]
 
-    # totalbyproject[p] is number of warnings in project p.
-    totalbyproject = {p:sum(warnings[p][s] for s in severity.kinds)
-                      for p in projectnames}
-
-    # totalbyseverity[s] is number of warnings of severity s.
-    totalbyseverity = {s:sum(warnings[p][s] for p in projectnames)
-                       for s in severity.kinds}
-
-    # emit table header
-    output('<blockquote><table border=1>\n<tr><th>Project</th>\n')
-    for s in severity.kinds:
-        if totalbyseverity[s]:
-            output('<th width="8%"><span style="background-color:{}">{}</span></th>'.
-                   format(severity.color[s], severity.columnheader[s]))
-    output('<th>TOTAL</th></tr>\n')
-
-    # emit a row of warning counts per project, skip no-warning projects
-    totalallprojects = 0
-    for p in projectnames:
-        if totalbyproject[p]:
-            output('<tr><td align="left">{}</td>'.format(p))
-            for s in severity.kinds:
-                if totalbyseverity[s]:
-                    output('<td align="right">{}</td>'.format(warnings[p][s]))
-            output('<td align="right">{}</td>'.format(totalbyproject[p]))
-            totalallprojects += totalbyproject[p]
-            output('</tr>\n')
-
-    # emit a row of warning counts per severity
-    totalallseverities = 0
-    output('<tr><td align="right">TOTAL</td>')
-    for s in severity.kinds:
-        if totalbyseverity[s]:
-            output('<td align="right">{}</td>'.format(totalbyseverity[s]))
-            totalallseverities += totalbyseverity[s]
-    output('<td align="right">{}</td></tr>\n'.format(totalallprojects))
-
-    # at the end of table, verify total counts
-    output('</table></blockquote><br>\n')
-    if totalallprojects != totalallseverities:
-        output('<h3>ERROR: Sum of warnings by project ' +
-               '!= Sum of warnings by severity.</h3>\n')
-
-# dump some stats about total number of warnings and such
-def dumpstats():
-    known = 0
-    skipped = 0
-    unknown = 0
-    sortwarnings()
-    for i in warnpatterns:
-        if i['severity'] == severity.UNKNOWN:
-            unknown += len(i['members'])
-        elif i['severity'] == severity.SKIP:
-            skipped += len(i['members'])
+def classify_warning(line):
+  for i in range(len(warn_patterns)):
+    w = warn_patterns[i]
+    for cpat in w['compiled_patterns']:
+      if cpat.match(line):
+        w['members'].append(line)
+        p = find_project_index(line)
+        index = len(warning_messages)
+        warning_messages.append(line)
+        warning_records.append([i, p, index])
+        pname = '???' if p < 0 else project_names[p]
+        # Count warnings by project.
+        if pname in w['projects']:
+          w['projects'][pname] += 1
         else:
-            known += len(i['members'])
-    output('\nNumber of classified warnings: <b>' + str(known) + '</b><br>' )
-    output('\nNumber of skipped warnings: <b>' + str(skipped) + '</b><br>')
-    output('\nNumber of unclassified warnings: <b>' + str(unknown) + '</b><br>')
-    total = unknown + known + skipped
-    output('\nTotal number of warnings: <b>' + str(total) + '</b>')
-    if total < 1000:
-        output('(low count may indicate incremental build)')
-    output('<br><br>\n')
-
-def emitbuttons():
-    output('<button class="button" onclick="expand_collapse(1);">' +
-           'Expand all warnings</button>\n' +
-           '<button class="button" onclick="expand_collapse(0);">' +
-           'Collapse all warnings</button><br>\n')
-
-# dump everything for a given severity
-def dumpseverity(sev):
-    global anchor
-    total = 0
-    for i in warnpatterns:
-        if i['severity'] == sev:
-            total = total + len(i['members'])
-    output('\n<br><span style="background-color:' + severity.color[sev] + '"><b>' +
-           severity.header[sev] + ': ' + str(total) + '</b></span>\n')
-    output('<blockquote>\n')
-    for i in warnpatterns:
-        if i['severity'] == sev and len(i['members']) > 0:
-            anchor += 1
-            i['anchor'] = str(anchor)
-            if args.byproject:
-                dumpcategorybyproject(sev, i)
-            else:
-                dumpcategory(sev, i)
-    output('</blockquote>\n')
-
-# emit all skipped project anchors for expand_collapse.
-def dumpskippedanchors():
-    output('<div style="display:none;">\n')  # hide these fake elements
-    for i in warnpatterns:
-        if i['severity'] == severity.SKIP and len(i['members']) > 0:
-            projects = i['projectwarning'].keys()
-            for p in projects:
-                output('<div id="' + i['projectanchor'][p] + '"></div>' +
-                       '<div id="' + i['projectanchor'][p] + '_mark"></div>\n')
-    output('</div>\n')
-
-def allpatterns(cat):
-    pats = ''
-    for i in cat['patterns']:
-        pats += i
-        pats += ' / '
-    return pats
-
-def descriptionfor(cat):
-    if cat['description'] != '':
-        return cat['description']
-    return allpatterns(cat)
+          w['projects'][pname] = 1
+        return
+      else:
+        # If we end up here, there was a problem parsing the log
+        # probably caused by 'make -j' mixing the output from
+        # 2 or more concurrent compiles
+        pass
 
 
-# show which warnings no longer occur
-def dumpfixed():
-    global anchor
-    anchor += 1
-    mark = str(anchor) + '_mark'
-    output('\n<br><p style="background-color:lightblue"><b>' +
-           '<button id="' + mark + '" ' +
-           'class="bt" onclick="expand(' + str(anchor) + ');">' +
-           '&#x2295</button> Fixed warnings. ' +
-           'No more occurences. Please consider turning these into ' +
-           'errors if possible, before they are reintroduced in to the build' +
-           ':</b></p>\n')
-    output('<blockquote>\n')
-    fixed_patterns = []
-    for i in warnpatterns:
-        if len(i['members']) == 0:
-            fixed_patterns.append(i['description'] + ' (' +
-                                  allpatterns(i) + ')')
-        if i['option']:
-            fixed_patterns.append(' ' + i['option'])
-    fixed_patterns.sort()
-    output('<div id="' + str(anchor) + '" style="display:none;"><table>\n')
-    for i in fixed_patterns:
-        tablerow(i)
-    output('</table></div>\n')
-    output('</blockquote>\n')
+def compile_patterns():
+  """Precompiling every pattern speeds up parsing by about 30x."""
+  for i in warn_patterns:
+    i['compiled_patterns'] = []
+    for pat in i['patterns']:
+      i['compiled_patterns'].append(re.compile(pat))
 
-def warningwithurl(line):
-    if not args.url:
-        return line
-    m = re.search( r'^([^ :]+):(\d+):(.+)', line, re.M|re.I)
-    if not m:
-        return line
-    filepath = m.group(1)
-    linenumber = m.group(2)
-    warning = m.group(3)
-    if args.separator:
-        return '<a href="' + args.url + '/' + filepath + args.separator + linenumber + '">' + filepath + ':' + linenumber + '</a>:' + warning
+
+def parse_input_file():
+  """Parse input file, match warning lines."""
+  global platform_version
+  global target_product
+  global target_variant
+  infile = open(args.buildlog, 'r')
+  line_counter = 0
+
+  warning_pattern = re.compile('.* warning:.*')
+  compile_patterns()
+
+  # read the log file and classify all the warnings
+  warning_lines = set()
+  for line in infile:
+    # replace fancy quotes with plain ol' quotes
+    line = line.replace('‘', "'")
+    line = line.replace('’', "'")
+    if warning_pattern.match(line):
+      if line not in warning_lines:
+        classify_warning(line)
+        warning_lines.add(line)
+    elif line_counter < 50:
+      # save a little bit of time by only doing this for the first few lines
+      line_counter += 1
+      m = re.search('(?<=^PLATFORM_VERSION=).*', line)
+      if m is not None:
+        platform_version = m.group(0)
+      m = re.search('(?<=^TARGET_PRODUCT=).*', line)
+      if m is not None:
+        target_product = m.group(0)
+      m = re.search('(?<=^TARGET_BUILD_VARIANT=).*', line)
+      if m is not None:
+        target_variant = m.group(0)
+
+
+# Return s with escaped quotation characters.
+def escape_string(s):
+  return s.replace('"', '\\"')
+
+
+# Return s without trailing '\n' and escape the quotation characters.
+def strip_escape_string(s):
+  if not s:
+    return s
+  s = s[:-1] if s[-1] == '\n' else s
+  return escape_string(s)
+
+
+def emit_warning_array(name):
+  print 'var warning_{} = ['.format(name)
+  for i in range(len(warn_patterns)):
+    print '{},'.format(warn_patterns[i][name])
+  print '];'
+
+
+def emit_warning_arrays():
+  emit_warning_array('severity')
+  print 'var warning_description = ['
+  for i in range(len(warn_patterns)):
+    if warn_patterns[i]['members']:
+      print '"{}",'.format(escape_string(warn_patterns[i]['description']))
     else:
-        return '<a href="' + args.url + '/' + filepath + '">' + filepath + '</a>:' + linenumber + ':' + warning
+      print '"",'  # no such warning
+  print '];'
 
-def dumpgroup(sev, anchor, description, warnings):
-    mark = anchor + '_mark'
-    output('\n<table class="t1">\n')
-    output('<tr bgcolor="' + severity.color[sev] + '">' +
-           '<td><button class="bt" id="' + mark +
-           '" onclick="expand(\'' + anchor + '\');">' +
-           '&#x2295</button> ' + description + '</td></tr>\n')
-    output('</table>\n')
-    output('<div id="' + anchor + '" style="display:none;">')
-    output('<table class="t1">\n')
-    for i in warnings:
-        tablerow(warningwithurl(i))
-    output('</table></div>\n')
+scripts_for_warning_groups = """
+  function compareMessages(x1, x2) { // of the same warning type
+    return (WarningMessages[x1[2]] <= WarningMessages[x2[2]]) ? -1 : 1;
+  }
+  function byMessageCount(x1, x2) {
+    return x2[2] - x1[2];  // reversed order
+  }
+  function bySeverityMessageCount(x1, x2) {
+    // orer by severity first
+    if (x1[1] != x2[1])
+      return  x1[1] - x2[1];
+    return byMessageCount(x1, x2);
+  }
+  const ParseLinePattern = /^([^ :]+):(\\d+):(.+)/;
+  function addURL(line) {
+    if (FlagURL == "") return line;
+    if (FlagSeparator == "") {
+      return line.replace(ParseLinePattern,
+        "<a href='" + FlagURL + "/$1'>$1</a>:$2:$3");
+    }
+    return line.replace(ParseLinePattern,
+      "<a href='" + FlagURL + "/$1" + FlagSeparator + "$2'>$1:$2</a>:$3");
+  }
+  function createArrayOfDictionaries(n) {
+    var result = [];
+    for (var i=0; i<n; i++) result.push({});
+    return result;
+  }
+  function groupWarningsBySeverity() {
+    // groups is an array of dictionaries,
+    // each dictionary maps from warning type to array of warning messages.
+    var groups = createArrayOfDictionaries(SeverityColors.length);
+    for (var i=0; i<Warnings.length; i++) {
+      var w = Warnings[i][0];
+      var s = WarnPatternsSeverity[w];
+      var k = w.toString();
+      if (!(k in groups[s]))
+        groups[s][k] = [];
+      groups[s][k].push(Warnings[i]);
+    }
+    return groups;
+  }
+  function groupWarningsByProject() {
+    var groups = createArrayOfDictionaries(ProjectNames.length);
+    for (var i=0; i<Warnings.length; i++) {
+      var w = Warnings[i][0];
+      var p = Warnings[i][1];
+      var k = w.toString();
+      if (!(k in groups[p]))
+        groups[p][k] = [];
+      groups[p][k].push(Warnings[i]);
+    }
+    return groups;
+  }
+  var GlobalAnchor = 0;
+  function createWarningSection(header, color, group) {
+    var result = "";
+    var groupKeys = [];
+    var totalMessages = 0;
+    for (var k in group) {
+       totalMessages += group[k].length;
+       groupKeys.push([k, WarnPatternsSeverity[parseInt(k)], group[k].length]);
+    }
+    groupKeys.sort(bySeverityMessageCount);
+    for (var idx=0; idx<groupKeys.length; idx++) {
+      var k = groupKeys[idx][0];
+      var messages = group[k];
+      var w = parseInt(k);
+      var wcolor = SeverityColors[WarnPatternsSeverity[w]];
+      var description = WarnPatternsDescription[w];
+      if (description.length == 0)
+          description = "???";
+      GlobalAnchor += 1;
+      result += "<table class='t1'><tr bgcolor='" + wcolor + "'><td>" +
+                "<button class='bt' id='" + GlobalAnchor + "_mark" +
+                "' onclick='expand(\\"" + GlobalAnchor + "\\");'>" +
+                "&#x2295</button> " +
+                description + " (" + messages.length + ")</td></tr></table>";
+      result += "<div id='" + GlobalAnchor +
+                "' style='display:none;'><table class='t1'>";
+      var c = 0;
+      messages.sort(compareMessages);
+      for (var i=0; i<messages.length; i++) {
+        result += "<tr><td class='c" + c + "'>" +
+                  addURL(WarningMessages[messages[i][2]]) + "</td></tr>";
+        c = 1 - c;
+      }
+      result += "</table></div>";
+    }
+    if (result.length > 0) {
+      return "<br><span style='background-color:" + color + "'><b>" +
+             header + ": " + totalMessages +
+             "</b></span><blockquote><table class='t1'>" +
+             result + "</table></blockquote>";
 
-# dump warnings in a category
-def dumpcategory(sev, cat):
-    description = descriptionfor(cat) + ' (' + str(len(cat['members'])) + ')'
-    dumpgroup(sev, cat['anchor'], description, cat['members'])
-
-# similar to dumpcategory but output one table per project.
-def dumpcategorybyproject(sev, cat):
-    warning = descriptionfor(cat)
-    projects = cat['projectwarning'].keys()
-    projects.sort()
-    for p in projects:
-        anchor = cat['projectanchor'][p]
-        projectwarnings = cat['projectwarning'][p]
-        description = '{}, in {} ({})'.format(warning, p, len(projectwarnings))
-        dumpgroup(sev, anchor, description, projectwarnings)
-
-def findproject(line):
-    for p in projectpatterns:
-        if p['pattern'].match(line):
-            return p['description']
-    return '???'
-
-def classifywarning(line):
-    global anchor
-    for i in warnpatterns:
-        for cpat in i['compiledpatterns']:
-            if cpat.match(line):
-                i['members'].append(line)
-                pname = findproject(line)
-                # Count warnings by project.
-                if pname in i['projects']:
-                    i['projects'][pname] += 1
-                else:
-                    i['projects'][pname] = 1
-                # Collect warnings by project.
-                if args.byproject:
-                    if pname in i['projectwarning']:
-                        i['projectwarning'][pname].append(line)
-                    else:
-                        i['projectwarning'][pname] = [line]
-                    if pname not in i['projectanchor']:
-                        anchor += 1
-                        i['projectanchor'][pname] = str(anchor)
-                return
-            else:
-                # If we end up here, there was a problem parsing the log
-                # probably caused by 'make -j' mixing the output from
-                # 2 or more concurrent compiles
-                pass
-
-# precompiling every pattern speeds up parsing by about 30x
-def compilepatterns():
-    for i in warnpatterns:
-        i['compiledpatterns'] = []
-        for pat in i['patterns']:
-            i['compiledpatterns'].append(re.compile(pat))
-
-def parseinputfile():
-    global platformversion
-    global targetproduct
-    global targetvariant
-    infile = open(args.buildlog, 'r')
-    linecounter = 0
-
-    warningpattern = re.compile('.* warning:.*')
-    compilepatterns()
-
-    # read the log file and classify all the warnings
-    warninglines = set()
-    for line in infile:
-        # replace fancy quotes with plain ol' quotes
-        line = line.replace("‘", "'");
-        line = line.replace("’", "'");
-        if warningpattern.match(line):
-            if line not in warninglines:
-                classifywarning(line)
-                warninglines.add(line)
-        else:
-            # save a little bit of time by only doing this for the first few lines
-            if linecounter < 50:
-                linecounter +=1
-                m = re.search('(?<=^PLATFORM_VERSION=).*', line)
-                if m != None:
-                    platformversion = m.group(0)
-                m = re.search('(?<=^TARGET_PRODUCT=).*', line)
-                if m != None:
-                    targetproduct = m.group(0)
-                m = re.search('(?<=^TARGET_BUILD_VARIANT=).*', line)
-                if m != None:
-                    targetvariant = m.group(0)
+    }
+    return "";  // empty section
+  }
+  function generateSectionsBySeverity() {
+    var result = "";
+    var groups = groupWarningsBySeverity();
+    for (s=0; s<SeverityColors.length; s++) {
+      result += createWarningSection(SeverityHeaders[s], SeverityColors[s], groups[s]);
+    }
+    return result;
+  }
+  function generateSectionsByProject() {
+    var result = "";
+    var groups = groupWarningsByProject();
+    for (i=0; i<groups.length; i++) {
+      result += createWarningSection(ProjectNames[i], 'lightgrey', groups[i]);
+    }
+    return result;
+  }
+  function groupWarnings(generator) {
+    GlobalAnchor = 0;
+    var e = document.getElementById("warning_groups");
+    e.innerHTML = generator();
+  }
+  function groupBySeverity() {
+    groupWarnings(generateSectionsBySeverity);
+  }
+  function groupByProject() {
+    groupWarnings(generateSectionsByProject);
+  }
+"""
 
 
-# dump the html output to stdout
-def dumphtml():
-    dumphtmlprologue('Warnings for ' + platformversion + ' - ' + targetproduct + ' - ' + targetvariant)
-    dumpstats()
-    dumpstatsbyproject()
-    emitbuttons()
-    # sort table based on number of members once dumpstats has deduplicated the
-    # members.
-    warnpatterns.sort(reverse=True, key=lambda i: len(i['members']))
-    # Dump warnings by severity. If severity.SKIP warnings are not dumpped,
-    # the project anchors should be dumped through dumpskippedanchors.
-    for s in severity.kinds:
-        dumpseverity(s)
-    dumpfixed()
-    dumphtmlepilogue()
+# Emit a JavaScript const string
+def emit_const_string(name, value):
+  print 'const ' + name + ' = "' + escape_string(value) + '";'
+
+
+# Emit a JavaScript const integer array.
+def emit_const_int_array(name, array):
+  print 'const ' + name + ' = ['
+  for n in array:
+    print str(n) + ','
+  print '];'
+
+
+# Emit a JavaScript const string array.
+def emit_const_string_array(name, array):
+  print 'const ' + name + ' = ['
+  for s in array:
+    print '"' + strip_escape_string(s) + '",'
+  print '];'
+
+
+# Emit a JavaScript const object array.
+def emit_const_object_array(name, array):
+  print 'const ' + name + ' = ['
+  for x in array:
+    print str(x) + ','
+  print '];'
+
+
+def emit_js_data():
+  """Dump dynamic HTML page's static JavaScript data."""
+  emit_const_string('FlagURL', args.url if args.url else '')
+  emit_const_string('FlagSeparator', args.separator if args.separator else '')
+  emit_const_string_array('SeverityColors', severity.colors)
+  emit_const_string_array('SeverityHeaders', severity.headers)
+  emit_const_string_array('SeverityColumnHeaders', severity.column_headers)
+  emit_const_string_array('ProjectNames', project_names)
+  emit_const_int_array('WarnPatternsSeverity',
+                       [w['severity'] for w in warn_patterns])
+  emit_const_string_array('WarnPatternsDescription',
+                          [w['description'] for w in warn_patterns])
+  emit_const_string_array('WarnPatternsOption',
+                          [w['option'] for w in warn_patterns])
+  emit_const_string_array('WarningMessages', warning_messages)
+  emit_const_object_array('Warnings', warning_records)
+
+draw_table_javascript = """
+google.charts.load('current', {'packages':['table']});
+google.charts.setOnLoadCallback(drawTable);
+function drawTable() {
+  var data = new google.visualization.DataTable();
+  data.addColumn('string', StatsHeader[0]);
+  for (var i=1; i<StatsHeader.length; i++) {
+    data.addColumn('number', StatsHeader[i]);
+  }
+  data.addRows(StatsRows);
+  for (var i=0; i<StatsRows.length; i++) {
+    for (var j=0; j<StatsHeader.length; j++) {
+      data.setProperty(i, j, 'style', 'border:1px solid black;');
+    }
+  }
+  var table = new google.visualization.Table(document.getElementById('stats_table'));
+  table.draw(data, {allowHtml: true, alternatingRowStyle: true});
+}
+"""
+
+
+def dump_html():
+  """Dump the html output to stdout."""
+  dump_html_prologue('Warnings for ' + platform_version + ' - ' +
+                     target_product + ' - ' + target_variant)
+  dump_stats()
+  print '<br><div id="stats_table"></div><br>'
+  print '\n<script>'
+  emit_js_data()
+  print scripts_for_warning_groups
+  print '</script>'
+  emit_buttons()
+  # Warning messages are grouped by severities or project names.
+  print '<br><div id="warning_groups"></div>'
+  if args.byproject:
+    print '<script>groupByProject();</script>'
+  else:
+    print '<script>groupBySeverity();</script>'
+  dump_fixed()
+  dump_html_epilogue()
 
 
 ##### Functions to count warnings and dump csv file. #########################
 
-def descriptionforcsv(cat):
-    if cat['description'] == '':
-        return '?'
-    return cat['description']
 
-def stringforcsv(s):
-    if ',' in s:
-        return '"{}"'.format(s)
-    return s
+def description_for_csv(category):
+  if not category['description']:
+    return '?'
+  return category['description']
 
-def countseverity(sev, kind):
-  sum = 0
-  for i in warnpatterns:
-      if i['severity'] == sev and len(i['members']) > 0:
-          n = len(i['members'])
-          sum += n
-          warning = stringforcsv(kind + ': ' + descriptionforcsv(i))
-          print '{},,{}'.format(n, warning)
-          # print number of warnings for each project, ordered by project name.
-          projects = i['projects'].keys()
-          projects.sort()
-          for p in projects:
-              print '{},{},{}'.format(i['projects'][p], p, warning)
-  print '{},,{}'.format(sum, kind + ' warnings')
-  return sum
+
+def string_for_csv(s):
+  # Only some Java warning desciptions have used quotation marks.
+  # TODO(chh): if s has double quote character, s should be quoted.
+  if ',' in s:
+    # TODO(chh): replace a double quote with two double quotes in s.
+    return '"{}"'.format(s)
+  return s
+
+
+def count_severity(sev, kind):
+  """Count warnings of given severity."""
+  total = 0
+  for i in warn_patterns:
+    if i['severity'] == sev and i['members']:
+      n = len(i['members'])
+      total += n
+      warning = string_for_csv(kind + ': ' + description_for_csv(i))
+      print '{},,{}'.format(n, warning)
+      # print number of warnings for each project, ordered by project name.
+      projects = i['projects'].keys()
+      projects.sort()
+      for p in projects:
+        print '{},{},{}'.format(i['projects'][p], p, warning)
+  print '{},,{}'.format(total, kind + ' warnings')
+  return total
+
 
 # dump number of warnings in csv format to stdout
-def dumpcsv():
-    sortwarnings()
-    total = 0
-    for s in severity.kinds:
-        total += countseverity(s, severity.columnheader[s])
-    print '{},,{}'.format(total, 'All warnings')
+def dump_csv():
+  """Dump number of warnings in csv format to stdout."""
+  sort_warnings()
+  total = 0
+  for s in severity.range:
+    total += count_severity(s, severity.column_headers[s])
+  print '{},,{}'.format(total, 'All warnings')
 
 
-parseinputfile()
+##### Main function starts here. #########################
+
+parse_input_file()
 if args.gencsv:
-    dumpcsv()
+  dump_csv()
 else:
-    dumphtml()
+  dump_html()