summaryrefslogtreecommitdiff
path: root/chromium/testing
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2018-05-03 13:42:47 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2018-05-15 10:27:51 +0000
commit8c5c43c7b138c9b4b0bf56d946e61d3bbc111bec (patch)
treed29d987c4d7b173cf853279b79a51598f104b403 /chromium/testing
parent830c9e163d31a9180fadca926b3e1d7dfffb5021 (diff)
downloadqtwebengine-chromium-8c5c43c7b138c9b4b0bf56d946e61d3bbc111bec.tar.gz
BASELINE: Update Chromium to 66.0.3359.156
Change-Id: I0c9831ad39911a086b6377b16f995ad75a51e441 Reviewed-by: Michal Klocek <michal.klocek@qt.io>
Diffstat (limited to 'chromium/testing')
-rw-r--r--chromium/testing/BUILD.gn30
-rw-r--r--chromium/testing/android/appurify_support/BUILD.gn14
-rw-r--r--chromium/testing/android/driver/BUILD.gn1
-rw-r--r--chromium/testing/android/native_test/BUILD.gn2
-rw-r--r--chromium/testing/android/reporter/BUILD.gn4
-rw-r--r--chromium/testing/buildbot/filters/BUILD.gn3
-rw-r--r--chromium/testing/empty_main.cc8
-rw-r--r--chromium/testing/gmock/BUILD.gn14
-rw-r--r--chromium/testing/gtest/BUILD.gn9
-rw-r--r--chromium/testing/iossim/iossim.mm56
-rwxr-xr-xchromium/testing/libfuzzer/coverage.py501
-rw-r--r--chromium/testing/libfuzzer/efficient_fuzzer.md2
-rw-r--r--chromium/testing/libfuzzer/fuzzers/BUILD.gn37
-rw-r--r--chromium/testing/libfuzzer/fuzzers/skia_image_filter_proto_fuzzer.cc71
-rw-r--r--chromium/testing/libfuzzer/fuzzers/template_url_parser_fuzzer.cc2
-rw-r--r--chromium/testing/libfuzzer/fuzzers/v8_fuzzer.cc32
-rw-r--r--chromium/testing/libfuzzer/libprotobuf-mutator.md152
-rw-r--r--chromium/testing/libfuzzer/proto/BUILD.gn32
-rw-r--r--chromium/testing/libfuzzer/proto/skia_image_filter.proto1665
-rw-r--r--chromium/testing/libfuzzer/proto/skia_image_filter_proto_converter.cc2354
-rw-r--r--chromium/testing/libfuzzer/proto/skia_image_filter_proto_converter.h449
-rwxr-xr-xchromium/testing/scripts/check_network_annotations.py3
-rw-r--r--chromium/testing/scripts/common.py18
-rwxr-xr-xchromium/testing/scripts/run_gtest_perf_test.py123
-rwxr-xr-xchromium/testing/scripts/run_performance_tests.py140
-rwxr-xr-xchromium/testing/scripts/run_telemetry_benchmark_as_googletest.py14
-rw-r--r--chromium/testing/test.gni53
-rwxr-xr-xchromium/testing/test_env.py31
-rw-r--r--chromium/testing/trigger_scripts/OWNERS5
-rw-r--r--chromium/testing/trigger_scripts/PRESUBMIT.py47
-rw-r--r--chromium/testing/trigger_scripts/README.md9
-rwxr-xr-xchromium/testing/trigger_scripts/base_test_triggerer.py287
-rwxr-xr-xchromium/testing/trigger_scripts/perf_device_trigger.py65
-rwxr-xr-xchromium/testing/trigger_scripts/perf_device_trigger_unittest.py146
-rwxr-xr-xchromium/testing/trigger_scripts/trigger_multiple_dimensions.py133
-rwxr-xr-xchromium/testing/trigger_scripts/trigger_multiple_dimensions_unittest.py330
-rw-r--r--chromium/testing/variations/fieldtrial_testing_config.json473
-rwxr-xr-xchromium/testing/xvfb.py9
38 files changed, 6389 insertions, 935 deletions
diff --git a/chromium/testing/BUILD.gn b/chromium/testing/BUILD.gn
index fc50637493a..1477b17320e 100644
--- a/chromium/testing/BUILD.gn
+++ b/chromium/testing/BUILD.gn
@@ -11,3 +11,33 @@ source_set("gmock_mutant") {
"//base",
]
}
+
+# Used by linux-gcc-rel to ensure gcc doesn't choke on clang-only flags.
+executable("empty_main") {
+ sources = [
+ "empty_main.cc",
+ ]
+ deps = [
+ "//build/config:exe_and_shlib_deps",
+ ]
+}
+
+# Targets needed for isolate script to execute.
+group("test_scripts_shared") {
+ data = [
+ "//testing/test_env.py",
+ "//testing/xvfb.py",
+ ]
+}
+
+group("run_gtest_perf_test") {
+ data = [
+ "//testing/scripts/common.py",
+ "//testing/scripts/run_gtest_perf_test.py",
+ "//tools/perf/generate_legacy_perf_dashboard_json.py",
+ ]
+
+ data_deps = [
+ ":test_scripts_shared",
+ ]
+}
diff --git a/chromium/testing/android/appurify_support/BUILD.gn b/chromium/testing/android/appurify_support/BUILD.gn
deleted file mode 100644
index 0ce47ed83c6..00000000000
--- a/chromium/testing/android/appurify_support/BUILD.gn
+++ /dev/null
@@ -1,14 +0,0 @@
-# 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.
-
-import("//build/config/android/rules.gni")
-
-android_library("appurify_support_java") {
- chromium_code = true
-
- java_files = [
- "java/src/org/chromium/test/support/ResultsBundleGenerator.java",
- "java/src/org/chromium/test/support/RobotiumBundleGenerator.java",
- ]
-}
diff --git a/chromium/testing/android/driver/BUILD.gn b/chromium/testing/android/driver/BUILD.gn
index adefa2209b3..aa47b8efbbc 100644
--- a/chromium/testing/android/driver/BUILD.gn
+++ b/chromium/testing/android/driver/BUILD.gn
@@ -12,7 +12,6 @@ android_apk("driver_apk") {
deps = [
"//base:base_java",
"//base:base_java_test_support",
- "//testing/android/appurify_support:appurify_support_java",
"//testing/android/broker:broker_java",
"//testing/android/reporter:reporter_java",
]
diff --git a/chromium/testing/android/native_test/BUILD.gn b/chromium/testing/android/native_test/BUILD.gn
index 728cec028b9..2e63483f46e 100644
--- a/chromium/testing/android/native_test/BUILD.gn
+++ b/chromium/testing/android/native_test/BUILD.gn
@@ -49,10 +49,10 @@ android_library("native_test_java") {
":native_main_runner_java",
"//base:base_java",
"//base:base_java_test_support",
- "//testing/android/appurify_support:appurify_support_java",
"//testing/android/reporter:reporter_java",
]
java_files = [
+ "java/src/org/chromium/native_test/NativeTestApplication.java",
"java/src/org/chromium/native_test/NativeBrowserTestActivity.java",
"java/src/org/chromium/native_test/NativeTest.java",
"java/src/org/chromium/native_test/NativeTestInstrumentationTestRunner.java",
diff --git a/chromium/testing/android/reporter/BUILD.gn b/chromium/testing/android/reporter/BUILD.gn
index 4ff062fc4de..886569e7ecc 100644
--- a/chromium/testing/android/reporter/BUILD.gn
+++ b/chromium/testing/android/reporter/BUILD.gn
@@ -5,11 +5,15 @@
import("//build/config/android/rules.gni")
android_library("reporter_java") {
+ testonly = true
chromium_code = true
deps = [
"//base:base_java",
]
+
+ deps += android_extra_test_deps
+
java_files = [
"java/src/org/chromium/test/reporter/TestStatusListener.java",
"java/src/org/chromium/test/reporter/TestStatusReceiver.java",
diff --git a/chromium/testing/buildbot/filters/BUILD.gn b/chromium/testing/buildbot/filters/BUILD.gn
index 7dc3897ce72..62c90dc449f 100644
--- a/chromium/testing/buildbot/filters/BUILD.gn
+++ b/chromium/testing/buildbot/filters/BUILD.gn
@@ -29,7 +29,6 @@ source_set("browser_tests_filters") {
data = [
"//testing/buildbot/filters/mash.browser_tests.filter",
- "//testing/buildbot/filters/mojo.fyi.mash.browser_tests.filter",
"//testing/buildbot/filters/mojo.fyi.network_browser_tests.filter",
"//testing/buildbot/filters/browser_tests_cros_asan.filter",
"//testing/buildbot/filters/site-per-process.browser_tests.filter",
@@ -86,7 +85,7 @@ source_set("fuchsia_filters") {
"//testing/buildbot/filters/fuchsia.base_unittests.filter",
"//testing/buildbot/filters/fuchsia.content_unittests.filter",
"//testing/buildbot/filters/fuchsia.ipc_tests.filter",
- "//testing/buildbot/filters/fuchsia.mojo_system_unittests.filter",
+ "//testing/buildbot/filters/fuchsia.mojo_unittests.filter",
"//testing/buildbot/filters/fuchsia.net_unittests.filter",
"//testing/buildbot/filters/fuchsia.service_manager_unittests.filter",
"//testing/buildbot/filters/fuchsia.ui_base_unittests.filter",
diff --git a/chromium/testing/empty_main.cc b/chromium/testing/empty_main.cc
new file mode 100644
index 00000000000..759687fa2ed
--- /dev/null
+++ b/chromium/testing/empty_main.cc
@@ -0,0 +1,8 @@
+// Copyright (c) 2018 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.
+
+// Used by bots that want to check that compiler command lines still work.
+int main(int argc, char** argv) {
+ return 0;
+}
diff --git a/chromium/testing/gmock/BUILD.gn b/chromium/testing/gmock/BUILD.gn
index 11d7901755b..1492a158e9b 100644
--- a/chromium/testing/gmock/BUILD.gn
+++ b/chromium/testing/gmock/BUILD.gn
@@ -5,6 +5,9 @@
# The file/directory layout of Google Test is not yet considered stable. Until
# it stabilizes, Chromium code MUST use this target instead of reaching directly
# into //third_party/googletest.
+
+import("//build_overrides/build.gni")
+
source_set("gmock") {
testonly = true
sources = [
@@ -17,6 +20,17 @@ source_set("gmock") {
"//third_party/googletest:gmock",
]
+ # TODO(crbug.com/806952): Depending on gmock_mutant only if build_with_chromium,
+ # because gmock_mutant depends on //base which uses C++14. Since gmock is a
+ # third_party library used by other projects it should not include C++14 only code.
+ if (build_with_chromium) {
+ # Allow Chromium targets depending on gmock to #include testing/gmock_mutant.h
+ # without triggering a `gn check` error.
+ public_deps = [
+ "//testing:gmock_mutant",
+ ]
+ }
+
public_configs = [
"//third_party/googletest:gmock_config",
"//third_party/googletest:gtest_config",
diff --git a/chromium/testing/gtest/BUILD.gn b/chromium/testing/gtest/BUILD.gn
index c105fb1fd7e..fe7ba6d4839 100644
--- a/chromium/testing/gtest/BUILD.gn
+++ b/chromium/testing/gtest/BUILD.gn
@@ -4,6 +4,7 @@
import("//build_overrides/gtest.gni")
if (is_ios) {
+ import("//build/config/coverage/coverage.gni")
import("//build/config/ios/ios_sdk.gni")
import("//build/buildflag_header.gni")
}
@@ -39,7 +40,7 @@ static_library("gtest") {
# Android. https://codereview.chromium.org/2852613002/#ps20001
"empty.cc",
]
- deps = [
+ public_deps = [
"//third_party/googletest:gtest",
]
@@ -75,7 +76,9 @@ static_library("gtest") {
"../coverage_util_ios.h",
"../coverage_util_ios.mm",
]
- deps += [ ":ios_enable_coverage" ]
+ deps = [
+ ":ios_enable_coverage",
+ ]
}
}
@@ -92,6 +95,6 @@ source_set("gtest_main") {
if (is_ios) {
buildflag_header("ios_enable_coverage") {
header = "ios_enable_coverage.h"
- flags = [ "IOS_ENABLE_COVERAGE=$ios_enable_coverage" ]
+ flags = [ "IOS_ENABLE_COVERAGE=$use_clang_coverage" ]
}
}
diff --git a/chromium/testing/iossim/iossim.mm b/chromium/testing/iossim/iossim.mm
index 3a891aab3e7..dc2db6faf77 100644
--- a/chromium/testing/iossim/iossim.mm
+++ b/chromium/testing/iossim/iossim.mm
@@ -14,13 +14,15 @@ void PrintUsage() {
" where <app_path> is the path to the .app directory and <xctest_path> "
"is the path to an optional xctest bundle.\n"
"Options:\n"
+ " -u Specifies the device udid to use. Will use -d, -s values to get "
+ "devices if not specified.\n"
" -d Specifies the device (must be one of the values from the iOS "
"Simulator's Hardware -> Device menu. Defaults to 'iPhone 6s'.\n"
" -w Wipe the device's contents and settings before running the "
"test.\n"
" -e Specifies an environment key=value pair that will be"
" set in the simulated application's environment.\n"
- " -t Specifies a test or test suite that should be included in the"
+ " -t Specifies a test or test suite that should be included in the "
"test run. All other tests will be excluded from this run.\n"
" -c Specifies command line flags to pass to application.\n"
" -p Print the device's home directory, does not run a test.\n"
@@ -196,6 +198,19 @@ NSString* GetDeviceBySDKAndName(NSDictionary* simctl_list,
return nil;
}
+bool FindDeviceByUDID(NSDictionary* simctl_list, NSString* udid) {
+ NSDictionary* devices_table = simctl_list[@"devices"];
+ for (id runtimes in devices_table) {
+ NSArray* devices = devices_table[runtimes];
+ for (NSDictionary* device in devices) {
+ if ([device[@"udid"] isEqualToString:udid]) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
// Prints the HOME environment variable for a device. Used by the bots to
// package up all the test data.
void PrintDeviceHome(NSString* udid) {
@@ -229,7 +244,7 @@ int RunApplication(NSString* app_path,
NSString* xctest_path,
NSString* udid,
NSMutableDictionary* app_env,
- NSString* cmd_args,
+ NSMutableArray* cmd_args,
NSMutableArray* tests_filter) {
NSString* tempFilePath = [NSTemporaryDirectory()
stringByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
@@ -270,8 +285,8 @@ int RunApplication(NSString* app_path,
[testTargetName setObject:app_env forKey:@"EnvironmentVariables"];
}
- if (cmd_args) {
- [testTargetName setObject:@[ cmd_args ] forKey:@"CommandLineArguments"];
+ if ([cmd_args count] > 0) {
+ [testTargetName setObject:cmd_args forKey:@"CommandLineArguments"];
}
if ([tests_filter count] > 0) {
@@ -332,7 +347,7 @@ int main(int argc, char* const argv[]) {
NSString* app_path = nil;
NSString* xctest_path = nil;
- NSString* cmd_args = nil;
+ NSString* udid = nil;
NSString* device_name = @"iPhone 6s";
bool wants_wipe = false;
bool wants_print_home = false;
@@ -343,6 +358,7 @@ int main(int argc, char* const argv[]) {
}
NSString* sdk_version = [NSString stringWithFormat:@"%0.1f", sdk];
NSMutableDictionary* app_env = [NSMutableDictionary dictionary];
+ NSMutableArray* cmd_args = [NSMutableArray array];
NSMutableArray* tests_filter = [NSMutableArray array];
int c;
@@ -354,12 +370,16 @@ int main(int argc, char* const argv[]) {
case 'd':
device_name = [NSString stringWithUTF8String:optarg];
break;
+ case 'u':
+ udid = [NSString stringWithUTF8String:optarg];
+ break;
case 'w':
wants_wipe = true;
break;
- case 'c':
- cmd_args = [NSString stringWithUTF8String:optarg];
- break;
+ case 'c': {
+ NSString* cmd_arg = [NSString stringWithUTF8String:optarg];
+ [cmd_args addObject:cmd_arg];
+ } break;
case 't': {
NSString* test = [NSString stringWithUTF8String:optarg];
[tests_filter addObject:test];
@@ -393,12 +413,22 @@ int main(int argc, char* const argv[]) {
}
}
- NSString* udid = GetDeviceBySDKAndName(simctl_list, device_name, sdk_version);
if (udid == nil) {
- LogError(@"Unable to find a device %@ with SDK %@.", device_name,
- sdk_version);
- PrintSupportedDevices(simctl_list);
- exit(kExitInvalidArguments);
+ udid = GetDeviceBySDKAndName(simctl_list, device_name, sdk_version);
+ if (udid == nil) {
+ LogError(@"Unable to find a device %@ with SDK %@.", device_name,
+ sdk_version);
+ PrintSupportedDevices(simctl_list);
+ exit(kExitInvalidArguments);
+ }
+ } else {
+ if (!FindDeviceByUDID(simctl_list, udid)) {
+ LogError(
+ @"Unable to find a device with udid %@. Use 'xcrun simctl list' to "
+ @"see valid device udids.",
+ udid);
+ exit(kExitInvalidArguments);
+ }
}
if (wants_print_home) {
diff --git a/chromium/testing/libfuzzer/coverage.py b/chromium/testing/libfuzzer/coverage.py
deleted file mode 100755
index 4ad6e26b66a..00000000000
--- a/chromium/testing/libfuzzer/coverage.py
+++ /dev/null
@@ -1,501 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2017 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.
-
-from __future__ import print_function
-
-import argparse
-import logging
-import os
-import subprocess
-import sys
-
-import SimpleHTTPServer
-import SocketServer
-
-HELP_MESSAGE = """
-This script helps to generate code coverage report. It uses Clang Source-based
-Code coverage (https://clang.llvm.org/docs/SourceBasedCodeCoverage.html).
-
-The output is a directory with HTML files that can be inspected via local web
-server (e.g. "python -m SimpleHTTPServer").
-
-In order to generate code coverage report, you need to build the target program
-with "use_clang_coverage=true" GN flag. You should also explicitly use the flag
-"is_component_build=false" as explained at the end of this paragraph.
-use_component_build is not compatible with sanitizer flags: "is_asan",
-"is_msan", etc. It is also incompatible with "optimize_for_fuzzing" and with
-"is_component_build". Beware that if "is_debug" is true (it defaults to true),
-then "is_component_build" will be set to true unless specified false as an
-argument. So it is best to pass is_component_build=false when using
-"use_clang_coveage".
-
-If you are building a fuzz target, you need to add "use_libfuzzer=true" GN flag
-as well.
-
-Sample workflow for a fuzz target (e.g. pdfium_fuzzer):
-
-cd <chromium_checkout_dir>/src
-gn gen //out/coverage --args='use_clang_coverage=true use_libfuzzer=true \
-is_component_build=false'
-ninja -C out/coverage -j100 pdfium_fuzzer
-./testing/libfuzzer/coverage.py \\
- --output="coverage_out" \\
- --command="out/coverage/pdfium_fuzzer -runs=<runs> <corpus_dir>"
- --filter third_party/pdfium/ pdf/
-
-where:
- <corpus_dir> - directory containing samples files for this format.
- <runs> - number of times to fuzz target function. Should be 0 when you just
- want to see the coverage on corpus and don't want to fuzz at all.
-Then, open http://localhost:9000/report.html to see coverage report.
-
-For Googlers, there are examples available at go/chrome-code-coverage-examples.
-
-If you have any questions, please send an email to fuzzing@chromium.org.
-"""
-
-HTML_FILE_EXTENSION = '.html'
-
-CHROME_SRC_PATH = os.path.dirname(
- os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
-LLVM_BUILD_PATH = os.path.join(CHROME_SRC_PATH, 'third_party', 'llvm-build')
-LLVM_BIN_PATH = os.path.join(LLVM_BUILD_PATH, 'Release+Asserts', 'bin')
-LLVM_COV_PATH = os.path.join(LLVM_BIN_PATH, 'llvm-cov')
-LLVM_PROFDATA_PATH = os.path.join(LLVM_BIN_PATH, 'llvm-profdata')
-
-LLVM_PROFILE_FILE_NAME = 'coverage.profraw'
-LLVM_COVERAGE_FILE_NAME = 'coverage.profdata'
-
-REPORT_FILENAME = 'report.html'
-
-REPORT_TEMPLATE = """<!DOCTYPE html>
-<html>
-<head>
-<meta name='viewport' content='width=device-width,initial-scale=1'>
-<meta charset='UTF-8'>
-<link rel="stylesheet" type="text/css" href="/style.css">
-</head>
-<body>
-{table_data}
-</body></html>"""
-
-SINGLE_FILE_START_MARKER = '<!doctype html><html>'
-SINGLE_FILE_END_MARKER = '</body></html>'
-
-SOURCE_FILENAME_START_MARKER = (
- "<body><div class='centered'><table><div class='source-name-title'><pre>")
-SOURCE_FILENAME_END_MARKER = '</pre>'
-
-STYLE_START_MARKER = '<style>'
-STYLE_END_MARKER = '</style>'
-STYLE_FILENAME = 'style.css'
-
-ZERO_FUNCTION_FILE_TEXT = 'Files which contain no functions'
-
-HTTP_PORT = 9000
-COVERAGE_REPORT_LINK = 'http://127.0.0.1:%d/report.html' % HTTP_PORT
-
-LARGE_BINARY_THRESHOLD = 128 * 2 ** 20
-
-
-def CheckBinaryAndArgs(executable_path, filters):
- """Verify that the given file has been built with coverage instrumentation,
- also perform check for "--filter" argument and for the binary size."""
- CheckFilterArgument(filters)
-
- with open(executable_path) as file_handle:
- data = file_handle.read()
-
- if len(data) > LARGE_BINARY_THRESHOLD and not filters:
- logging.warning('The target binary is quite large. Generating the full '
- 'coverage report may take a while. To generate the report '
- 'faster, consider using the "--filter" argument to specify '
- 'the source code files and directories shown in the report.'
- )
-
- # For minimum threshold reference, tiny "Hello World" program has count of 34.
- if data.count('__llvm_profile') > 20:
- return
-
- logging.error('It looks like the target binary has been compiled without '
- 'coverage instrumentation.')
- print('Have you used use_clang_coverage=true flag in GN args? [y/N]')
- answer = raw_input()
- if not answer.lower().startswith('y'):
- print('Exiting.')
- sys.exit(-1)
-
-
-def CheckFilterArgument(filters):
- """Verify that all the paths specified in --filter arg exist."""
- for path in filters:
- if not os.path.exists(path):
- logging.error('The path specified does not exist: %s.' % path)
- sys.exit(-1)
-
-
-def CreateOutputDir(dir_path):
- """Create a directory for the script output files."""
- if not os.path.exists(dir_path):
- os.mkdir(dir_path)
- return
-
- if os.path.isdir(dir_path):
- logging.warning('%s already exists.', dir_path)
- return
-
- logging.error('%s exists and does not point to a directory.', dir_path)
- raise Exception('Invalid --output argument specified.')
-
-
-def DownloadCoverageToolsIfNeeded():
- """Temporary solution to download llvm-profdata and llvm-cov tools."""
- # TODO(mmoroz): remove this function once tools get included to Clang bundle:
- # https://chromium-review.googlesource.com/c/chromium/src/+/688221
- clang_script_path = os.path.join(CHROME_SRC_PATH, 'tools', 'clang', 'scripts')
- sys.path.append(clang_script_path)
- import update as clang_update
- import urllib2
-
- def _GetRevisionFromStampFile(file_path):
- """Read the build stamp file created by tools/clang/scripts/update.py."""
- if not os.path.exists(file_path):
- return 0, 0
-
- with open(file_path) as file_handle:
- revision_stamp_data = file_handle.readline().strip()
- revision_stamp_data = revision_stamp_data.split('-')
- return int(revision_stamp_data[0]), int(revision_stamp_data[1])
-
- clang_revision, clang_sub_revision = _GetRevisionFromStampFile(
- clang_update.STAMP_FILE)
-
- coverage_revision_stamp_file = os.path.join(
- os.path.dirname(clang_update.STAMP_FILE), 'cr_coverage_revision')
- coverage_revision, coverage_sub_revision = _GetRevisionFromStampFile(
- coverage_revision_stamp_file)
-
- if (coverage_revision == clang_revision and
- coverage_sub_revision == clang_sub_revision):
- # LLVM coverage tools are up to date, bail out.
- return clang_revision
-
- package_version = '%d-%d' % (clang_revision, clang_sub_revision)
- coverage_tools_file = 'llvm-code-coverage-%s.tgz' % package_version
-
- # The code bellow follows the code from tools/clang/scripts/update.py.
- if sys.platform == 'win32' or sys.platform == 'cygwin':
- coverage_tools_url = clang_update.CDS_URL + '/Win/' + coverage_tools_file
- elif sys.platform == 'darwin':
- coverage_tools_url = clang_update.CDS_URL + '/Mac/' + coverage_tools_file
- else:
- assert sys.platform.startswith('linux')
- coverage_tools_url = (
- clang_update.CDS_URL + '/Linux_x64/' + coverage_tools_file)
-
- try:
- clang_update.DownloadAndUnpack(coverage_tools_url,
- clang_update.LLVM_BUILD_DIR)
- print('Coverage tools %s unpacked.' % package_version)
- with open(coverage_revision_stamp_file, 'w') as file_handle:
- file_handle.write(package_version)
- file_handle.write('\n')
- except urllib2.URLError:
- raise Exception(
- 'Failed to download coverage tools: %s.' % coverage_tools_url)
-
- return clang_revision
-
-
-def ExtractAndFixFilename(data, source_dir):
- """Extract full paths to source code files and replace with relative paths."""
- filename_start = data.find(SOURCE_FILENAME_START_MARKER)
- if filename_start == -1:
- logging.error('Failed to extract source code filename.')
- raise Exception('Failed to process coverage dump.')
-
- filename_start += len(SOURCE_FILENAME_START_MARKER)
- filename_end = data[filename_start:].find(SOURCE_FILENAME_END_MARKER)
- if filename_end == -1:
- logging.error('Failed to extract source code filename.')
- raise Exception('Failed to process coverage dump.')
-
- filename_end += filename_start
-
- filename = data[filename_start:filename_end]
-
- source_dir = os.path.abspath(source_dir)
-
- if not filename.startswith(source_dir):
- logging.error('Invalid source code path ("%s") specified.\n'
- 'Coverage dump refers to "%s".', source_dir, filename)
- raise Exception('Failed to process coverage dump.')
-
- filename = filename[len(source_dir):]
- filename = filename.lstrip('/\\')
-
- # Replace the filename with the shorter version.
- data = data[:filename_start] + filename + data[filename_end:]
- return filename, data
-
-
-def GenerateReport(report_data):
- """Build HTML page with the summary report and links to individual files."""
- table_data = '<table class="centered">\n'
- report_lines = report_data.splitlines()
-
- # Write header.
- table_data += ' <tr class="source-name-title">\n'
- for column in report_lines[0].split(' '):
- if not column:
- continue
- table_data += ' <th><pre>%s</pre></th>\n' % column
- table_data += ' </tr>\n'
-
- for line in report_lines[1:-1]:
- if not line or line.startswith('---'):
- continue
-
- if line.startswith(ZERO_FUNCTION_FILE_TEXT):
- table_data += ' <tr class="source-name-title">\n'
- table_data += (
- ' <th class="column-entry-left"><pre>%s</pre></th>\n' % line)
- table_data += ' </tr>\n'
- continue
-
- table_data += ' <tr>\n'
-
- columns = line.split()
-
- # First column is a file name, build a link.
- table_data += (' <td class="column-entry-left">\n'
- ' <a href="/%s"><pre>%s</pre></a>\n'
- ' </td>\n') % (columns[0] + HTML_FILE_EXTENSION,
- columns[0])
-
- for column in columns[1:]:
- table_data += ' <td class="column-entry"><pre>%s</pre></td>\n' % column
- table_data += ' </tr>\n'
-
- # Write the last "TOTAL" row.
- table_data += ' <tr class="source-name-title">\n'
- for column in report_lines[-1].split():
- table_data += ' <td class="column-entry"><pre>%s</pre></td>\n' % column
- table_data += ' </tr>\n'
- table_data += '</table>\n'
-
- return REPORT_TEMPLATE.format(table_data=table_data)
-
-
-def GenerateSources(executable_path, output_dir, source_dir, filters,
- coverage_file):
- """Generate coverage visualization for source code files."""
- llvm_cov_command = [
- LLVM_COV_PATH, 'show', '-format=html', executable_path,
- '-instr-profile=%s' % coverage_file
- ]
-
- for path in filters:
- llvm_cov_command.append(path)
-
- data = subprocess.check_output(llvm_cov_command)
-
- # Extract CSS style from the data.
- style_start = data.find(STYLE_START_MARKER)
- style_end = data.find(STYLE_END_MARKER)
- if style_end <= style_start or style_start == -1:
- logging.error('Failed to extract CSS style from coverage report.')
- raise Exception('Failed to process coverage dump.')
-
- style_data = data[style_start + len(STYLE_START_MARKER):style_end]
-
- # Add hover for table <tr>.
- style_data += '\ntr:hover { background-color: #eee; }'
-
- with open(os.path.join(output_dir, STYLE_FILENAME), 'w') as file_handle:
- file_handle.write(style_data)
- style_length = (
- len(style_data) + len(STYLE_START_MARKER) + len(STYLE_END_MARKER))
-
- # Extract every source code file. Use "offset" to avoid creating new strings.
- offset = 0
- while True:
- file_start = data.find(SINGLE_FILE_START_MARKER, offset)
- if file_start == -1:
- break
-
- file_end = data.find(SINGLE_FILE_END_MARKER, offset)
- if file_end == -1:
- break
-
- file_end += len(SINGLE_FILE_END_MARKER)
- offset += file_end - file_start
-
- # Remove <style> as it's always the same and has been extracted separately.
- file_data = ReplaceStyleWithCss(data[file_start:file_end], style_length,
- STYLE_FILENAME)
-
- filename, file_data = ExtractAndFixFilename(file_data, source_dir)
- file_path = os.path.join(output_dir, filename)
- dirname = os.path.dirname(file_path)
-
- try:
- os.makedirs(dirname)
- except OSError:
- pass
-
- with open(file_path + HTML_FILE_EXTENSION, 'w') as file_handle:
- file_handle.write(file_data)
-
-
-def GenerateSummary(executable_path, output_dir, filters, coverage_file,
- clang_revision):
- """Generate code coverage summary report (i.e. a table with all files)."""
- llvm_cov_command = [
- LLVM_COV_PATH, 'report', executable_path,
- '-instr-profile=%s' % coverage_file
- ]
-
- for path in filters:
- llvm_cov_command.append(path)
-
- data = subprocess.check_output(llvm_cov_command)
- report = GenerateReport(data)
-
- with open(os.path.join(output_dir, REPORT_FILENAME), 'w') as file_handle:
- # TODO(mmoroz): remove this hacky warning after next clang roll.
- if filters and clang_revision < 315685:
- report = ('Warning: the report below contains information for all the '
- 'sources even though you used "--filter" option. This bug has '
- 'been fixed upstream. It will be fixed in Chromium after next '
- 'clang roll (https://reviews.llvm.org/rL315685).<br>' + report)
- file_handle.write(report)
-
-
-def ServeReportOnHTTP(output_directory):
- """Serve report directory on HTTP."""
- os.chdir(output_directory)
-
- SocketServer.TCPServer.allow_reuse_address = True
- httpd = SocketServer.TCPServer(('', HTTP_PORT),
- SimpleHTTPServer.SimpleHTTPRequestHandler)
- print('Load coverage report using %s. Press Ctrl+C to exit.' %
- COVERAGE_REPORT_LINK)
-
- try:
- httpd.serve_forever()
- except KeyboardInterrupt:
- httpd.server_close()
-
-
-def ProcessCoverageDump(profile_file, coverage_file):
- """Process and convert raw LLVM profile data into coverage data format."""
- print('Processing coverage dump and generating visualization.')
- merge_command = [
- LLVM_PROFDATA_PATH, 'merge', '-sparse', profile_file, '-o', coverage_file
- ]
- data = subprocess.check_output(merge_command)
-
- if not os.path.exists(coverage_file) or not os.path.getsize(coverage_file):
- logging.error('%s is either not created or empty:\n%s', coverage_file, data)
- raise Exception('Failed to merge coverage information after command run.')
-
-
-def ReplaceStyleWithCss(data, style_data_length, css_file_path):
- """Replace <style></style> data with the include of common style.css file."""
- style_start = data.find(STYLE_START_MARKER)
- # Since "style" data is always the same, try some optimization here.
- style_end = style_start + style_data_length
- if (style_end > len(data) or
- data[style_end - len(STYLE_END_MARKER):style_end] != STYLE_END_MARKER):
- # Looks like our optimization has failed, find end of "style" data.
- style_end = data.find(STYLE_END_MARKER)
- if style_end <= style_start or style_start == -1:
- logging.error('Failed to extract CSS style from coverage report.')
- raise Exception('Failed to process coverage dump.')
- style_end += len(STYLE_END_MARKER)
-
- css_include = (
- '<link rel="stylesheet" type="text/css" href="/%s">' % css_file_path)
- result = '\n'.join([data[:style_start], css_include, data[style_end:]])
- return result
-
-
-def RunCommand(command, profile_file):
- """Run the given command in order to generate raw LLVM profile data."""
- print('Running "%s".' % command)
- print('-' * 80)
- os.environ['LLVM_PROFILE_FILE'] = profile_file
- os.system(command)
- print('-' * 80)
- print('Finished command execution.')
-
- if not os.path.exists(profile_file) or not os.path.getsize(profile_file):
- logging.error('%s is either not created or empty.', profile_file)
- raise Exception('Failed to dump coverage information during command run.')
-
-
-def main():
- """The main routing for processing the arguments and generating coverage."""
- parser = argparse.ArgumentParser(
- description=HELP_MESSAGE,
- formatter_class=argparse.RawDescriptionHelpFormatter)
- parser.add_argument(
- '--command',
- required=True,
- help='The command to run target binary for which code coverage is '
- 'required.')
- parser.add_argument(
- '--source',
- required=False,
- default=CHROME_SRC_PATH,
- help='Location of chromium source checkout, if it differs from '
- 'current checkout: %s.' % CHROME_SRC_PATH)
- parser.add_argument(
- '--output',
- required=True,
- help='Directory where code coverage files will be written to.')
- parser.add_argument(
- '--filter',
- required=False,
- nargs='+',
- default = [],
- help='(Optional) Paths to source code files/directories shown in the '
- 'report. By default, the report shows all the sources compiled and '
- 'linked into the target executable.')
-
- if not len(sys.argv[1:]):
- # Print help when no arguments are provided on command line.
- parser.print_help()
- parser.exit()
-
- args = parser.parse_args()
-
- executable_path = args.command.split()[0]
-
- CheckBinaryAndArgs(executable_path, args.filter)
-
- clang_revision = DownloadCoverageToolsIfNeeded()
-
- CreateOutputDir(args.output)
- profile_file = os.path.join(args.output, LLVM_PROFILE_FILE_NAME)
- RunCommand(args.command, profile_file)
-
- coverage_file = os.path.join(args.output, LLVM_COVERAGE_FILE_NAME)
- ProcessCoverageDump(profile_file, coverage_file)
-
- GenerateSummary(executable_path, args.output, args.filter, coverage_file,
- clang_revision)
- GenerateSources(executable_path, args.output, args.source, args.filter,
- coverage_file)
-
- ServeReportOnHTTP(args.output)
-
- print('Done.')
-
-
-if __name__ == '__main__':
- main()
diff --git a/chromium/testing/libfuzzer/efficient_fuzzer.md b/chromium/testing/libfuzzer/efficient_fuzzer.md
index 339c5ab1ead..bbde9ab8703 100644
--- a/chromium/testing/libfuzzer/efficient_fuzzer.md
+++ b/chromium/testing/libfuzzer/efficient_fuzzer.md
@@ -303,7 +303,7 @@ All other options can be passed using `libfuzzer_options` property.
[ClusterFuzz status]: clusterfuzz.md#Status-Links
[Corpus GCS Bucket]: https://console.cloud.google.com/storage/clusterfuzz-corpus/libfuzzer
[issue 638836]: https://bugs.chromium.org/p/chromium/issues/detail?id=638836
-[coverage script]: https://cs.chromium.org/chromium/src/testing/libfuzzer/coverage.py
+[coverage script]: https://cs.chromium.org/chromium/src/tools/code_coverage/coverage.py
[gsutil]: https://cloud.google.com/storage/docs/gsutil
[libFuzzer options]: https://llvm.org/docs/LibFuzzer.html#options
[startup initialization]: https://llvm.org/docs/LibFuzzer.html#startup-initialization
diff --git a/chromium/testing/libfuzzer/fuzzers/BUILD.gn b/chromium/testing/libfuzzer/fuzzers/BUILD.gn
index ed5bc637492..1038fd0d507 100644
--- a/chromium/testing/libfuzzer/fuzzers/BUILD.gn
+++ b/chromium/testing/libfuzzer/fuzzers/BUILD.gn
@@ -220,26 +220,32 @@ fuzzer_test("v8_regexp_parser_fuzzer") {
libfuzzer_options = [ "max_len=64" ]
}
-fuzzer_test("v8_wasm_code_fuzzer") {
+fuzzer_test("v8_regexp_builtins_fuzzer") {
sources = []
deps = [
- "//v8:wasm_code_fuzzer",
+ "//v8:regexp_builtins_fuzzer",
]
- libfuzzer_options = [ "max_len=500" ]
}
-fuzzer_test("v8_wasm_compile_fuzzer") {
+fuzzer_test("v8_multi_return_fuzzer") {
sources = []
deps = [
- "//v8:wasm_compile_fuzzer",
+ "//v8:multi_return_fuzzer",
+ ]
+}
+
+fuzzer_test("v8_wasm_code_fuzzer") {
+ sources = []
+ deps = [
+ "//v8:wasm_code_fuzzer",
]
libfuzzer_options = [ "max_len=500" ]
}
-fuzzer_test("v8_wasm_call_fuzzer") {
+fuzzer_test("v8_wasm_compile_fuzzer") {
sources = []
deps = [
- "//v8:wasm_call_fuzzer",
+ "//v8:wasm_compile_fuzzer",
]
libfuzzer_options = [ "max_len=500" ]
}
@@ -518,3 +524,20 @@ fuzzer_test("v8_fully_instrumented_fuzzer") {
dict = "dicts/generated/javascript.dict"
libfuzzer_options = [ "only_ascii=1" ]
}
+
+if (!is_win) {
+ fuzzer_test("skia_image_filter_proto_fuzzer") {
+ sources = [
+ "skia_image_filter_proto_fuzzer.cc",
+ ]
+
+ deps = [
+ "//base",
+ "//base/test:test_support",
+ "//skia",
+ "//testing/libfuzzer/proto:skia_image_filter_converter",
+ "//testing/libfuzzer/proto:skia_image_filter_proto",
+ "//third_party/libprotobuf-mutator",
+ ]
+ }
+}
diff --git a/chromium/testing/libfuzzer/fuzzers/skia_image_filter_proto_fuzzer.cc b/chromium/testing/libfuzzer/fuzzers/skia_image_filter_proto_fuzzer.cc
new file mode 100644
index 00000000000..62e10c08fbb
--- /dev/null
+++ b/chromium/testing/libfuzzer/fuzzers/skia_image_filter_proto_fuzzer.cc
@@ -0,0 +1,71 @@
+// Copyright 2018 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.
+
+// Takes an Input protobuf message from libprotobuf-mutator, converts it to an
+// actual skia image filter and then applies it to a canvas for the purpose of
+// fuzzing skia. This should uncover bugs that could be used by a compromised
+// renderer to exploit the browser process.
+
+#include <stdlib.h>
+
+#include <iostream>
+#include <string>
+
+#include "testing/libfuzzer/proto/skia_image_filter_proto_converter.h"
+
+#include "base/process/memory.h"
+#include "base/test/test_discardable_memory_allocator.h"
+#include "third_party/libprotobuf-mutator/src/src/libfuzzer/libfuzzer_macro.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkImageFilter.h"
+
+protobuf_mutator::protobuf::LogSilencer log_silencer;
+
+using skia_image_filter_proto_converter::Input;
+using skia_image_filter_proto_converter::Converter;
+
+static const int kBitmapSize = 24;
+
+struct Environment {
+ base::TestDiscardableMemoryAllocator* discardable_memory_allocator;
+ Environment() {
+ base::EnableTerminationOnOutOfMemory();
+ discardable_memory_allocator = new base::TestDiscardableMemoryAllocator();
+ base::DiscardableMemoryAllocator::SetInstance(discardable_memory_allocator);
+ }
+};
+
+DEFINE_PROTO_FUZZER(const Input& input) {
+ static Environment environment = Environment();
+ ALLOW_UNUSED_LOCAL(environment);
+
+ static Converter converter = Converter();
+ std::string ipc_filter_message = converter.Convert(input);
+
+ // Allow the flattened skia filter to be retrieved easily.
+ if (getenv("LPM_DUMP_NATIVE_INPUT")) {
+ // Don't write a newline since it will make the output invalid (so that it
+ // cannot be fed to filter_fuzz_stub) Flush instead.
+ std::cout << ipc_filter_message << std::flush;
+ }
+
+ sk_sp<SkImageFilter> flattenable = SkImageFilter::Deserialize(
+ ipc_filter_message.c_str(), ipc_filter_message.size());
+
+ if (!flattenable)
+ return;
+
+ SkBitmap bitmap;
+ bitmap.allocN32Pixels(kBitmapSize, kBitmapSize);
+ SkCanvas canvas(bitmap);
+ canvas.clear(0x00000000);
+ SkPaint paint;
+ paint.setImageFilter(flattenable);
+ canvas.save();
+ canvas.clipRect(SkRect::MakeXYWH(0, 0, SkIntToScalar(kBitmapSize),
+ SkIntToScalar(kBitmapSize)));
+ canvas.drawBitmap(bitmap, 0, 0, &paint);
+ canvas.restore();
+}
diff --git a/chromium/testing/libfuzzer/fuzzers/template_url_parser_fuzzer.cc b/chromium/testing/libfuzzer/fuzzers/template_url_parser_fuzzer.cc
index 93ce6dea812..c9a6d5feb86 100644
--- a/chromium/testing/libfuzzer/fuzzers/template_url_parser_fuzzer.cc
+++ b/chromium/testing/libfuzzer/fuzzers/template_url_parser_fuzzer.cc
@@ -34,7 +34,7 @@ struct FuzzerFixedParams {
base::AtExitManager at_exit_manager; // used by ICU integration
-extern "C" int LLVMFuzzerInitialize(int argc, char*** argv) {
+extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) {
CHECK(base::i18n::InitializeICU());
return 0;
}
diff --git a/chromium/testing/libfuzzer/fuzzers/v8_fuzzer.cc b/chromium/testing/libfuzzer/fuzzers/v8_fuzzer.cc
index 690851580f9..3f95f344dce 100644
--- a/chromium/testing/libfuzzer/fuzzers/v8_fuzzer.cc
+++ b/chromium/testing/libfuzzer/fuzzers/v8_fuzzer.cc
@@ -46,11 +46,6 @@ class MockArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
public:
MockArrayBufferAllocator()
: v8::ArrayBuffer::Allocator(), currently_allocated_(0) {}
- void SetProtection(void* data,
- size_t length,
- Protection protection) override {
- allocator_->SetProtection(data, length, protection);
- }
void* Allocate(size_t length) override {
void* data = AllocateUninitialized(length);
@@ -73,33 +68,6 @@ class MockArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
// be innacurate.
free(ptr);
}
-
- void Free(void* data, size_t length, AllocationMode mode) override {
- switch (mode) {
- case AllocationMode::kNormal: {
- // Free locks and unlocks for us.
- Free(data, length);
- return;
- }
- case AllocationMode::kReservation: {
- lock_guard<mutex> mtx_locker(mtx_);
- currently_allocated_ -= length;
- allocator_->Free(data, length, mode);
- return;
- }
- default:
- NOTREACHED();
- }
- }
-
- void* Reserve(size_t length) override {
- lock_guard<mutex> mtx_locker(mtx_);
- if (length + currently_allocated_ > kAllocationLimit) {
- return nullptr;
- }
- currently_allocated_ += length;
- return allocator_->Reserve(length);
- }
};
void terminate_execution(v8::Isolate* isolate,
diff --git a/chromium/testing/libfuzzer/libprotobuf-mutator.md b/chromium/testing/libfuzzer/libprotobuf-mutator.md
index fdfe5d50df6..f7f6760cbc4 100644
--- a/chromium/testing/libfuzzer/libprotobuf-mutator.md
+++ b/chromium/testing/libfuzzer/libprotobuf-mutator.md
@@ -2,9 +2,10 @@
*** note
**Note:** libprotobuf-mutator (LPM) is new to Chromium and does not (yet) have a
-long track record of success. Also, writing fuzzers with libprotobuf-mutator
-will probably require more effort than writing fuzzers with libFuzzer alone.
-If you run into problems, send an email to [fuzzing@chromium.org] for help.
+long track record of success. Also, writing grammar fuzzers with
+libprotobuf-mutator requires greater effort than writing fuzzers with libFuzzer
+alone. If you run into problems, send an email to [fuzzing@chromium.org] for
+help.
**Prerequisites:** Knowledge of [libFuzzer in Chromium] and basic understanding of
[Protocol Buffers].
@@ -21,28 +22,107 @@ manipulate protobufs. This allows libFuzzer's mutations to be more specific
to the format it is fuzzing and less arbitrary. Below are some good use cases
for libprotobuf-mutator:
-* Fuzzing targets that accept Protocol Buffers as input. Note that if you are
-fuzzing a target that accepts protobuffers, your protobuf definition *cannot*
-be optimized for `LITE_RUNTIME`, as is the case with almost all protobuf
-definitions in Chromium. To get around this you can copy the file without the
-optimization.
-* Fuzzing targets that accept other highly structured input. To do this you
-must write code that converts data from a protobuf-based format to a format the
-target accepts. url_parse_proto_fuzzer is a working example of this and is
-commented extensively. Readers may wish to consult its code, which is located in
-`testing/libfuzzer/fuzzers/url_parse_proto_fuzzer.cc`, and
-`testing/libfuzzer/fuzzers/url.proto`. Its build configuration can be found
-in `testing/libfuzzer/fuzzers/BUILD.gn`.
+* Fuzzing targets that accept Protocol Buffers as input. See the next section
+for how to do this.
+* Fuzzing targets that accept input defined by a grammar. To do this you
+must write code that converts data from a protobuf-based format that represents
+the grammar to a format the target accepts. url_parse_proto_fuzzer is a working
+example of this and is commented extensively. Readers may wish to consult its
+code, which is located in `testing/libfuzzer/fuzzers/url_parse_proto_fuzzer.cc`,
+and `testing/libfuzzer/fuzzers/url.proto`. Its build configuration can be found
+in `testing/libfuzzer/fuzzers/BUILD.gn`. We also provide a walkthrough on how to
+do this in the section after the next.
* Fuzzing targets that accept more than one argument (such as data and flags).
In this case, you can define each argument as its own field in your protobuf
definition.
-In the next two sections, we will discuss how to write and build fuzzers using
-libprotobuf-mutator. Interested readers may also want to look at [this] example
-of a libprotobuf-mutator fuzzer that is even more trivial than
+In the next section, we discuss building a fuzzer that targets code that accepts
+an already existing protobuf definition. In the section after that, we discuss
+how to write and build grammar-based fuzzers using libprotobuf-mutator.
+Interested readers may also want to look at [this] example of a
+libprotobuf-mutator fuzzer that is even more trivial than
url_parse_proto_fuzzer.
-## Write a libprotobuf-mutator fuzzer
+## Write a fuzzer for code that accepts protobufs
+This is almost as easy as writing a standard libFuzzer-based fuzzer. You can
+look at [override_lite_runtime_plugin_test_fuzzer] for an example of a working example of
+this (don't copy the line adding "//testing/libfuzzer:no_clusterfuzz" to
+additional_configs). Or you can follow this walkthrough:
+
+Start by creating a fuzz target. This is what the .cc file will look like:
+
+```c++
+// my_fuzzer.cc
+
+#include "third_party/libprotobuf-mutator/src/src/libfuzzer/libfuzzer_macro.h"
+
+// Assuming the .proto file is path/to/your/proto_file/my_proto.proto.
+#include "path/to/your/proto_file/my_proto.pb.h"
+
+// Silence libprotobuf_mutator's logging.
+protobuf_mutator::protobuf::LogSilencer log_silencer;
+
+DEFINE_BINARY_PROTO_FUZZER(
+ const my_proto::MyProtoMessage& my_proto_message) {
+ targeted_code(my_proto_message);
+}
+```
+
+The BUILD.gn definition for this target will be very similar to regular
+libFuzzer-based fuzzer_test. However it will also have libprotobuf-mutator in
+its deps. This is an example of what it will look like:
+
+```python
+// You must wrap the target in "use_libfuzzer" since trying to compile the
+// target without use_libfuzzer will fail (for reasons alluded to in the next
+// step), which the commit queue will try.
+if (use_libfuzzer) {
+ fuzzer_test("my_fuzzer") {
+ sources = [ "my_fuzzer.cc" ]
+ deps = [
+ // The proto library defining the message accepted by
+ // DEFINE_BINARY_PROTO_FUZZER().
+ ":my_proto",
+
+ "//third_party/libprotobuf-mutator",
+ ...
+ ]
+ }
+}
+```
+
+There's one more step however. Because Chromium doesn't want to ship to users
+the full protobuf library, all `.proto` files in Chromium that are used in
+production contain this line: `option optimize_for = LITE_RUNTIME` But this
+line is incompatible with libprotobuf-mutator. Thus, we need to modify the
+`proto_library` build target so that builds when fuzzing are compatible with
+libprotobuf-mutator. We do this by using a protobuf compiler plugin,
+`override_lite_runtime_plugin`. Here's what a `proto_library` build target that
+is configured properly looks like:
+
+```python
+proto_library("my_proto") {
+ ... // These lines (probably) dont need to be changed.
+
+ if (use_libfuzzer && current_toolchain == host_toolchain) {
+ generator_plugin_label =
+ "//third_party/libprotobuf-mutator:override_lite_runtime_plugin"
+ generator_plugin_suffix = ".pb"
+ # The plugin will generate cc, so don't ask for it to be done by protoc.
+ generate_cc = false
+ // If deps is already defined, change "=" to "+=".
+ deps = ["//third_party/libprotobuf-mutator:override_lite_runtime_plugin"]
+ }
+}
+```
+
+Note that this will not have any affects on the proto_library in production,
+since the plugin is only used when `use_libfuzzer == true`.
+And with that we have completed writing a libprotobuf-mutator fuzz target for
+Chromium code that accepts protobufs.
+
+
+## Write a grammar-based fuzzer with libprotobuf-mutator
Once you have in mind the code you want to fuzz and the format it accepts, you
are ready to start writing a libprotobuf-mutator fuzzer. Writing the fuzzer
@@ -50,7 +130,7 @@ will have three steps:
* Define the fuzzed format (not required for protobuf formats, unless the
original definition is optimized for `LITE_RUNTIME`).
-* Write the fuzzer target and conversion code (for non-protobuf formats).
+* Write the fuzz target and conversion code (for non-protobuf formats).
* Define the GN target
### Define the Fuzzed Format
@@ -71,10 +151,10 @@ See `testing/libfuzzer/fuzzers/url.proto` for an example of this in practice.
That example has extensive comments on URL syntax and how that influenced
the definition of the Url message.
-### Write the Fuzzer Target and Conversion Code
+### Write the Fuzz Target and Conversion Code
Create a new .cc and write a `DEFINE_BINARY_PROTO_FUZZER` function:
-```cpp
+```c++
// Needed since we use getenv().
#include <stdlib.h>
@@ -113,7 +193,7 @@ This is very similar to the same step in writing a standard libFuzzer fuzzer.
The only real differences are accepting protobufs rather than raw data and
converting them to the desired format. Conversion code can't really be explored
in this guide since it is format-specific. However, a good example of conversion
-code (and a fuzzer target) can be found in
+code (and a fuzz target) can be found in
`testing/libfuzzer/fuzzers/url_parse_proto_fuzzer.cc`. That example thoroughly
documents how it converts the Url protobuf message into a real URL string.
Note that `DEFINE_TEXT_PROTO_FUZZER` can be used during development instead of
@@ -142,7 +222,7 @@ import("//third_party/protobuf/proto_library.gni")
fuzzer_test("my_fuzzer") {
sources = [ "my_fuzzer.cc" ]
deps = [
- :my_format_proto
+ ":my_format_proto",
"//third_party/libprotobuf-mutator"
...
]
@@ -155,13 +235,7 @@ proto_library("my_format_proto") {
See `testing/libfuzzer/fuzzers/BUILD.gn` for an example of this in practice.
-### Wrapping Up
-Once you have written a fuzzer with libprotobuf-mutator, building and running
-it is pretty much the same as if the fuzzer were a standard libFuzzer-based
-fuzzer (with minor exceptions, like your seed corpus must be in protobuf
-format).
-
-### Tips
+### Tips For Grammar Based Fuzzers
* If you have messages that are defined recursively (eg: message `Foo` has a
field of type `Foo`), make sure to bound recursive calls to code converting
your message into native input. Otherwise you will (probably) end up with an
@@ -204,15 +278,22 @@ std::string Convert(MyFormat& my_format) {
}
```
-* Check out some of the [existing proto fuzzers], as not only will they be helpful
-examples, but it is possible that your format is already defined or partially
-defined by an existing proto definition.
-
* libprotobuf-mutator supports both proto2 and proto3 syntax. Be aware though
that it handles strings differently in each because of differences in the way
the proto library handles strings in each syntax (in short, proto3 strings must
actually be UTF-8 while in proto2 they do not). See [here] for more details.
+## Wrapping Up
+Once you have written a fuzzer with libprotobuf-mutator, building and running
+it is pretty much the same as if the fuzzer were a standard libFuzzer-based
+fuzzer (with minor exceptions, like your seed corpus must be in protobuf
+format).
+
+## General Tips
+* Check out some of the [existing proto fuzzers], as not only will they be helpful
+examples, but it is possible that your format is already defined or partially
+defined by an existing proto definition (if you are writing a grammar fuzzer).
+
[libfuzzer in Chromium]: getting_started.md
[Protocol Buffers]: https://developers.google.com/protocol-buffers/docs/cpptutorial
@@ -220,3 +301,4 @@ actually be UTF-8 while in proto2 they do not). See [here] for more details.
[this]: https://github.com/google/libprotobuf-mutator/tree/master/examples/libfuzzer/libfuzzer_example.cc
[existing proto fuzzers]: https://cs.chromium.org/search/?q=DEFINE_(BINARY_%7CTEXT_)?PROTO_FUZZER+-file:src/third_party/libprotobuf-mutator/src/src/libfuzzer/libfuzzer_macro.h&sq=package:chromium&type=cs
[here]: https://github.com/google/libprotobuf-mutator/blob/master/README.md#utf-8-strings
+[override_lite_runtime_plugin_test_fuzzer]: https://cs.chromium.org/chromium/src/third_party/libprotobuf-mutator/BUILD.gn?l=78
diff --git a/chromium/testing/libfuzzer/proto/BUILD.gn b/chromium/testing/libfuzzer/proto/BUILD.gn
index e4addeda6c7..861e8e655e5 100644
--- a/chromium/testing/libfuzzer/proto/BUILD.gn
+++ b/chromium/testing/libfuzzer/proto/BUILD.gn
@@ -35,3 +35,35 @@ source_set("json_proto_converter") {
":json_proto",
]
}
+
+if (!is_win) {
+ static_library("skia_image_filter_converter") {
+ sources = [
+ "skia_image_filter_proto_converter.cc",
+ "skia_image_filter_proto_converter.h",
+ ]
+ deps = [
+ ":skia_image_filter_proto",
+ "//base",
+ "//skia",
+ "//third_party/libprotobuf-mutator",
+ ]
+ defines = [ "AVOID_MISBEHAVIOR=1" ]
+
+ testonly = true
+
+ # Can't disable instrumentation because of container-overflow false
+ # positives.
+
+ # Assertion failures and integer oveflows in skia are uninteresting.
+ if (is_debug || is_ubsan) {
+ all_dependent_configs = [ "//testing/libfuzzer:no_clusterfuzz" ]
+ }
+ }
+
+ proto_library("skia_image_filter_proto") {
+ sources = [
+ "skia_image_filter.proto",
+ ]
+ }
+}
diff --git a/chromium/testing/libfuzzer/proto/skia_image_filter.proto b/chromium/testing/libfuzzer/proto/skia_image_filter.proto
new file mode 100644
index 00000000000..0cbbc6bfe58
--- /dev/null
+++ b/chromium/testing/libfuzzer/proto/skia_image_filter.proto
@@ -0,0 +1,1665 @@
+// Copyright 2018 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.
+
+// Contains the input used by filter_proto_converter to create a valid flattened
+// skia image filter. These messages were made using CreateProc and flatten
+// methods in mind so that a valid flattenable is produced. Many of the enums
+// here are copied from skia. Most of the messages are straightforward, but
+// there are a few things worth noting here. First is that many fields can be
+// written to the flattenable output as-is (such as the fields x,y and z in
+// Point3). And sometimes entire messages such as Point3 are written
+// as-is. These Message and fields can be what we call "autovisited". For fields
+// it means that they can be handled by the WriteFields method in
+// filter_proto_converter.cc. For messages it means they can be handled by
+// WriteFields and the generic Visit function (void Converter::Visit(const
+// Message& msg)) and do not need their own specific code. In this file, we will
+// put a comment "Autovisit:" before fields or messages that can be autovisited.
+// A second thing to know is the parent and child pattern we use here. In many
+// cases we need to specify one type of some kind of skia flattenable, like one
+// ColorFilter. To do this we create a message $CONCEPT + "Child" (eg
+// ColorFilterChild) that contains oneof these subtypes. In many cases these
+// subtypes (or children) will share things in common with each other. To avoid
+// re-implementing the same thing dozens of times, we give these children a
+// field called parent, that when Visited handles this common functionality. One
+// important exception to this general pattern is the LightParent message which
+// contains its own children, rather than the usual practice (which is the other
+// way around). That was done because the order in which the common
+// functionality must be done is different than in other cases (here it must
+// happen after the child functionality is handled, not before as in the other
+// cases). Finally, the last thing to know is because protobuf doesn't have a
+// good way to specify an array of a certain length so this functionality is
+// implemented by defining a message containing required fields representing
+// each element in the array (eg OverdrawColorFilter).
+
+syntax = "proto2";
+
+package skia_image_filter_proto_converter;
+
+// Used for testcases.
+message Input {
+ required ImageFilterChild image_filter = 1;
+ required uint64 rng_seed = 2;
+}
+
+// No content when flattened.
+message LumaColorFilter {}
+
+message OverdrawColorFilter {
+ // Autovisit:
+ required uint32 val1 = 1;
+ required uint32 val2 = 2;
+ required uint32 val3 = 3;
+ required uint32 val4 = 4;
+ required uint32 val5 = 5;
+ required uint32 val6 = 6;
+}
+
+message ColorFilterChild {
+ oneof children {
+ ModeColorFilter mode_color_filter = 1;
+ ColorMatrixFilterRowMajor255 color_matrix_filter_row_major_255 = 2;
+ ComposeColorFilter compose_color_filter = 3;
+ SRGBGammaColorFilter srgb_gamma_color_filter = 4;
+ HighContrast_Filter high_contrast__filter = 5;
+ LumaColorFilter luma_color_filter = 6;
+ OverdrawColorFilter overdraw_color_filter = 7;
+ Table_ColorFilter table__color_filter = 8;
+ ToSRGBColorFilter to_srgb_color_filter = 9;
+ }
+}
+
+message TransferFn {
+ required Named named = 1;
+ required float a = 2;
+ required float b = 3;
+ required float c = 4;
+ required float d = 5;
+ required float e = 6;
+ required float f = 7;
+ required float g = 8;
+ required ThreeByFour three_by_four = 9;
+}
+
+enum Named {
+ kSRGB_Named = 0;
+ kAdobeRGB_Named = 1;
+ kSRGBLinear_Named = 2;
+ kSRGB_NonLinearBlending_Named = 3;
+}
+
+message ColorSpace_XYZ {
+ enum GammaNamed {
+ kLinear_SkGammaNamed = 0;
+ kSRGB_SkGammaNamed = 1;
+ k2Dot2Curve_SkGammaNamed = 2;
+ }
+ required GammaNamed gamma_named = 1;
+ required ThreeByFour three_by_four = 2;
+}
+
+message ColorSpaceNamed {
+ enum ColorSpaceNamedEnum {
+ kAdobeRGB_Named = 1;
+ kSRGBLinear_Named = 2;
+ }
+ required ColorSpaceNamedEnum named = 1;
+ required GammaNamed gamma_named = 2;
+}
+
+message ColorSpaceChild {
+ oneof data {
+ ICC icc = 1;
+ TransferFn transfer_fn = 2;
+ ColorSpace_XYZ color_space__xyz = 3;
+ }
+ required ColorSpaceNamed named = 4;
+}
+
+message ToSRGBColorFilter {
+ required ColorSpaceChild color_space = 1;
+}
+
+message ColorTable {
+ required float val1 = 1;
+ required float val2 = 2;
+ required float val3 = 3;
+ required float val4 = 4;
+ required float val5 = 5;
+ required float val6 = 6;
+ required float val7 = 7;
+ required float val8 = 8;
+ required float val9 = 9;
+ required float val10 = 10;
+ required float val11 = 11;
+ required float val12 = 12;
+ required float val13 = 13;
+ required float val14 = 14;
+ required float val15 = 15;
+ required float val16 = 16;
+ required float val17 = 17;
+ required float val18 = 18;
+ required float val19 = 19;
+ required float val20 = 20;
+ required float val21 = 21;
+ required float val22 = 22;
+ required float val23 = 23;
+ required float val24 = 24;
+ required float val25 = 25;
+ required float val26 = 26;
+ required float val27 = 27;
+ required float val28 = 28;
+ required float val29 = 29;
+ required float val30 = 30;
+ required float val31 = 31;
+ required float val32 = 32;
+ required float val33 = 33;
+ required float val34 = 34;
+ required float val35 = 35;
+ required float val36 = 36;
+ required float val37 = 37;
+ required float val38 = 38;
+ required float val39 = 39;
+ required float val40 = 40;
+ required float val41 = 41;
+ required float val42 = 42;
+ required float val43 = 43;
+ required float val44 = 44;
+ required float val45 = 45;
+ required float val46 = 46;
+ required float val47 = 47;
+ required float val48 = 48;
+ required float val49 = 49;
+ required float val50 = 50;
+ required float val51 = 51;
+ required float val52 = 52;
+ required float val53 = 53;
+ required float val54 = 54;
+ required float val55 = 55;
+ required float val56 = 56;
+ required float val57 = 57;
+ required float val58 = 58;
+ required float val59 = 59;
+ required float val60 = 60;
+ required float val61 = 61;
+ required float val62 = 62;
+ required float val63 = 63;
+ required float val64 = 64;
+}
+
+message Table_ColorFilter {
+ optional ColorTable table_a = 1;
+ optional ColorTable table_r = 2;
+ optional ColorTable table_g = 3;
+ optional ColorTable table_b = 4;
+}
+
+// See SkHighContrastFilter.cpp
+message HighContrast_Filter {
+ enum InvertStyle {
+ kNoInvert = 0;
+ kInvertBrightness = 1;
+ kInvertLightness = 2;
+ }
+
+ required bool grayscale = 1;
+ required InvertStyle invert_style = 2;
+ // Autovisit up to here
+ required float contrast = 3;
+}
+
+// Autovisit:
+message SRGBGammaColorFilter {
+ enum Direction {
+ kLinearToSRGB = 0;
+ kSRGBToLinear = 1;
+ }
+ required Direction direction = 1;
+}
+
+message ComposeColorFilter {
+ required ColorFilterChild outer = 1;
+ required ColorFilterChild inner = 2;
+}
+
+message ColorFilterMatrix {
+ // Autovisit:
+ required float val1 = 1;
+ required float val2 = 2;
+ required float val3 = 3;
+ required float val4 = 4;
+ required float val5 = 5;
+ required float val6 = 6;
+ required float val7 = 7;
+ required float val8 = 8;
+ required float val9 = 9;
+ required float val10 = 10;
+ required float val11 = 11;
+ required float val12 = 12;
+ required float val13 = 13;
+ required float val14 = 14;
+ required float val15 = 15;
+ required float val16 = 16;
+ required float val17 = 17;
+ required float val18 = 18;
+ required float val19 = 19;
+ required float val20 = 20;
+}
+
+// See SkColorMatrixFilterRowMajor255.cpp (https://goo.gl/qwF8DK)
+message ColorMatrixFilterRowMajor255 {
+ // Autovisit:
+ required ColorFilterMatrix color_filter_matrix = 1;
+}
+
+message ModeColorFilter {
+ required uint32 color = 1;
+ required BlendMode mode = 2;
+}
+
+message Rectangle {
+ required float left = 1;
+ required float top = 2;
+ required float right = 3;
+ required float bottom = 4;
+}
+
+message IRect {
+ required int32 left = 1;
+ required int32 top = 2;
+ required int32 right = 3;
+ required int32 bottom = 4;
+}
+
+message CropRectangle {
+ required Rectangle rectangle = 1;
+ required uint32 flags = 2;
+}
+
+message PictureInfo {
+ // TODO(metzman): Figure out how to keep this up to date.
+ enum Version {
+ V0 = 56;
+ V1 = 57;
+ V2 = 58;
+ V3 = 59;
+ kRemoveHeaderFlags_Version = 60;
+ V4 = 61;
+ }
+
+ required Version version = 1;
+ required Rectangle rectangle = 2;
+ required uint32 flags = 3;
+}
+
+message PictureData {
+ // SkPictureData.cpp (https://goo.gl/hDnKjz)
+ repeated PictureTagChild tags = 1;
+ required ReaderPictureTag reader_tag = 2;
+}
+
+enum BlendMode {
+ kClear = 0;
+ kSrc = 1;
+ kDst = 2;
+ // TODO(metzman): Uncomment this when bug 786133 is fixed.
+ // kSrcOver = 3;
+ kDstOver = 4;
+ kSrcIn = 5;
+ kDstIn = 6;
+ kSrcOut = 7;
+ kDstOut = 8;
+ kSrcATop = 9;
+ kDstATop = 10;
+ kXor = 11;
+ kPlus = 12;
+ kModulate = 13;
+ kScreenAndLastCoeffMode = 14;
+ kOverlay = 15;
+ kDarken = 16;
+ kLighten = 17;
+ kColorDodge = 18;
+ kColorBurn = 19;
+ kHardLight = 20;
+ kSoftLight = 21;
+ kDifference = 22;
+ kExclusion = 23;
+ kLastSeparableModeAndMultiply = 24;
+ kHue = 25;
+ kSaturation = 26;
+ kColor = 27;
+ kLuminosity = 28;
+}
+
+message Paint {
+ required float text_size = 1;
+ required float text_scale_x = 2;
+ required float text_skew_x = 3;
+ required float stroke_width = 4;
+ required float stroke_miter = 5;
+ required uint32 color = 6;
+ // Autovisit up to here
+
+ enum TextEncoding {
+ kUTF8_TextEncoding = 0;
+ kUTF16_TextEncoding = 1;
+ kUTF32_TextEncoding = 2;
+ kGlyphID_TextEncoding = 3;
+ }
+
+ enum Style {
+ kFill_Style = 0;
+ kStroke_Style = 1;
+ kStrokeAndFill_Style = 2;
+ }
+
+ enum StrokeCap {
+ kButt_Cap = 0;
+ kRound_Cap = 1;
+ kSquare_Cap = 2;
+ }
+
+ enum StrokeJoin {
+ kMiter_Join = 0;
+ kRound_Join = 1;
+ kBevel_Join = 2;
+ }
+
+ required StrokeCap stroke_cap = 7;
+ required StrokeJoin stroke_join = 8;
+ required Style style = 9;
+ required TextEncoding text_encoding = 10;
+ required BlendMode blend_mode = 11;
+ optional PaintEffects effects = 12;
+
+ enum PaintFlags {
+ kAntiAlias_Flag = 0x01;
+ kDither_Flag = 0x04;
+ kFakeBoldText_Flag = 0x20;
+ kLinearText_Flag = 0x40;
+ kSubpixelText_Flag = 0x80;
+ kDevKernText_Flag = 0x100;
+ kLCDRenderText_Flag = 0x200;
+ kEmbeddedBitmapText_Flag = 0x400;
+ kAutoHinting_Flag = 0x800;
+ kVerticalText_Flag = 0x1000;
+ kGenA8FromLCD_Flag = 0x2000;
+ kAllFlags = 0xFFFF;
+ }
+
+ enum Hinting {
+ kNo_Hinting = 0;
+ kSlight_Hinting = 1;
+ kNormal_Hinting = 2;
+ kFull_Hinting = 3;
+ }
+
+ enum Align {
+ kLeft_Align = 0;
+ kCenter_Align = 1;
+ kRight_Align = 2;
+ }
+
+ // Stuff that gets packed into flags.
+ required PaintFlags flags = 13;
+ required Hinting hinting = 14;
+ required Align align = 15;
+ required FilterQuality filter_quality = 16;
+}
+
+message Point {
+ required float x = 1;
+ required float y = 2;
+}
+
+message PathEffectChild {
+ oneof children {
+ PairPathEffect pair_path_effect = 1;
+ Path2DPathEffect path_2d_path_effect = 2;
+ Line2DPathEffect line_2d_path_effect = 3;
+ CornerPathEffect corner_path_effect = 4;
+ DashImpl dash_impl = 5;
+ DiscretePathEffect discrete_path_effect = 6;
+ Path1DPathEffect path_1d_path_effect = 7;
+ }
+}
+
+// Autovisit:
+message CornerPathEffect {
+ // 0 is a very bad choice for radius, so make field optional with a default of
+ // 1.
+ optional float radius = 1 [default = 1];
+}
+
+message Path2DPathEffect {
+ required Matrix matrix = 1;
+ required Path path = 2;
+}
+
+message Line2DPathEffect {
+ required Matrix matrix = 1;
+ required float width = 2;
+}
+
+message DashImpl {
+ required float phase = 1;
+ required float interval_1 = 2;
+ required float interval_2 = 3;
+ repeated float intervals = 4;
+}
+
+// Autovisit:
+message DiscretePathEffect {
+ required float seg_length = 1;
+ required float perterb = 2;
+ required uint32 seed_assist = 3;
+}
+
+message Path1DPathEffect {
+ enum Style {
+ kTranslate_Style = 0;
+ kRotate_Style = 1;
+ kMorph_Style = 2;
+ }
+ required float advance = 1;
+ required Path path = 2;
+ required float initial_offset = 3;
+ required Style style = 4;
+}
+
+message Path {
+ enum Convexity {
+ kUnknown_Convexity = 0;
+ kConvex_Convexity = 1;
+ kConcave_Convexity = 2;
+ }
+ enum FirstDirection {
+ kCW_FirstDirection = 0;
+ kCCW_FirstDirection = 1;
+ kUnknown_FirstDirection = 2;
+ }
+ enum SerializationVersion {
+ kPathPrivFirstDirection_Version = 1;
+ kPathPrivLastMoveToIndex_Version = 2;
+ kPathPrivTypeEnumVersion = 3;
+ }
+
+ required Convexity convexity = 1;
+ required uint32 fill_type = 2; // Should be 8 bytes
+ required FirstDirection first_direction = 3;
+ required bool is_volatile = 4;
+ required SerializationVersion serialized_version = 5;
+ required int32 last_move_to_index = 6;
+ required PathRef path_ref = 7;
+}
+
+message ValidVerb {
+ enum Value {
+ kMove_Verb = 0;
+ kLine_Verb = 1;
+ kQuad_Verb = 2;
+ kConic_Verb = 3;
+ kCubic_Verb = 4;
+ kClose_Verb = 5;
+ // We don't actually want kDone_Verb.
+ }
+ required Value value = 1;
+ required Point point1 = 2;
+ required Point point2 = 3;
+ required Point point3 = 4;
+ required float conic_weight = 5;
+}
+
+message PathRef {
+ repeated ValidVerb verbs = 1;
+ required bool is_finite = 2;
+ required uint32 segment_mask = 3;
+ required ValidVerb first_verb = 4;
+}
+
+message PairPathEffect {
+ enum Type {
+ SUM = 1;
+ COMPOSE = 2;
+ }
+ required Type type = 1;
+ required PathEffectChild path_effect_1 = 2;
+ required PathEffectChild path_effect_2 = 3;
+}
+
+message ShaderChild {
+ oneof children {
+ ColorShader color_shader = 1;
+ Color4Shader color_4_shader = 2;
+ ColorFilterShader color_filter_shader = 3;
+ ComposeShader compose_shader = 4;
+ EmptyShader empty_shader = 5;
+ ImageShader image_shader = 6;
+ PictureShader picture_shader = 7;
+ PerlinNoiseShaderImpl perlin_noise_shader_impl = 8;
+ LocalMatrixShader local_matrix_shader = 9;
+ LinearGradient linear_gradient = 10;
+ RadialGradient radial_gradient = 11;
+ SweepGradient sweep_gradient = 12;
+ TwoPointConicalGradient two_point_conical_gradient = 13;
+ }
+}
+
+message TwoPointConicalGradient {
+ required GradientParent parent = 1;
+ // Autovisit:
+ required Point center1 = 2;
+ required Point center2 = 3;
+ required float radius1 = 4;
+ required float radius2 = 5;
+}
+
+message SweepGradient {
+ required GradientParent parent = 1;
+ // Autovisit:
+ required Point center = 2;
+ // TODO(metzman): Handle case when buffer.fVersion >=
+ // kTileInfoInSweepGradient_Version or fVersion != 0.
+ required float bias = 3;
+ required float scale = 4;
+}
+
+message RadialGradient {
+ required GradientParent parent = 1;
+ // Autovisit:
+ required Point center = 2;
+ required float radius = 3;
+}
+
+message Color4f {
+ // Autovisit:
+ required float r = 1;
+ required float g = 2;
+ required float b = 3;
+ required float a = 4;
+}
+
+// Note that this cannot be named "Descriptor" since that name is used by
+// protobuf's reflection methods
+message GradientDescriptor {
+ optional ColorSpaceChild color_space = 1;
+ optional float pos = 2;
+ optional Matrix local_matrix = 3;
+ required TileMode tile_mode = 4;
+ required uint32 grad_flags = 5; // <= UINT8_MAX
+ repeated Color4f colors = 6;
+}
+
+// Contained by children
+message GradientParent {
+ required GradientDescriptor gradient_descriptor = 1;
+}
+
+message LinearGradient {
+ required GradientParent parent = 1;
+ // Autovisit:
+ required Point start = 2;
+ required Point end = 3;
+}
+
+message LocalMatrixShader {
+ required Matrix matrix = 1;
+ required ShaderChild proxy_shader = 2;
+}
+
+// Autovisit:
+message PerlinNoiseShaderImpl {
+ enum Type {
+ kFractalNoise_Type = 0;
+ kTurbulence_Type = 1;
+ kImprovedNoise_Type = 2;
+ }
+ required Type type = 1;
+ required float base_frequency_x = 2;
+ required float base_frequency_y = 3;
+ required int32 octaves = 4;
+ required float seed = 5;
+ required int32 height = 6;
+ required int32 width = 7;
+}
+
+message PictureShader {
+ required Matrix matrix = 1;
+ // Autovisit:
+ required TileMode tmx = 2;
+ required TileMode tmy = 3;
+ required Rectangle rect = 4;
+}
+
+enum TileMode {
+ kClamp_TileMode = 0;
+ kRepeat_TileMode = 1;
+ kMirror_TileMode = 2;
+}
+
+// Autovisit:
+message ImageShader {
+ required TileMode tile_mode_x = 1;
+ required TileMode tile_mode_y = 2;
+ required Matrix matrix = 3;
+ required Image image = 4;
+}
+
+message ImageInfo {
+ enum AlphaType {
+ kUnknown_SkAlphaType = 0;
+ kOpaque_SkAlphaType = 1;
+ kPremul_SkAlphaType = 2;
+ kUnpremul_SkAlphaType = 3;
+ }
+
+ enum ColorType {
+ kUnknown_Stored_SkColorType = 0;
+ kAlpha_8_Stored_SkColorType = 1;
+ kRGB_565_Stored_SkColorType = 2;
+ kARGB_4444_Stored_SkColorType = 3;
+ kRGBA_8888_Stored_SkColorType = 4;
+ kBGRA_8888_Stored_SkColorType = 5;
+ kIndex_8_Stored_SkColorType_DEPRECATED = 6;
+ kGray_8_Stored_SkColorType = 7;
+ kRGBA_F16_Stored_SkColorType = 8;
+ }
+
+ required int32 width = 1;
+ required int32 height = 2;
+ required AlphaType alpha_type = 3;
+ required ColorType color_type = 4;
+ required ColorSpaceChild color_space = 5;
+}
+
+message ImageData {
+ repeated uint32 data = 1;
+}
+
+// TODO(metzman): Finish implementing using ImageInfo.
+message Image {
+ // Must be non-negative.
+ required int32 width = 1;
+ required int32 height = 2;
+ required ImageData data = 3;
+ // Must be nonnegative.
+ required int32 origin_x = 4;
+ required int32 origin_y = 5;
+}
+
+// Autovisit:
+message EmptyShader {}
+
+message ComposeShader {
+ required ShaderChild dst = 1;
+ required ShaderChild src = 2;
+ // Autovisit:
+ required BlendMode mode = 3;
+ required float lerp_t = 4;
+}
+
+message ColorFilterShader {
+ required ShaderChild shader = 1;
+ required ColorFilterChild filter = 2;
+}
+
+message Color4Shader {
+ required uint32 color = 1;
+}
+
+// Autovisit:
+message ColorShader {
+ required uint32 color = 1;
+}
+
+message LooperChild {
+ required LayerDrawLooper layer_draw_looper = 1;
+}
+
+message LayerDrawLooper {
+ repeated LayerInfo layer_infos = 1;
+}
+
+message LayerInfo {
+ required int32 paint_bits = 1;
+ required BlendMode color_mode = 2;
+ required Point point = 3;
+ required bool post_translate = 4;
+ // Autovisit up to here
+ required Paint paint = 5;
+}
+
+message MaskFilterChild {
+ oneof children {
+ BlurMaskFilter blur_mask_filter_impl = 1;
+ EmbossMaskFilter emboss_mask_filter = 2;
+ RRectsGaussianEdgeMaskFilterImpl r_rects_gaussian_edge_mask_filter_impl = 3;
+ }
+}
+
+message RRectsGaussianEdgeMaskFilterImpl {
+ required Rectangle rect_1 = 1;
+ required float x_rad_1 = 2;
+ required float y_rad_1 = 3;
+ required Rectangle rect_2 = 4;
+ required float x_rad_2 = 5;
+ required float y_rad_2 = 6;
+ required float radius = 7;
+}
+
+message EmbossMaskFilterLight {
+ required float direction_x = 1;
+ required float direction_y = 2;
+ required float direction_z = 3;
+ required uint32 ambient = 4;
+ required uint32 specular = 5;
+}
+
+message EmbossMaskFilter {
+ required EmbossMaskFilterLight light = 1;
+ required float blur_sigma = 2;
+}
+
+enum BlurStyle {
+ kNormal_SkBlurStyle = 0;
+ kSolid_SkBlurStyle = 1;
+ kOuter_SkBlurStyle = 2;
+ kInner_SkBlurStyle = 3;
+}
+
+// Copied from https://goo.gl/Yy5Euw
+enum BlurFlags {
+ kNone_BlurFlag = 0x00;
+ kIgnoreTransform_BlurFlag = 0x01;
+ kHighQuality_BlurFlag = 0x02;
+ kAll_BlurFlag = 0x03;
+}
+
+message BlurMaskFilter {
+ required float sigma = 1;
+ required BlurStyle style = 2;
+ required BlurFlags flags = 3;
+ required Rectangle occluder = 4;
+}
+
+message PaintEffects {
+ optional PathEffectChild path_effect = 1;
+ optional ShaderChild shader = 2;
+ optional MaskFilterChild mask_filter = 3;
+ optional ColorFilterChild color_filter = 4;
+ optional LooperChild looper = 5;
+ optional ImageFilterChild image_filter = 6;
+}
+
+message RecordingData {
+ repeated Paint paints = 1;
+}
+
+message PaintImageFilter {
+ required ImageFilterParent image_filter_parent = 1;
+ required Paint paint = 2;
+}
+
+message PictureTagChild {
+ oneof children {
+ PaintPictureTag paint = 1;
+ PathPictureTag path = 2;
+ Image image = 3;
+ Vertices vertices = 4;
+ TextBlob text_blob = 5;
+ }
+}
+
+message TextBlob {
+ required Rectangle bounds = 1;
+ enum GlyphPositioning {
+ kDefault_Positioning = 0;
+ kHorizontal_Positioning = 1;
+ kFull_Positioning = 2;
+ }
+ required GlyphPositioning glyph_positioning = 2;
+ required bool extended = 3;
+ required Point offset = 4;
+ required Paint paint = 5;
+ required GlyphAndPosAndCluster glyph_pos_cluster_1 = 6;
+ required GlyphAndPosAndCluster glyph_pos_cluster_2 = 7;
+ repeated GlyphAndPosAndCluster glyph_pos_clusters = 8;
+ repeated uint32 text = 9;
+}
+
+message GlyphAndPosAndCluster {
+ required uint32 glyph = 1;
+ required float position_1 = 2;
+ required float position_2 = 3;
+ required uint32 cluster = 4;
+}
+
+message Vertices {
+ enum VertexMode {
+ kTriangles_VertexMode = 0;
+ kTriangleStrip_VertexMode = 1;
+ kTriangleFan_VertexMode = 2;
+ }
+ required VertexMode mode = 1;
+ required bool has_texs = 2;
+ required bool has_colors = 3;
+ repeated VertexTexColor vertex_text_colors = 4;
+ repeated uint32 indices = 5;
+}
+
+message VertexTexColor {
+ required Point vertex = 1;
+ required Point tex = 2;
+ required Point color = 3;
+}
+
+message ReaderPictureTag {
+ required uint32 first_bytes = 1;
+ repeated uint32 later_bytes = 2;
+}
+
+message PaintPictureTag {
+ required Paint paint = 1;
+}
+
+message PathPictureTag {
+ required Path path = 1;
+}
+
+message Picture {
+ required PictureInfo info = 1;
+ optional PictureData data = 2;
+}
+
+// Copied with comments from skia.
+// Enums in C++ that don't have set values start at 0.
+enum FilterQuality {
+ // fastest but lowest quality, typically nearest-neighbor
+ kNone_SkFilterQuality = 0;
+ kLow_SkFilterQuality = 1; // typically bilerp
+ kMedium_SkFilterQuality = 2; // typically bilerp + mipmaps for down-scaling
+ // slowest but highest quality, typically bicubic or better
+ kHigh_SkFilterQuality = 3;
+}
+
+message PictureImageFilter {
+ enum PictureResolution {
+ kDeviceSpace_PictureResolution = 0;
+ kLocalSpace_PictureResolution = 1;
+ }
+
+ optional Picture picture = 1;
+ required Rectangle crop_rectangle = 2;
+ required PictureResolution resolution = 3;
+}
+
+message Matrix {
+ required float val1 = 1;
+ required float val2 = 2;
+ required float val3 = 3;
+ required float val4 = 4;
+ required float val5 = 5;
+ required float val6 = 6;
+ required float val7 = 7;
+ required float val8 = 8;
+ required float val9 = 9;
+}
+
+message MatrixImageFilter {
+ required ImageFilterParent image_filter_parent = 1;
+ required Matrix transform = 2;
+ required FilterQuality filter_quality = 3;
+}
+
+message ImageFilterChild {
+ oneof children {
+ PaintImageFilter paint_image_filter = 1;
+ MatrixImageFilter matrix_image_filter = 2;
+ SpecularLightingImageFilter specular_lighting_image_filter = 3;
+ ArithmeticImageFilter arithmetic_image_filter = 4;
+ AlphaThresholdFilterImpl alpha_threshold_filter_impl = 5;
+ BlurImageFilterImpl blur_image_filter_impl = 6;
+ ColorFilterImageFilter color_filter_image_filter = 7;
+ ComposeImageFilter compose_image_filter = 8;
+ DisplacementMapEffect displacement_map_effect = 9;
+ DropShadowImageFilter drop_shadow_image_filter = 10;
+ LocalMatrixImageFilter local_matrix_image_filter = 11;
+ MagnifierImageFilter magnifier_image_filter = 13;
+ MatrixConvolutionImageFilter matrix_convolution_image_filter = 14;
+ MergeImageFilter merge_image_filter = 15;
+ DilateImageFilter dilate_image_filter = 16;
+ ErodeImageFilter erode_image_filter = 17;
+ OffsetImageFilter offset_image_filter = 18;
+ PictureImageFilter picture_image_filter = 19;
+ TileImageFilter tile_image_filter = 20;
+ XfermodeImageFilter_Base xfermode_image_filter__base = 21;
+ XfermodeImageFilter xfermode_image_filter = 22;
+ DiffuseLightingImageFilter diffuse_lighting_image_filter = 23;
+ ImageSource image_source = 24;
+ }
+}
+
+message DiffuseLightingImageFilter {
+ required ImageFilterParent parent = 1;
+ required LightParent light = 2;
+ required float surface_scale = 3;
+ required float kd = 4;
+}
+
+message XfermodeImageFilter {
+ required ImageFilterParent parent = 1;
+ required BlendMode mode = 2;
+}
+
+message XfermodeImageFilter_Base {
+ required ImageFilterParent parent = 1;
+ required BlendMode mode = 2;
+}
+
+message TileImageFilter {
+ required ImageFilterParent parent = 1;
+ required Rectangle src = 2;
+ required Rectangle dst = 3;
+}
+
+message OffsetImageFilter {
+ required ImageFilterParent parent = 1;
+ required Point offset = 2;
+}
+
+message ErodeImageFilter {
+ required ImageFilterParent parent = 1;
+ required int32 width = 2;
+ required int32 height = 3;
+}
+
+message DilateImageFilter {
+ required ImageFilterParent parent = 1;
+ required int32 width = 2;
+ required int32 height = 3;
+}
+
+message MergeImageFilter {
+ required ImageFilterParent parent = 1;
+}
+
+message MatrixConvolutionImageFilter {
+ required ImageFilterParent parent = 1;
+ required int32 width = 2;
+ required int32 height = 3;
+ // Since we can't specify a field of repeated bytes that is width*height, use
+ // a kernel_seed to seed a RNG to get the number of bytes we need.
+ required int64 kernel_seed = 4;
+ required float gain = 5;
+ required float bias = 6;
+ required int32 offset_x = 7;
+ required int32 offset_y = 8;
+ required TileMode tile_mode = 9;
+ required bool convolve_alpha = 10;
+}
+
+message MagnifierImageFilter {
+ required ImageFilterParent parent = 1;
+ required Rectangle src = 2;
+ required float inset = 3;
+}
+
+message LocalMatrixImageFilter {
+ required ImageFilterParent parent = 1;
+ required Matrix matrix = 2;
+}
+
+message ImageSource {
+ required FilterQuality filter_quality = 1;
+ required Rectangle src = 2;
+ required Rectangle dst = 3;
+ // / Autovisit
+ required Image image = 4;
+}
+
+message DropShadowImageFilter {
+ enum ShadowMode {
+ kDrawShadowAndForeground_ShadowMode = 0;
+ kDrawShadowOnly_ShadowMode = 1;
+ kDrawShadowOnly_ShadowMod = 2;
+ }
+ required ImageFilterParent parent = 1;
+ // Autovisit:
+ required float dx = 2;
+ required float dy = 3;
+ required float sigma_x = 4;
+ required float sigma_y = 5;
+ required uint32 color = 6;
+ required ShadowMode shadow_mode = 7;
+}
+
+message DisplacementMapEffect {
+ enum ChannelSelectorType {
+ kUnknown_ChannelSelectorType = 0;
+ kR_ChannelSelectorType = 1;
+ kG_ChannelSelectorType = 2;
+ kB_ChannelSelectorType = 3;
+ kA_ChannelSelectorTyp = 4;
+ }
+
+ required ImageFilterParent parent = 1;
+ // Autovisit:
+ required ChannelSelectorType xsel = 2;
+ required ChannelSelectorType ysel = 3;
+ required float scale = 4;
+}
+
+message ComposeImageFilter {
+ required ImageFilterParent parent = 1;
+}
+
+message ColorFilterImageFilter {
+ required ImageFilterParent parent = 1;
+ required ColorFilterChild color_filter = 2;
+}
+
+message BlurImageFilterImpl {
+ required ImageFilterParent parent = 1;
+ required float sigma_x = 2;
+ required float sigma_y = 3;
+ required TileMode mode = 4;
+}
+
+message AlphaThresholdFilterImpl {
+ required ImageFilterParent parent = 1;
+ required float inner = 2;
+ required float outer = 3;
+ required Region rgn = 4;
+}
+
+message Region {
+ required IRect bounds = 1;
+ // TODO(metzman): Properly implement complex regions.
+}
+
+message RegionComplex {
+ required int32 y_span_count = 1;
+ required int32 interval_count = 2;
+ repeated int32 run_seed = 3;
+}
+
+message ArithmeticImageFilter {
+ required ImageFilterParent parent = 1;
+
+ // Ignored see SkXfermodeImageFilter_Base::LegacyArithmeticCreateProc.
+ // Converter will write a mode even without a corresponding field.
+ // required BlendMode mode = 2;
+
+ // Autovisit:
+ required float val1 = 2;
+ required float val2 = 3;
+ required float val3 = 4;
+ required float val4 = 5;
+ required bool enforce_pm_color = 6;
+}
+
+// Contained by children
+message ImageFilterParent {
+ required ImageFilterChild default_input = 1;
+ repeated ImageFilterChild inputs = 2;
+ required CropRectangle crop_rectangle = 3;
+}
+
+// Autovisit:
+message Point3 {
+ required float x = 1;
+ required float y = 2;
+ required float z = 3;
+}
+
+// Contains children
+message LightParent {
+ required Point3 color = 1;
+ required LightChild light_child = 2;
+}
+
+// Autovisit:
+message DistantLight {
+ required Point3 direction = 1;
+}
+
+// Autovisit:
+message PointLight {
+ required Point3 location = 1;
+}
+
+// See SkLightingImageFilter.cpp
+// Autovisit:
+message SpotLight {
+ required Point3 location = 1;
+ required Point3 target = 2;
+ required float specular_exponent = 3;
+ required float cos_outer_cone_angle = 4;
+ required float cos_inner_cone_angle = 5;
+ required float cone_scale = 6;
+ required Point3 s = 7;
+}
+
+message LightChild {
+ oneof children {
+ PointLight point_light = 1;
+ SpotLight spot_light = 2;
+ }
+ required DistantLight distant_light = 3;
+}
+
+message SpecularLightingImageFilter {
+ required ImageFilterParent image_filter_parent = 1;
+ required LightParent light = 2;
+ required float surface_scale = 3;
+ required float ks = 4;
+ required float shininess = 5;
+}
+
+enum GammaNamed {
+ kLinear_SkGammaNamed = 0;
+ kSRGB_SkGammaNamed = 1;
+ k2Dot2Curve_SkGammaNamed = 2;
+ kNonStandard_SkGammaNamed = 3;
+}
+
+message ThreeByFour {
+ required float val1 = 1;
+ required float val2 = 2;
+ required float val3 = 3;
+ required float val4 = 4;
+ required float val5 = 5;
+ required float val6 = 6;
+ required float val7 = 7;
+ required float val8 = 8;
+ required float val9 = 9;
+ required float val10 = 10;
+ required float val11 = 11;
+ required float val12 = 12;
+}
+
+enum ICCTag {
+ kTAG_rXYZ = 0;
+ kTAG_gXYZ = 1;
+ kTAG_bXYZ = 2;
+ kTAG_rTRC = 3;
+ kTAG_gTRC = 4;
+ kTAG_bTRC = 5;
+ kTAG_kTRC = 6;
+ kTAG_A2B0 = 7;
+ kTAG_CurveType = 8;
+ kTAG_ParaCurveType = 9;
+ kTAG_TextType = 10;
+}
+
+// This contains a lot of commented out fields since they are in the actual
+// struct this message represents, but are unused. We don't define them and
+// WriteIgnoredFields is used to write them rather than wasting LPM's time
+// setting them. However, we leave them here commented out for reference, and
+// we don't use their numbers in case we use them in the future.
+message ICC {
+ enum Profile {
+ Display_Profile = 0;
+ Input_Profile = 1;
+ Output_Profile = 2;
+ ColorSpace_Profile = 3;
+ }
+
+ enum InputColorSpace {
+ RGB_ColorSpace = 0;
+ CMYK_ColorSpace = 1;
+ Gray_ColorSpace = 2;
+ }
+ enum PCS {
+ kXYZ_PCSSpace = 0;
+ kLAB_PCSSpace = 1;
+ }
+ enum RenderingIntent {
+ kPerceptual = 0;
+ kRelative = 1;
+ kSaturation = 2;
+ kAbsolute = 3;
+ }
+ required Named named = 34;
+ // required uint32 size = 1; // Always 132.
+ // required uint32 cmm_type_ignored = 2;
+ required uint32 version = 3;
+ required Profile profile_class = 4;
+ required InputColorSpace input_color_space = 5;
+ required PCS pcs = 6;
+ // required uint32 datetime_ignored_1 = 7;
+ // required uint32 datetime_ignored_2 = 8;
+ // required uint32 datetime_ignored_3 = 9;
+ // Always SkSetFourByteTag('a', 'c', 's', 'p')
+ // required uint32 signature = 10;
+ // required uint32 platform_target_ignored = 11;
+ // required uint32 flags_ignored = 12;
+ // required uint32 device_manufacturer_ignored = 13;
+ // required uint32 device_model_ignored = 14;
+ // required uint32 device_attributes_ignored_1 = 15;
+ // required uint32 device_attributes_ignored_2 = 16;
+ required RenderingIntent rendering_intent = 17;
+ required int32 illuminant_x = 18;
+ required int32 illuminant_y = 19;
+ required int32 illuminant_z = 20;
+
+ // required uint32 creator_ignored = 21;
+ // required uint32 profileid_ignored_1 = 22;
+ // required uint32 profileid_ignored_2 = 23;
+ // required uint32 profileid_ignored_3 = 24;
+ // required uint32 profileid_ignored_4 = 25;
+ // required uint32 reserved_ignored_1 = 26;
+ // required uint32 reserved_ignored_2 = 27;
+ // required uint32 reserved_ignored_3 = 28;
+ // required uint32 reserved_ignored_4 = 29;
+ // required uint32 reserved_ignored_5 = 30;
+ // required uint32 reserved_ignored_6 = 31;
+ // required uint32 reserved_ignored_7 = 32;
+
+ // We'll use colorspaces instead
+ required ICCColorSpace color_space = 33;
+ // repeated Tag tags = 33;
+}
+
+message ICCColorSpace {
+ oneof color_space {
+ ICCXYZ xyz = 1;
+ ICCGray gray = 2;
+ }
+ // Default.
+ required ICCA2B0 a2b0 = 3;
+}
+
+message ICCXYZ {}
+
+message ICCGray {}
+
+message ICCA2B0 {
+ oneof type {
+ ICCA2B0Lut8 lut8 = 1;
+ ICCA2B0Lut16 lut16 = 2;
+ }
+ // Default.
+ required ICCA2B0AToB atob = 3;
+}
+
+enum Ignored { VALUE = 0; }
+
+enum UInt8 {
+ VAL0 = 0;
+ VAL1 = 1;
+ VAL2 = 2;
+ VAL3 = 3;
+ VAL4 = 4;
+ VAL5 = 5;
+ VAL6 = 6;
+ VAL7 = 7;
+ VAL8 = 8;
+ VAL9 = 9;
+ VAL10 = 10;
+ VAL11 = 11;
+ VAL12 = 12;
+ VAL13 = 13;
+ VAL14 = 14;
+ VAL15 = 15;
+ VAL16 = 16;
+ VAL17 = 17;
+ VAL18 = 18;
+ VAL19 = 19;
+ VAL20 = 20;
+ VAL21 = 21;
+ VAL22 = 22;
+ VAL23 = 23;
+ VAL24 = 24;
+ VAL25 = 25;
+ VAL26 = 26;
+ VAL27 = 27;
+ VAL28 = 28;
+ VAL29 = 29;
+ VAL30 = 30;
+ VAL31 = 31;
+ VAL32 = 32;
+ VAL33 = 33;
+ VAL34 = 34;
+ VAL35 = 35;
+ VAL36 = 36;
+ VAL37 = 37;
+ VAL38 = 38;
+ VAL39 = 39;
+ VAL40 = 40;
+ VAL41 = 41;
+ VAL42 = 42;
+ VAL43 = 43;
+ VAL44 = 44;
+ VAL45 = 45;
+ VAL46 = 46;
+ VAL47 = 47;
+ VAL48 = 48;
+ VAL49 = 49;
+ VAL50 = 50;
+ VAL51 = 51;
+ VAL52 = 52;
+ VAL53 = 53;
+ VAL54 = 54;
+ VAL55 = 55;
+ VAL56 = 56;
+ VAL57 = 57;
+ VAL58 = 58;
+ VAL59 = 59;
+ VAL60 = 60;
+ VAL61 = 61;
+ VAL62 = 62;
+ VAL63 = 63;
+ VAL64 = 64;
+ VAL65 = 65;
+ VAL66 = 66;
+ VAL67 = 67;
+ VAL68 = 68;
+ VAL69 = 69;
+ VAL70 = 70;
+ VAL71 = 71;
+ VAL72 = 72;
+ VAL73 = 73;
+ VAL74 = 74;
+ VAL75 = 75;
+ VAL76 = 76;
+ VAL77 = 77;
+ VAL78 = 78;
+ VAL79 = 79;
+ VAL80 = 80;
+ VAL81 = 81;
+ VAL82 = 82;
+ VAL83 = 83;
+ VAL84 = 84;
+ VAL85 = 85;
+ VAL86 = 86;
+ VAL87 = 87;
+ VAL88 = 88;
+ VAL89 = 89;
+ VAL90 = 90;
+ VAL91 = 91;
+ VAL92 = 92;
+ VAL93 = 93;
+ VAL94 = 94;
+ VAL95 = 95;
+ VAL96 = 96;
+ VAL97 = 97;
+ VAL98 = 98;
+ VAL99 = 99;
+ VAL100 = 100;
+ VAL101 = 101;
+ VAL102 = 102;
+ VAL103 = 103;
+ VAL104 = 104;
+ VAL105 = 105;
+ VAL106 = 106;
+ VAL107 = 107;
+ VAL108 = 108;
+ VAL109 = 109;
+ VAL110 = 110;
+ VAL111 = 111;
+ VAL112 = 112;
+ VAL113 = 113;
+ VAL114 = 114;
+ VAL115 = 115;
+ VAL116 = 116;
+ VAL117 = 117;
+ VAL118 = 118;
+ VAL119 = 119;
+ VAL120 = 120;
+ VAL121 = 121;
+ VAL122 = 122;
+ VAL123 = 123;
+ VAL124 = 124;
+ VAL125 = 125;
+ VAL126 = 126;
+ VAL127 = 127;
+ VAL128 = 128;
+ VAL129 = 129;
+ VAL130 = 130;
+ VAL131 = 131;
+ VAL132 = 132;
+ VAL133 = 133;
+ VAL134 = 134;
+ VAL135 = 135;
+ VAL136 = 136;
+ VAL137 = 137;
+ VAL138 = 138;
+ VAL139 = 139;
+ VAL140 = 140;
+ VAL141 = 141;
+ VAL142 = 142;
+ VAL143 = 143;
+ VAL144 = 144;
+ VAL145 = 145;
+ VAL146 = 146;
+ VAL147 = 147;
+ VAL148 = 148;
+ VAL149 = 149;
+ VAL150 = 150;
+ VAL151 = 151;
+ VAL152 = 152;
+ VAL153 = 153;
+ VAL154 = 154;
+ VAL155 = 155;
+ VAL156 = 156;
+ VAL157 = 157;
+ VAL158 = 158;
+ VAL159 = 159;
+ VAL160 = 160;
+ VAL161 = 161;
+ VAL162 = 162;
+ VAL163 = 163;
+ VAL164 = 164;
+ VAL165 = 165;
+ VAL166 = 166;
+ VAL167 = 167;
+ VAL168 = 168;
+ VAL169 = 169;
+ VAL170 = 170;
+ VAL171 = 171;
+ VAL172 = 172;
+ VAL173 = 173;
+ VAL174 = 174;
+ VAL175 = 175;
+ VAL176 = 176;
+ VAL177 = 177;
+ VAL178 = 178;
+ VAL179 = 179;
+ VAL180 = 180;
+ VAL181 = 181;
+ VAL182 = 182;
+ VAL183 = 183;
+ VAL184 = 184;
+ VAL185 = 185;
+ VAL186 = 186;
+ VAL187 = 187;
+ VAL188 = 188;
+ VAL189 = 189;
+ VAL190 = 190;
+ VAL191 = 191;
+ VAL192 = 192;
+ VAL193 = 193;
+ VAL194 = 194;
+ VAL195 = 195;
+ VAL196 = 196;
+ VAL197 = 197;
+ VAL198 = 198;
+ VAL199 = 199;
+ VAL200 = 200;
+ VAL201 = 201;
+ VAL202 = 202;
+ VAL203 = 203;
+ VAL204 = 204;
+ VAL205 = 205;
+ VAL206 = 206;
+ VAL207 = 207;
+ VAL208 = 208;
+ VAL209 = 209;
+ VAL210 = 210;
+ VAL211 = 211;
+ VAL212 = 212;
+ VAL213 = 213;
+ VAL214 = 214;
+ VAL215 = 215;
+ VAL216 = 216;
+ VAL217 = 217;
+ VAL218 = 218;
+ VAL219 = 219;
+ VAL220 = 220;
+ VAL221 = 221;
+ VAL222 = 222;
+ VAL223 = 223;
+ VAL224 = 224;
+ VAL225 = 225;
+ VAL226 = 226;
+ VAL227 = 227;
+ VAL228 = 228;
+ VAL229 = 229;
+ VAL230 = 230;
+ VAL231 = 231;
+ VAL232 = 232;
+ VAL233 = 233;
+ VAL234 = 234;
+ VAL235 = 235;
+ VAL236 = 236;
+ VAL237 = 237;
+ VAL238 = 238;
+ VAL239 = 239;
+ VAL240 = 240;
+ VAL241 = 241;
+ VAL242 = 242;
+ VAL243 = 243;
+ VAL244 = 244;
+ VAL245 = 245;
+ VAL246 = 246;
+ VAL247 = 247;
+ VAL248 = 248;
+ VAL249 = 249;
+ VAL250 = 250;
+ VAL251 = 251;
+ VAL252 = 252;
+ VAL253 = 253;
+ VAL254 = 254;
+ VAL255 = 255;
+}
+
+enum InputChannels {
+ ONE = 1;
+ TWO = 2;
+ THREE = 3;
+}
+
+enum OutputChannels {
+ // Can't be named THREE or else it will conflict with THREE in InputChannels.
+ // It doesn't matter, since we only use the numeric value on the converter
+ // side.
+ _THREE = 3;
+}
+
+message ICCA2B0AToB {
+ required InputChannels input_channels = 1;
+ required OutputChannels output_channels = 2; // Must be 3
+}
+
+message ICCA2B0Lut16 {
+ required ICCA2B0Lut8 lut8 = 1;
+
+ // TODO(metzman): allow these to be specified rather than generated.
+ // required uint32 in_table_entries = 2; // uint16_t
+ // required uint32 out_table_entries = 3; // uint16_t
+ required uint64 in_table_seed = 4;
+ required uint64 out_table_seed = 5;
+}
+
+message ICCA2B0Lut8 {
+ required Ignored ignored_byte_4 = 1;
+ required Ignored ignored_byte_5 = 2;
+ required Ignored ignored_byte_6 = 3;
+ required Ignored ignored_byte_7 = 4;
+ // Needs to agree with output_channels
+ required OutputChannels input_channels = 5;
+ required OutputChannels output_channels = 6; // Must be 3
+ required UInt8 clut_grid_points = 7;
+ required Ignored ignored_byte_11 = 8;
+ required Matrix matrix = 9;
+ required OneChannelGammas input_gammas_1 = 10;
+ required OneChannelGammas input_gammas_2 = 11;
+ required OneChannelGammas input_gammas_3 = 12;
+ required uint64 clut_bytes_seed = 13;
+ required OutputGammas output_gammas = 14;
+}
+
+message OneChannelGammas {
+ required int32 bytes_0_3 = 1;
+ required int32 bytes_4_7 = 2;
+ required int32 bytes_8_11 = 3;
+ required int32 bytes_12_15 = 4;
+ required int32 bytes_16_19 = 5;
+ required int32 bytes_20_23 = 6;
+ required int32 bytes_24_27 = 7;
+ required int32 bytes_28_31 = 8;
+ required int32 bytes_32_35 = 9;
+ required int32 bytes_36_39 = 10;
+ required int32 bytes_40_43 = 11;
+ required int32 bytes_44_47 = 12;
+ required int32 bytes_48_51 = 13;
+ required int32 bytes_52_55 = 14;
+ required int32 bytes_56_59 = 15;
+ required int32 bytes_60_63 = 16;
+ required int32 bytes_64_67 = 17;
+ required int32 bytes_68_71 = 18;
+ required int32 bytes_72_75 = 19;
+ required int32 bytes_76_79 = 20;
+ required int32 bytes_80_83 = 21;
+ required int32 bytes_84_87 = 22;
+ required int32 bytes_88_91 = 23;
+ required int32 bytes_92_95 = 24;
+ required int32 bytes_96_99 = 25;
+ required int32 bytes_100_103 = 26;
+ required int32 bytes_104_107 = 27;
+ required int32 bytes_108_111 = 28;
+ required int32 bytes_112_115 = 29;
+ required int32 bytes_116_119 = 30;
+ required int32 bytes_120_123 = 31;
+ required int32 bytes_124_127 = 32;
+ required int32 bytes_128_131 = 33;
+ required int32 bytes_132_135 = 34;
+ required int32 bytes_136_139 = 35;
+ required int32 bytes_140_143 = 36;
+ required int32 bytes_144_147 = 37;
+ required int32 bytes_148_151 = 38;
+ required int32 bytes_152_155 = 39;
+ required int32 bytes_156_159 = 40;
+ required int32 bytes_160_163 = 41;
+ required int32 bytes_164_167 = 42;
+ required int32 bytes_168_171 = 43;
+ required int32 bytes_172_175 = 44;
+ required int32 bytes_176_179 = 45;
+ required int32 bytes_180_183 = 46;
+ required int32 bytes_184_187 = 47;
+ required int32 bytes_188_191 = 48;
+ required int32 bytes_192_195 = 49;
+ required int32 bytes_196_199 = 50;
+ required int32 bytes_200_203 = 51;
+ required int32 bytes_204_207 = 52;
+ required int32 bytes_208_211 = 53;
+ required int32 bytes_212_215 = 54;
+ required int32 bytes_216_219 = 55;
+ required int32 bytes_220_223 = 56;
+ required int32 bytes_224_227 = 57;
+ required int32 bytes_228_231 = 58;
+ required int32 bytes_232_235 = 59;
+ required int32 bytes_236_239 = 60;
+ required int32 bytes_240_243 = 61;
+ required int32 bytes_244_247 = 62;
+ required int32 bytes_248_251 = 63;
+ required int32 bytes_252_255 = 64;
+}
+
+// Since output gammas are 3 times the size of input gammas, make
+message OutputGammas {
+ required OneChannelGammas bytes_0_255 = 1;
+ required OneChannelGammas bytes_255_511 = 2;
+ required OneChannelGammas bytes_512_768 = 3;
+}
diff --git a/chromium/testing/libfuzzer/proto/skia_image_filter_proto_converter.cc b/chromium/testing/libfuzzer/proto/skia_image_filter_proto_converter.cc
new file mode 100644
index 00000000000..784db20f328
--- /dev/null
+++ b/chromium/testing/libfuzzer/proto/skia_image_filter_proto_converter.cc
@@ -0,0 +1,2354 @@
+// Copyright 2018 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.
+
+// Converts an Input protobuf Message to a string that can be successfully read
+// by SkImageFilter::Deserialize and used as an image filter. The string
+// is essentially a valid flattened skia image filter. Note: We will sometimes
+// not use the exact values given to us by LPM in cases where those particular
+// values cause issues with OOMs and timeouts. Other times, we may write a value
+// that isn't exactly the same as the one given to us by LPM, since we may want
+// to write invalid values that the proto definition forbids (eg a number that
+// is not in enum). Also note that the skia unflattening code is necessary to
+// apply the output of the converter to a canvas, but it isn't the main target
+// of the fuzzer. This means that we will generally try to produce output that
+// can be applied to a canvas, even if we will consequently be unable to produce
+// outputs that allow us to reach paths in the unflattening code (in particular,
+// code that handles invalid input). We make this tradeoff because being applied
+// to a canvas makes an image filter more likely to cause bugs than if it were
+// just deserialized. Thus, increasing the chance that a filter is applied is
+// more important than hitting all paths in unflattening, particularly if those
+// paths return nullptr because they've detected an invalid filter. The mutated
+// enum values are a case where we knowingly generate output that may not be
+// unflattened successfully, which is why we mutate enums relatively
+// infrequently.
+// Note that since this is a work in progress and skia serialization is a
+// moving target, not everything is finished. Many of these parts of the code
+// are #defined out if DEVELOPMENT is not defined.
+
+#include "testing/libfuzzer/proto/skia_image_filter_proto_converter.h"
+
+#include <ctype.h>
+#include <stdlib.h>
+
+#include <algorithm>
+#include <cmath>
+#include <limits>
+#include <random>
+#include <set>
+#include <string>
+#include <tuple>
+#include <unordered_map>
+#include <vector>
+
+#include "base/logging.h"
+#include "third_party/protobuf/src/google/protobuf/descriptor.h"
+#include "third_party/protobuf/src/google/protobuf/message.h"
+#include "third_party/protobuf/src/google/protobuf/repeated_field.h"
+#include "third_party/skia/include/core/SkPoint.h"
+#include "third_party/skia/include/core/SkRect.h"
+
+using google::protobuf::Descriptor;
+using google::protobuf::EnumDescriptor;
+using google::protobuf::EnumValueDescriptor;
+using google::protobuf::FieldDescriptor;
+using google::protobuf::Message;
+using google::protobuf::Reflection;
+
+namespace skia_image_filter_proto_converter {
+
+// Visit the skia flattenable that is stored on the oneof FIELD field of MSG if
+// not flattenable_visited and MSG.has_FIELD. Sets flattenable_visited to true
+// if the MSG.FIELD() is visited. Note that `bool flattenable_visited` must be
+// defined false in the same context that this macro is used, before it can be
+// used.
+#define VISIT_ONEOF_FLATTENABLE(MSG, FIELD) \
+ if (MSG.has_##FIELD() && !IsBlacklisted(#FIELD)) { \
+ CHECK(!flattenable_visited); \
+ if (PreVisitFlattenable(FieldToFlattenableName(#FIELD))) { \
+ Visit(MSG.FIELD()); \
+ PostVisitFlattenable(); \
+ } \
+ flattenable_visited = true; \
+ }
+
+// Visit FIELD if FIELD is set or if no other field on message was visited
+// (this should be used at the end of a series of calls to
+// VISIT_ONEOF_FLATTENABLE).
+// Note FIELD should not be a message that contains itself by default.
+// This is used for messages like ImageFilterChild where we must visit one of
+// the fields in a oneof. Even though protobuf doesn't mandate that one of these
+// be set, we can still visit one of them if they are not set and protobuf will
+// return the default values for each field on that message.
+#define VISIT_DEFAULT_FLATTENABLE(MSG, FIELD) \
+ VISIT_ONEOF_FLATTENABLE(MSG, FIELD); \
+ if (!flattenable_visited) { \
+ flattenable_visited = true; \
+ if (PreVisitFlattenable(FieldToFlattenableName(#FIELD))) { \
+ Visit(MSG.FIELD()); \
+ PostVisitFlattenable(); \
+ } \
+ }
+
+// Visit FIELD if it is set on MSG, or write a NULL to indicate it is not
+// present.
+#define VISIT_OPT_OR_NULL(MSG, FIELD) \
+ if (MSG.has_##FIELD()) { \
+ Visit(MSG.FIELD()); \
+ } else { \
+ WriteNum(0); \
+ }
+
+// Call VisitPictureTag on picture_tag.FIELD() if it is set.
+#define VISIT_OPT_TAG(FIELD, TAG) \
+ if (picture_tag.has_##FIELD()) { \
+ VisitPictureTag(picture_tag.FIELD(), TAG); \
+ }
+
+// Copied from third_party/skia/include/core/SkTypes.h:SkSetFourByteTag.
+#define SET_FOUR_BYTE_TAG(A, B, C, D) \
+ (((A) << 24) | ((B) << 16) | ((C) << 8) | (D))
+
+// The following enums and constants are copied from various parts of the skia
+// codebase.
+enum FlatFlags {
+ kHasTypeface_FlatFlag = 0x1,
+ kHasEffects_FlatFlag = 0x2,
+ kFlatFlagMask = 0x3,
+};
+
+enum LightType {
+ kDistant_LightType,
+ kPoint_LightType,
+ kSpot_LightType,
+};
+
+// Copied from SkVertices.cpp.
+enum VerticesConstants {
+ kMode_Mask = 0x0FF,
+ kHasTexs_Mask = 0x100,
+ kHasColors_Mask = 0x200,
+};
+
+// Copied from SerializationOffsets in SkPath.h. Named PathSerializationOffsets
+// to avoid conflicting with PathRefSerializationOffsets. Both enums were named
+// SerializationOffsets in skia.
+enum PathSerializationOffsets {
+ kType_SerializationShift = 28,
+ kDirection_SerializationShift = 26,
+ kIsVolatile_SerializationShift = 25,
+ kConvexity_SerializationShift = 16,
+ kFillType_SerializationShift = 8,
+};
+
+// Copied from SerializationOffsets in SkPathRef.h. Named
+// PathRefSerializationOffsets to avoid conflicting with
+// PathSerializationOffsets. Both enums were named SerializationOffsets in skia.
+enum PathRefSerializationOffsets {
+ kLegacyRRectOrOvalStartIdx_SerializationShift = 28,
+ kLegacyRRectOrOvalIsCCW_SerializationShift = 27,
+ kLegacyIsRRect_SerializationShift = 26,
+ kIsFinite_SerializationShift = 25,
+ kLegacyIsOval_SerializationShift = 24,
+ kSegmentMask_SerializationShift = 0
+};
+
+const uint32_t Converter::kPictEofTag = SET_FOUR_BYTE_TAG('e', 'o', 'f', ' ');
+
+const uint32_t Converter::kProfileLookupTable[] = {
+ SET_FOUR_BYTE_TAG('m', 'n', 't', 'r'),
+ SET_FOUR_BYTE_TAG('s', 'c', 'n', 'r'),
+ SET_FOUR_BYTE_TAG('p', 'r', 't', 'r'),
+ SET_FOUR_BYTE_TAG('s', 'p', 'a', 'c'),
+};
+
+const uint32_t Converter::kInputColorSpaceLookupTable[] = {
+ SET_FOUR_BYTE_TAG('R', 'G', 'B', ' '),
+ SET_FOUR_BYTE_TAG('C', 'M', 'Y', 'K'),
+ SET_FOUR_BYTE_TAG('G', 'R', 'A', 'Y'),
+};
+
+const uint32_t Converter::kPCSLookupTable[] = {
+ SET_FOUR_BYTE_TAG('X', 'Y', 'Z', ' '),
+ SET_FOUR_BYTE_TAG('L', 'a', 'b', ' '),
+};
+
+const uint32_t Converter::kTagLookupTable[] = {
+ SET_FOUR_BYTE_TAG('r', 'X', 'Y', 'Z'),
+ SET_FOUR_BYTE_TAG('g', 'X', 'Y', 'Z'),
+ SET_FOUR_BYTE_TAG('b', 'X', 'Y', 'Z'),
+ SET_FOUR_BYTE_TAG('r', 'T', 'R', 'C'),
+ SET_FOUR_BYTE_TAG('g', 'T', 'R', 'C'),
+ SET_FOUR_BYTE_TAG('b', 'T', 'R', 'C'),
+ SET_FOUR_BYTE_TAG('k', 'T', 'R', 'C'),
+ SET_FOUR_BYTE_TAG('A', '2', 'B', '0'),
+ SET_FOUR_BYTE_TAG('c', 'u', 'r', 'v'),
+ SET_FOUR_BYTE_TAG('p', 'a', 'r', 'a'),
+ SET_FOUR_BYTE_TAG('m', 'l', 'u', 'c'),
+};
+
+const char Converter::kSkPictReaderTag[] = {'r', 'e', 'a', 'd'};
+const char Converter::kPictureMagicString[] = {'s', 'k', 'i', 'a',
+ 'p', 'i', 'c', 't'};
+
+const uint8_t Converter::kCountNibBits[] = {0, 1, 1, 2, 1, 2, 2, 3,
+ 1, 2, 2, 3, 2, 3, 3, 4};
+
+// The rest of the Converter attributes are not copied from skia.
+const int Converter::kFlattenableDepthLimit = 3;
+const int Converter::kColorTableBufferLength = 256;
+uint8_t Converter::kColorTableBuffer[kColorTableBufferLength];
+const int Converter::kNumBound = 20;
+const uint8_t Converter::kMutateEnumDenominator = 40;
+
+// Does not include SkSumPathEffect, SkComposePathEffect or SkRegion
+// since they don't use the VISIT FLATTENABLE macros.
+const string_map_t Converter::kFieldToFlattenableName = {
+ {"path_1d_path_effect", "SkPath1DPathEffect"},
+ {"path_2d_path_effect", "SkPath2DPathEffect"},
+ {"alpha_threshold_filter_impl", "SkAlphaThresholdFilterImpl"},
+ {"arithmetic_image_filter", "SkArithmeticImageFilter"},
+ {"blur_image_filter_impl", "SkBlurImageFilterImpl"},
+ {"blur_mask_filter_impl", "SkBlurMaskFilterImpl"},
+ {"color_4_shader", "SkColor4Shader"},
+ {"color_filter_image_filter", "SkColorFilterImageFilter"},
+ {"color_filter_shader", "SkColorFilterShader"},
+ {"color_matrix_filter_row_major_255", "SkColorMatrixFilterRowMajor255"},
+ {"color_shader", "SkColorShader"},
+ {"compose_color_filter", "SkComposeColorFilter"},
+ {"compose_image_filter", "SkComposeImageFilter"},
+ {"compose_shader", "SkComposeShader"},
+ {"corner_path_effect", "SkCornerPathEffect"},
+ {"dash_impl", "SkDashImpl"},
+ {"diffuse_lighting_image_filter", "SkDiffuseLightingImageFilter"},
+ {"dilate_image_filter", "SkDilateImageFilter"},
+ {"discrete_path_effect", "SkDiscretePathEffect"},
+ {"displacement_map_effect", "SkDisplacementMapEffect"},
+ {"drop_shadow_image_filter", "SkDropShadowImageFilter"},
+ {"emboss_mask_filter", "SkEmbossMaskFilter"},
+ {"empty_shader", "SkEmptyShader"},
+ {"image_shader", "SkImageShader"},
+ {"image_source", "SkImageSource"},
+ {"line_2d_path_effect", "SkLine2DPathEffect"},
+ {"linear_gradient", "SkLinearGradient"},
+ {"local_matrix_image_filter", "SkLocalMatrixImageFilter"},
+ {"local_matrix_shader", "SkLocalMatrixShader"},
+ {"luma_color_filter", "SkLumaColorFilter"},
+ {"magnifier_image_filter", "SkMagnifierImageFilter"},
+ {"matrix_convolution_image_filter", "SkMatrixConvolutionImageFilter"},
+ {"matrix_image_filter", "SkMatrixImageFilter"},
+ {"merge_image_filter", "SkMergeImageFilter"},
+ {"mode_color_filter", "SkModeColorFilter"},
+ {"offset_image_filter", "SkOffsetImageFilter"},
+ {"overdraw_color_filter", "SkOverdrawColorFilter"},
+ {"paint_image_filter", "SkPaintImageFilter"},
+ {"picture_image_filter", "SkPictureImageFilter"},
+ {"picture_shader", "SkPictureShader"},
+ {"radial_gradient", "SkRadialGradient"},
+ {"r_rects_gaussian_edge_mask_filter_impl",
+ "SkRRectsGaussianEdgeMaskFilterImpl"},
+ {"specular_lighting_image_filter", "SkSpecularLightingImageFilter"},
+ {"sweep_gradient", "SkSweepGradient"},
+ {"tile_image_filter", "SkTileImageFilter"},
+ {"two_point_conical_gradient", "SkTwoPointConicalGradient"},
+ {"xfermode_image_filter", "SkXfermodeImageFilter"},
+ {"xfermode_image_filter__base", "SkXfermodeImageFilter_Base"},
+ {"srgb_gamma_color_filter", "SkSRGBGammaColorFilter"},
+ {"high_contrast__filter", "SkHighContrast_Filter"},
+ {"table__color_filter", "SkTable_ColorFilter"},
+ {"to_srgb_color_filter", "SkToSRGBColorFilter"},
+ {"layer_draw_looper", "SkLayerDrawLooper"},
+ {"perlin_noise_shader_impl", "SkPerlinNoiseShaderImpl"},
+ {"erode_image_filter", "SkErodeImageFilter"},
+};
+
+const std::set<std::string> Converter::kMisbehavedFlattenableBlacklist = {
+ "matrix_image_filter", // Causes OOMs.
+ "discrete_path_effect", // Causes timeouts.
+ "path_1d_path_effect", // Causes timeouts.
+};
+
+// We don't care about default values of attributes because Reset() sets them to
+// correct values and is called by Convert(), the only important public
+// function.
+Converter::Converter() {
+ CHECK_GT(kMutateEnumDenominator, 2);
+}
+
+Converter::~Converter() {}
+
+Converter::Converter(const Converter& other) {}
+
+std::string Converter::FieldToFlattenableName(
+ const std::string& field_name) const {
+ CHECK(kFieldToFlattenableName.find(field_name) !=
+ kFieldToFlattenableName.end());
+
+ return kFieldToFlattenableName.at(field_name);
+}
+
+void Converter::Reset() {
+ output_.clear();
+ bound_positive_ = false;
+ dont_mutate_enum_ = true;
+ pair_path_effect_depth_ = 0;
+ flattenable_depth_ = 0;
+ stroke_style_used_ = false;
+ in_compose_color_filter_ = false;
+// In production we don't need attributes used by ICC code since it is not
+// built for production code.
+#ifdef DEVELOPMENT
+ tag_offset_ = 0;
+ icc_base_ = 0;
+#endif // DEVELOPMENT
+}
+
+std::string Converter::Convert(const Input& input) {
+ Reset();
+ rand_gen_ = std::mt19937(input.rng_seed());
+ enum_mutator_chance_distribution_ =
+ std::uniform_int_distribution<>(2, kMutateEnumDenominator);
+
+ // This will recursively call Visit on each proto flattenable until all of
+ // them are converted to strings and stored in output_.
+ Visit(input.image_filter());
+ CheckAlignment();
+ return std::string(&output_[0], output_.size());
+}
+
+void Converter::Visit(const CropRectangle& crop_rectangle) {
+ Visit(crop_rectangle.rectangle());
+ WriteNum(BoundNum(crop_rectangle.flags()));
+}
+
+void Converter::Visit(const Rectangle& rectangle) {
+ WriteRectangle(GetValidRectangle(rectangle.left(), rectangle.top(),
+ rectangle.right(), rectangle.bottom()));
+}
+
+std::tuple<float, float, float, float>
+Converter::GetValidRectangle(float left, float top, float right, float bottom) {
+ bool initial = bound_positive_;
+ bound_positive_ = true;
+ left = BoundFloat(left);
+ top = BoundFloat(top);
+ right = BoundFloat(right);
+ bottom = BoundFloat(bottom);
+
+ if (right < left)
+ right = left;
+
+ if (bottom < top)
+ bottom = top;
+
+ // Inspired by SkValidationUtils.h:SkIsValidRect
+ CHECK_LE(left, right);
+ CHECK_LE(top, bottom);
+ CHECK(IsFinite(right - left));
+ CHECK(IsFinite(bottom - top));
+ bound_positive_ = initial;
+ return std::make_tuple(left, top, right, bottom);
+}
+
+std::tuple<int32_t, int32_t, int32_t, int32_t> Converter::GetValidIRect(
+ int32_t left,
+ int32_t top,
+ int32_t right,
+ int32_t bottom) {
+ auto float_rectangle = GetValidRectangle(left, top, right, bottom);
+ return std::make_tuple(static_cast<int32_t>(std::get<0>(float_rectangle)),
+ static_cast<int32_t>(std::get<1>(float_rectangle)),
+ static_cast<int32_t>(std::get<2>(float_rectangle)),
+ static_cast<int32_t>(std::get<3>(float_rectangle)));
+}
+
+template <typename T>
+void Converter::WriteRectangle(std::tuple<T, T, T, T> rectangle) {
+ WriteNum(std::get<0>(rectangle));
+ WriteNum(std::get<1>(rectangle));
+ WriteNum(std::get<2>(rectangle));
+ WriteNum(std::get<3>(rectangle));
+}
+
+void Converter::Visit(const LightChild& light_child) {
+ if (light_child.has_point_light())
+ Visit(light_child.point_light());
+ else if (light_child.has_spot_light())
+ Visit(light_child.spot_light());
+ else
+ Visit(light_child.distant_light());
+}
+
+void Converter::Visit(const LightParent& light_parent) {
+ if (light_parent.light_child().has_point_light())
+ WriteNum(kPoint_LightType);
+ else if (light_parent.light_child().has_spot_light())
+ WriteNum(kSpot_LightType);
+ else // Assume we have distant light
+ WriteNum(kDistant_LightType);
+ Visit(light_parent.color());
+ Visit(light_parent.light_child());
+}
+
+void Converter::Visit(const ImageFilterChild& image_filter_child) {
+ bool flattenable_visited = false;
+ VISIT_ONEOF_FLATTENABLE(image_filter_child, specular_lighting_image_filter);
+ VISIT_ONEOF_FLATTENABLE(image_filter_child, matrix_image_filter);
+ VISIT_ONEOF_FLATTENABLE(image_filter_child, arithmetic_image_filter);
+ VISIT_ONEOF_FLATTENABLE(image_filter_child, alpha_threshold_filter_impl);
+ VISIT_ONEOF_FLATTENABLE(image_filter_child, blur_image_filter_impl);
+ VISIT_ONEOF_FLATTENABLE(image_filter_child, color_filter_image_filter);
+ VISIT_ONEOF_FLATTENABLE(image_filter_child, compose_image_filter);
+ VISIT_ONEOF_FLATTENABLE(image_filter_child, displacement_map_effect);
+ VISIT_ONEOF_FLATTENABLE(image_filter_child, drop_shadow_image_filter);
+ VISIT_ONEOF_FLATTENABLE(image_filter_child, local_matrix_image_filter);
+ VISIT_ONEOF_FLATTENABLE(image_filter_child, magnifier_image_filter);
+ VISIT_ONEOF_FLATTENABLE(image_filter_child, matrix_convolution_image_filter);
+ VISIT_ONEOF_FLATTENABLE(image_filter_child, merge_image_filter);
+ VISIT_ONEOF_FLATTENABLE(image_filter_child, dilate_image_filter);
+ VISIT_ONEOF_FLATTENABLE(image_filter_child, erode_image_filter);
+ VISIT_ONEOF_FLATTENABLE(image_filter_child, offset_image_filter);
+ VISIT_ONEOF_FLATTENABLE(image_filter_child, picture_image_filter);
+ VISIT_ONEOF_FLATTENABLE(image_filter_child, tile_image_filter);
+ VISIT_ONEOF_FLATTENABLE(image_filter_child, xfermode_image_filter__base);
+ VISIT_ONEOF_FLATTENABLE(image_filter_child, xfermode_image_filter);
+ VISIT_ONEOF_FLATTENABLE(image_filter_child, diffuse_lighting_image_filter);
+ VISIT_ONEOF_FLATTENABLE(image_filter_child, image_source);
+ VISIT_DEFAULT_FLATTENABLE(image_filter_child, paint_image_filter);
+}
+
+void Converter::Visit(
+ const DiffuseLightingImageFilter& diffuse_lighting_image_filter) {
+ Visit(diffuse_lighting_image_filter.parent(), 1);
+ Visit(diffuse_lighting_image_filter.light());
+ WriteNum(diffuse_lighting_image_filter.surface_scale());
+ // Can't be negative, see:
+ // https://www.w3.org/TR/SVG/filters.html#feDiffuseLightingElement
+ const float kd = fabs(BoundFloat(diffuse_lighting_image_filter.kd()));
+ WriteNum(kd);
+}
+
+void Converter::Visit(const XfermodeImageFilter& xfermode_image_filter) {
+ Visit(xfermode_image_filter.parent(), 2);
+ WriteNum(xfermode_image_filter.mode());
+}
+
+void Converter::Visit(
+ const XfermodeImageFilter_Base& xfermode_image_filter__base) {
+ Visit(xfermode_image_filter__base.parent(), 2);
+ WriteNum(xfermode_image_filter__base.mode());
+}
+
+void Converter::Visit(const TileImageFilter& tile_image_filter) {
+ Visit(tile_image_filter.parent(), 1);
+ Visit(tile_image_filter.src());
+ Visit(tile_image_filter.dst());
+}
+
+void Converter::Visit(const OffsetImageFilter& offset_image_filter) {
+ Visit(offset_image_filter.parent(), 1);
+ Visit(offset_image_filter.offset());
+}
+
+void Converter::Visit(const HighContrast_Filter& high_contrast__filter) {
+ WriteFields(high_contrast__filter, 1, 2);
+ // Use contrast as a seed.
+ WriteNum(GetRandomFloat(high_contrast__filter.contrast(), -1.0, 1.0));
+}
+
+void Converter::Visit(const MergeImageFilter& merge_image_filter) {
+ Visit(merge_image_filter.parent(), merge_image_filter.parent().inputs_size());
+}
+
+void Converter::Visit(const ErodeImageFilter& erode_image_filter) {
+ Visit(erode_image_filter.parent(), 1);
+ bool initial = bound_positive_;
+ bound_positive_ = true;
+ WriteFields(erode_image_filter, 2);
+ bound_positive_ = initial;
+}
+
+template <typename T>
+T Converter::BoundNum(T num, int upper_bound) const {
+ if (bound_positive_)
+ num = Abs(num);
+ if (num >= 0) {
+ return num % upper_bound;
+ } else {
+ // Don't let negative numbers be too negative.
+ return num % -upper_bound;
+ }
+}
+
+template <typename T>
+T Converter::BoundNum(T num) {
+ return BoundNum(num, kNumBound);
+}
+
+float Converter::BoundFloat(float num) {
+ return BoundFloat(num, kNumBound);
+}
+
+float Converter::BoundFloat(float num, const float num_bound) {
+ // Don't allow nans infs, they can cause OOMs.
+ if (!IsFinite(num))
+ num = GetRandomFloat(&rand_gen_);
+
+ float result;
+ if (num >= 0)
+ result = fmod(num, num_bound);
+ else if (bound_positive_)
+ result = fmod(fabsf(num), num_bound);
+ else
+ // Bound negative numbers.
+ result = fmod(num, -num_bound);
+ if (!IsFinite(result))
+ return BoundFloat(num);
+ return result;
+}
+
+void Converter::Visit(const DilateImageFilter& dilate_image_filter) {
+ Visit(dilate_image_filter.parent(), 1);
+ // Make sure WriteFields writes positive values for width and height.
+ // Save the value of bound_positive_ and restore it after WriteFields
+ // returns.
+ bool initial_bound_positive = bound_positive_;
+ bound_positive_ = true;
+ WriteFields(dilate_image_filter, 2);
+ bound_positive_ = initial_bound_positive;
+}
+
+void Converter::Visit(
+ const MatrixConvolutionImageFilter& matrix_convolution_image_filter) {
+ Visit(matrix_convolution_image_filter.parent(), 1);
+ // Avoid timeouts from having to generate too many random numbers.
+ // TODO(metzman): actually calculate the limit based on this bound (eg 31 x 1
+ // probably doesn't need to be bounded).
+ const int upper_bound = 30;
+
+ // Use 2 instead of 1 to avoid FPEs in BoundNum.
+ int32_t width = std::max(
+ 2, BoundNum(Abs(matrix_convolution_image_filter.width()), upper_bound));
+
+ WriteNum(width);
+
+ int32_t height = std::max(
+ 2, BoundNum(Abs(matrix_convolution_image_filter.height()), upper_bound));
+
+ WriteNum(height);
+
+ std::mt19937 rand_gen(matrix_convolution_image_filter.kernel_seed());
+ const uint32_t kernel_size = width * height;
+ WriteNum(kernel_size);
+ // Use rand_gen to ensure we have a large enough kernel.
+ for (uint32_t kernel_counter = 0; kernel_counter < kernel_size;
+ kernel_counter++) {
+ float kernel_element = GetRandomFloat(&rand_gen);
+ WriteNum(kernel_element);
+ }
+ WriteFields(matrix_convolution_image_filter, 5, 6);
+
+ const uint32_t offset_x =
+ std::max(0, matrix_convolution_image_filter.offset_x());
+
+ const uint32_t offset_y =
+ std::max(0, matrix_convolution_image_filter.offset_y());
+
+ WriteNum(BoundNum(offset_x, width - 1));
+ WriteNum(BoundNum(offset_y, height - 1));
+ WriteFields(matrix_convolution_image_filter, 9);
+}
+
+void Converter::Visit(const MagnifierImageFilter& magnifier_image_filter) {
+ Visit(magnifier_image_filter.parent(), 1);
+ Visit(magnifier_image_filter.src());
+ const float inset = fabs(BoundFloat(magnifier_image_filter.inset()));
+ CHECK(IsFinite(inset));
+ WriteNum(inset);
+}
+
+void Converter::Visit(const LocalMatrixImageFilter& local_matrix_image_filter) {
+ // TODO(metzman): Make it so that deserialization always succeeds by ensuring
+ // the type isn't kAffine_Mask or KPerspectiveMask (see constructor for
+ // SkLocalMatrixImageFilter).
+ Visit(local_matrix_image_filter.parent(), 1);
+ Visit(local_matrix_image_filter.matrix(), true);
+}
+
+void Converter::Visit(const ImageSource& image_source) {
+ WriteNum(image_source.filter_quality());
+ auto src_rect = GetValidRectangle(
+ image_source.src().left(), image_source.src().top(),
+ image_source.src().right(), image_source.src().bottom());
+
+ // See SkImageSource::Make for why we mandate width and height be at least
+ // .01. This is such a small difference that we won't bother bounding again.
+ float left = std::get<0>(src_rect);
+ float* right = &std::get<2>(src_rect);
+ if ((*right - left) <= 0.0f)
+ *right += .01;
+
+ float top = std::get<1>(src_rect);
+ float* bottom = &std::get<3>(src_rect);
+ if ((*bottom - top) <= 0.0f)
+ *bottom += .01;
+
+ WriteRectangle(src_rect);
+ Visit(image_source.dst());
+ Visit(image_source.image());
+}
+
+void Converter::Visit(const DropShadowImageFilter& drop_shadow_image_filter) {
+ Visit(drop_shadow_image_filter.parent(), 1);
+ WriteFields(drop_shadow_image_filter, 2);
+}
+
+void Converter::Visit(const DisplacementMapEffect& displacement_map_effect) {
+ Visit(displacement_map_effect.parent(), 2);
+ bool initial = dont_mutate_enum_;
+ dont_mutate_enum_ = true;
+ WriteFields(displacement_map_effect, 2);
+ dont_mutate_enum_ = initial;
+}
+
+void Converter::Visit(const ComposeImageFilter& compose_image_filter) {
+ Visit(compose_image_filter.parent(), 2);
+}
+
+void Converter::Visit(const ColorFilterImageFilter& color_filter_image_filter) {
+ Visit(color_filter_image_filter.parent(), 1);
+ Visit(color_filter_image_filter.color_filter());
+}
+
+void Converter::Visit(const BlurImageFilterImpl& blur_image_filter_impl) {
+ Visit(blur_image_filter_impl.parent(), 1);
+ WriteFields(blur_image_filter_impl, 2);
+}
+
+void Converter::Visit(
+ const AlphaThresholdFilterImpl& alpha_threshold_filter_impl) {
+ Visit(alpha_threshold_filter_impl.parent(), 1);
+ WriteFields(alpha_threshold_filter_impl, 2, 3);
+ Visit(alpha_threshold_filter_impl.rgn());
+}
+
+std::tuple<int32_t, int32_t, int32_t, int32_t> Converter::WriteNonEmptyIRect(
+ const IRect& irect) {
+ // Make sure bounds do not specify an empty rectangle.
+ // See SkRect.h:202
+ auto rectangle =
+ GetValidIRect(irect.left(), irect.top(), irect.right(), irect.bottom());
+
+ // Ensure top and right are greater than left and top.
+ if (irect.left() >= irect.right() || irect.top() >= irect.bottom()) {
+ std::get<2>(rectangle) = std::get<0>(rectangle) + 1;
+ std::get<3>(rectangle) = std::get<1>(rectangle) + 1;
+ }
+ WriteRectangle(rectangle);
+ return rectangle;
+}
+
+void Converter::Visit(const Region& region) {
+ // Write simple region.
+ WriteNum(0);
+ WriteNonEmptyIRect(region.bounds());
+
+// Complex regions are not finished.
+#ifdef DEVELOPMENT
+ enum { kRunTypeSentinel = 0x7FFFFFFF };
+ auto rectangle = WriteNonEmptyIRect(region.bounds());
+ const int32_t bound_left = std::get<0>(rectangle);
+ const int32_t bound_top = std::get<1>(rectangle);
+ const int32_t bound_right = std::get<2>(rectangle);
+ const int32_t bound_bottom = std::get<3>(rectangle);
+
+ const int32_t y_span_count =
+ BoundNum(std::max(1, Abs(region.y_span_count())));
+
+ const int32_t interval_count = BoundNum(std::max(1, Abs(region.interval_())));
+
+ WriteNum(run_count);
+ WriteNum(y_span_count);
+ WriteNum(interval_count);
+
+ // See SkRegion::validate_run.
+ // Really this is two less, but we will write the two sentinels
+ ourselves const int32_t run_count = 3 * y_span_count + 2 * interval_count;
+ CHECK(run_count >= 7);
+
+ WriteNum(run_count + 2);
+ // Write runs.
+
+ // Write top.
+ Write(bound_top);
+
+ WriteNum(kRunTypeSentinel);
+ WriteNum(kRunTypeSentinel);
+#endif // DEVELOPMENT
+}
+
+void Converter::Visit(const PictureInfo& picture_info) {
+ WriteArray(kPictureMagicString, sizeof(kPictureMagicString));
+ WriteNum(picture_info.version());
+ Visit(picture_info.rectangle());
+ if (picture_info.version() < PictureInfo::kRemoveHeaderFlags_Version)
+ WriteNum(picture_info.flags());
+}
+
+void Converter::Visit(const ImageFilterParent& image_filter,
+ const int num_inputs_required) {
+ CHECK_GE(num_inputs_required, 0);
+ if (!num_inputs_required) {
+ WriteNum(0);
+ } else {
+ WriteNum(num_inputs_required);
+ WriteBool(true);
+ Visit(image_filter.default_input());
+ int num_inputs = 1;
+ for (const auto& input : image_filter.inputs()) {
+ if (num_inputs++ >= num_inputs_required)
+ break;
+ WriteBool(true);
+ Visit(input);
+ }
+ for (; num_inputs < num_inputs_required; num_inputs++) {
+ // Copy default_input until we have enough.
+ WriteBool(true);
+ Visit(image_filter.default_input());
+ }
+ }
+ Visit(image_filter.crop_rectangle());
+}
+
+void Converter::Visit(const ArithmeticImageFilter& arithmetic_image_filter) {
+ Visit(arithmetic_image_filter.parent(), 2);
+
+ // This is field is ignored, but write kSrcOver (3) as the flattening code
+ // does.
+ // TODO(metzman): change to enum value (SkBlendMode::kSrcOver) when it
+ // is uncommented, for now just write, its value: 3.
+ WriteNum(3);
+
+ WriteFields(arithmetic_image_filter, 2);
+}
+
+void Converter::Visit(
+ const SpecularLightingImageFilter& specular_lighting_image_filter) {
+ Visit(specular_lighting_image_filter.image_filter_parent(), 1);
+ Visit(specular_lighting_image_filter.light());
+ WriteNum(BoundFloat(specular_lighting_image_filter.surface_scale()) * 255);
+ WriteNum(fabs(BoundFloat(specular_lighting_image_filter.ks())));
+ WriteNum(BoundFloat(specular_lighting_image_filter.shininess()));
+}
+
+void Converter::RecordSize() {
+ // Reserve space to overwrite when we are done writing whatever size we are
+ // recording.
+ WriteNum(0);
+ start_sizes_.push_back(output_.size());
+}
+
+size_t Converter::PopStartSize() {
+ CHECK_GT(start_sizes_.size(), static_cast<size_t>(0));
+ const size_t back = start_sizes_.back();
+ start_sizes_.pop_back();
+ return back;
+}
+
+template <typename T>
+void Converter::WriteNum(const T num) {
+ if (sizeof(T) > 4) {
+ CHECK(num <= UINT32_MAX);
+ uint32_t four_byte_num = static_cast<uint32_t>(num);
+ char num_arr[sizeof(four_byte_num)];
+ memcpy(num_arr, &four_byte_num, sizeof(four_byte_num));
+ for (size_t idx = 0; idx < sizeof(four_byte_num); idx++)
+ output_.push_back(num_arr[idx]);
+ return;
+ }
+ char num_arr[sizeof(T)];
+ memcpy(num_arr, &num, sizeof(T));
+ for (size_t idx = 0; idx < sizeof(T); idx++)
+ output_.push_back(num_arr[idx]);
+}
+
+void Converter::InsertSize(const size_t size, const uint32_t position) {
+ char size_arr[sizeof(uint32_t)];
+ memcpy(size_arr, &size, sizeof(uint32_t));
+
+ for (size_t idx = 0; idx < sizeof(uint32_t); idx++) {
+ const size_t output__idx = position + idx - sizeof(uint32_t);
+ CHECK_LT(output__idx, output_.size());
+ output_[output__idx] = size_arr[idx];
+ }
+}
+
+void Converter::WriteBytesWritten() {
+ const size_t start_size = PopStartSize();
+ CHECK_LT(start_size, std::numeric_limits<uint32_t>::max());
+ const size_t end_size = output_.size();
+ CHECK_LE(start_size, end_size);
+ const size_t bytes_written = end_size - start_size;
+ CHECK_LT(bytes_written, std::numeric_limits<uint32_t>::max());
+ InsertSize(bytes_written, start_size);
+}
+
+void Converter::WriteString(const std::string str) {
+ WriteNum(str.size());
+ const char* c_str = str.c_str();
+ for (size_t idx = 0; idx < str.size(); idx++)
+ output_.push_back(c_str[idx]);
+
+ output_.push_back('\0'); // Add trailing NULL.
+
+ Pad(str.size() + 1);
+}
+
+void Converter::WriteArray(
+ const google::protobuf::RepeatedField<uint32_t>& repeated_field,
+ const size_t size) {
+ WriteNum(size * sizeof(uint32_t)); // Array size.
+ for (uint32_t element : repeated_field)
+ WriteNum(element);
+ // Padding is not a concern because uint32_ts are 4 bytes.
+}
+
+void Converter::WriteArray(const char* arr, const size_t size) {
+ WriteNum(size);
+ for (size_t idx = 0; idx < size; idx++)
+ output_.push_back(arr[idx]);
+
+ for (unsigned idx = 0; idx < size % 4; idx++)
+ output_.push_back('\0');
+}
+
+void Converter::WriteBool(const bool bool_val) {
+ // bools are usually written as 32 bit integers in skia flattening.
+ WriteNum(static_cast<uint32_t>(bool_val));
+}
+
+void Converter::WriteNum(const char (&num_arr)[4]) {
+ for (size_t idx = 0; idx < 4; idx++)
+ output_.push_back(num_arr[idx]);
+}
+
+void Converter::Visit(const PictureShader& picture_shader) {
+ // PictureShader cannot be autovisited because matrix cannot be.
+ Visit(picture_shader.matrix());
+ WriteFields(picture_shader, 2, 3);
+ Visit(picture_shader.rect());
+ WriteBool(false);
+}
+
+void Converter::Visit(const Message& msg) {
+ WriteFields(msg);
+}
+
+// Visit the Message elements of repeated_field, using the type-specific Visit
+// methods (thanks to templating).
+template <class T>
+void Converter::Visit(
+ const google::protobuf::RepeatedPtrField<T>& repeated_field) {
+ for (const T& single_field : repeated_field)
+ Visit(single_field);
+}
+
+void Converter::Visit(const PictureImageFilter& picture_image_filter) {
+ WriteBool(picture_image_filter.has_picture());
+ if (picture_image_filter.has_picture())
+ Visit(picture_image_filter.picture());
+ // Allow 0x0 rectangles to sometimes be written even though it will mess up
+ // make_localspace_filter.
+ Visit(picture_image_filter.crop_rectangle());
+ if (picture_image_filter.has_picture()) {
+ if (picture_image_filter.picture().info().version() <
+ PictureInfo::kRemoveHeaderFlags_Version)
+
+ WriteNum(picture_image_filter.resolution());
+ }
+}
+
+void Converter::Visit(const PictureData& picture_data) {
+ for (auto& tag : picture_data.tags()) {
+ Visit(tag);
+ }
+ Visit(picture_data.reader_tag());
+
+ WriteNum(kPictEofTag);
+}
+
+void Converter::VisitPictureTag(const PaintPictureTag& paint_picture_tag,
+ uint32_t tag) {
+ WriteNum(tag);
+ WriteNum(1); // Size.
+ Visit(paint_picture_tag.paint());
+}
+
+void Converter::VisitPictureTag(const PathPictureTag& path_picture_tag,
+ uint32_t tag) {
+ WriteNum(tag);
+ WriteNum(1); // Size.
+ WriteNum(1); // Count.
+ Visit(path_picture_tag.path());
+}
+
+template <class T>
+void Converter::VisitPictureTag(const T& picture_tag_child, uint32_t tag) {
+ WriteNum(tag);
+ WriteNum(1);
+ Visit(picture_tag_child);
+}
+
+void Converter::Visit(const ReaderPictureTag& reader) {
+ WriteNum(SET_FOUR_BYTE_TAG('r', 'e', 'a', 'd'));
+ const uint32_t size = sizeof(uint32_t) * (1 + reader.later_bytes_size());
+ WriteNum(size);
+ WriteNum(size);
+ WriteNum(reader.first_bytes());
+ for (auto bytes : reader.later_bytes())
+ WriteNum(bytes);
+}
+
+// Copied from SkPaint.cpp.
+static uint32_t pack_4(unsigned a, unsigned b, unsigned c, unsigned d) {
+ CHECK_EQ(a, (uint8_t)a);
+ CHECK_EQ(b, (uint8_t)b);
+ CHECK_EQ(c, (uint8_t)c);
+ CHECK_EQ(d, (uint8_t)d);
+ return (a << 24) | (b << 16) | (c << 8) | d;
+}
+
+// Copied from SkPaint.cpp.
+static uint32_t pack_paint_flags(unsigned flags,
+ unsigned hint,
+ unsigned align,
+ unsigned filter,
+ unsigned flatFlags) {
+ // left-align the fields of "known" size, and right-align the last (flatFlags)
+ // so it can easily add more bits in the future.
+ return (flags << 16) | (hint << 14) | (align << 12) | (filter << 10) |
+ flatFlags;
+}
+
+bool Converter::IsFinite(float num) const {
+ // If num is inf, -inf, nan or -nan then num*0 will be nan.
+ return !std::isnan(num * 0);
+}
+
+void Converter::Visit(const Paint& paint) {
+ WriteFields(paint, 1, 6);
+
+ uint8_t flat_flags = 0;
+ if (paint.has_effects())
+ flat_flags |= kHasEffects_FlatFlag;
+
+ WriteNum(pack_paint_flags(paint.flags(), paint.hinting(), paint.align(),
+ paint.filter_quality(), flat_flags));
+
+ int style = paint.style();
+ Paint::StrokeCap stroke_cap = paint.stroke_cap();
+
+ if (stroke_style_used_) {
+ style = Paint::kFill_Style;
+ } else if (style == Paint::kStrokeAndFill_Style ||
+ style == Paint::kStroke_Style) {
+ stroke_style_used_ = true;
+ // Avoid timeouts.
+ stroke_cap = Paint::kButt_Cap;
+ }
+
+ uint32_t tmp =
+ pack_4(stroke_cap, paint.stroke_join(),
+ (style << 4) | paint.text_encoding(), paint.blend_mode());
+
+ WriteNum(tmp); // See https://goo.gl/nYJfTy
+
+ if (paint.has_effects())
+ Visit(paint.effects());
+}
+
+void Converter::Visit(const PaintEffects& paint_effects) {
+ // There should be a NULL written for every paint_effects field that is not
+ // set.
+ VISIT_OPT_OR_NULL(paint_effects, path_effect);
+ VISIT_OPT_OR_NULL(paint_effects, shader);
+ VISIT_OPT_OR_NULL(paint_effects, mask_filter);
+ VISIT_OPT_OR_NULL(paint_effects, color_filter);
+ WriteNum(0); // Write ignored number where rasterizer used to be.
+ VISIT_OPT_OR_NULL(paint_effects, looper);
+ VISIT_OPT_OR_NULL(paint_effects, image_filter);
+}
+
+void Converter::Visit(const ColorFilterChild& color_filter_child) {
+ bool flattenable_visited = false;
+ VISIT_ONEOF_FLATTENABLE(color_filter_child,
+ color_matrix_filter_row_major_255);
+
+ if (!in_compose_color_filter_)
+ VISIT_ONEOF_FLATTENABLE(color_filter_child, compose_color_filter);
+
+ VISIT_ONEOF_FLATTENABLE(color_filter_child, srgb_gamma_color_filter);
+ VISIT_ONEOF_FLATTENABLE(color_filter_child, high_contrast__filter);
+ VISIT_ONEOF_FLATTENABLE(color_filter_child, luma_color_filter);
+ VISIT_ONEOF_FLATTENABLE(color_filter_child, overdraw_color_filter);
+ VISIT_ONEOF_FLATTENABLE(color_filter_child, table__color_filter);
+ VISIT_ONEOF_FLATTENABLE(color_filter_child, to_srgb_color_filter);
+ VISIT_DEFAULT_FLATTENABLE(color_filter_child, mode_color_filter);
+}
+
+void Converter::Visit(const Color4f& color_4f) {
+ WriteFields(color_4f);
+}
+
+void Converter::Visit(const GradientDescriptor& gradient_descriptor) {
+ // See SkGradientShaderBase::Descriptor::flatten in SkGradientShader.cpp.
+ enum GradientSerializationFlags {
+ // Bits 29:31 used for various boolean flags
+ kHasPosition_GSF = 0x80000000,
+ kHasLocalMatrix_GSF = 0x40000000,
+ kHasColorSpace_GSF = 0x20000000,
+
+ // Bits 12:28 unused
+
+ // Bits 8:11 for fTileMode
+ kTileModeShift_GSF = 8,
+ kTileModeMask_GSF = 0xF,
+
+ // Bits 0:7 for fGradFlags (note that kForce4fContext_PrivateFlag is 0x80)
+ kGradFlagsShift_GSF = 0,
+ kGradFlagsMask_GSF = 0xFF,
+ };
+
+ uint32_t flags = 0;
+ if (gradient_descriptor.has_pos())
+ flags |= kHasPosition_GSF;
+ if (gradient_descriptor.has_local_matrix())
+ flags |= kHasLocalMatrix_GSF;
+ if (gradient_descriptor.has_color_space())
+ flags |= kHasColorSpace_GSF;
+ flags |= (gradient_descriptor.tile_mode() << kTileModeShift_GSF);
+ uint32_t grad_flags =
+ (gradient_descriptor.grad_flags() % (kGradFlagsMask_GSF + 1));
+ CHECK_LE(grad_flags, kGradFlagsMask_GSF);
+ WriteNum(flags);
+
+ const uint32_t count = gradient_descriptor.colors_size();
+
+ WriteNum(count);
+ for (auto& color : gradient_descriptor.colors())
+ Visit(color);
+
+ Visit(gradient_descriptor.color_space());
+
+ WriteNum(count);
+ for (uint32_t counter = 0; counter < count; counter++)
+ WriteNum(gradient_descriptor.pos());
+
+ Visit(gradient_descriptor.local_matrix());
+}
+
+void Converter::Visit(const GradientParent& gradient_parent) {
+ Visit(gradient_parent.gradient_descriptor());
+}
+
+void Converter::Visit(const ToSRGBColorFilter& to_srgb_color_filter) {
+ Visit(to_srgb_color_filter.color_space());
+}
+
+void Converter::Visit(const LooperChild& looper) {
+ if (PreVisitFlattenable("SkLayerDrawLooper")) {
+ Visit(looper.layer_draw_looper());
+ PostVisitFlattenable();
+ }
+}
+
+// Copied from SkPackBits.cpp.
+static uint8_t* flush_diff8(uint8_t* dst, const uint8_t* src, size_t count) {
+ while (count > 0) {
+ size_t n = count > 128 ? 128 : count;
+ *dst++ = (uint8_t)(n + 127);
+ memcpy(dst, src, n);
+ src += n;
+ dst += n;
+ count -= n;
+ }
+ return dst;
+}
+
+// Copied from SkPackBits.cpp.
+static uint8_t* flush_same8(uint8_t dst[], uint8_t value, size_t count) {
+ while (count > 0) {
+ size_t n = count > 128 ? 128 : count;
+ *dst++ = (uint8_t)(n - 1);
+ *dst++ = (uint8_t)value;
+ count -= n;
+ }
+ return dst;
+}
+
+// Copied from SkPackBits.cpp.
+static size_t compute_max_size8(size_t srcSize) {
+ // Worst case is the number of 8bit values + 1 byte per (up to) 128 entries.
+ return ((srcSize + 127) >> 7) + srcSize;
+}
+
+// Copied from SkPackBits.cpp.
+static size_t pack8(const uint8_t* src,
+ size_t srcSize,
+ uint8_t* dst,
+ size_t dstSize) {
+ if (dstSize < compute_max_size8(srcSize)) {
+ return 0;
+ }
+
+ uint8_t* const origDst = dst;
+ const uint8_t* stop = src + srcSize;
+
+ for (intptr_t count = stop - src; count > 0; count = stop - src) {
+ if (1 == count) {
+ *dst++ = 0;
+ *dst++ = *src;
+ break;
+ }
+
+ unsigned value = *src;
+ const uint8_t* s = src + 1;
+
+ if (*s == value) { // accumulate same values...
+ do {
+ s++;
+ if (s == stop) {
+ break;
+ }
+ } while (*s == value);
+ dst = flush_same8(dst, value, (size_t)(s - src));
+ } else { // accumulate diff values...
+ do {
+ if (++s == stop) {
+ goto FLUSH_DIFF;
+ }
+ // only stop if we hit 3 in a row,
+ // otherwise we get bigger than compuatemax
+ } while (*s != s[-1] || s[-1] != s[-2]);
+ s -= 2; // back up so we don't grab the "same" values that follow
+ FLUSH_DIFF:
+ dst = flush_diff8(dst, src, (size_t)(s - src));
+ }
+ src = s;
+ }
+ return dst - origDst;
+}
+
+const uint8_t* Converter::ColorTableToArray(const ColorTable& color_table) {
+ float* dst = reinterpret_cast<float*>(kColorTableBuffer);
+ const int array_size = 64;
+ // Now write the 256 fields.
+ const Descriptor* descriptor = color_table.GetDescriptor();
+ CHECK(descriptor);
+ const Reflection* reflection = color_table.GetReflection();
+ CHECK(reflection);
+ for (int field_num = 1; field_num <= array_size; field_num++, dst++) {
+ const FieldDescriptor* field_descriptor =
+ descriptor->FindFieldByNumber(field_num);
+ CHECK(field_descriptor);
+ *dst = BoundFloat(reflection->GetFloat(color_table, field_descriptor));
+ }
+ return kColorTableBuffer;
+}
+
+void Converter::Visit(const Table_ColorFilter& table__color_filter) {
+ // See SkTable_ColorFilter::SkTable_ColorFilter
+ enum {
+ kA_Flag = 1 << 0,
+ kR_Flag = 1 << 1,
+ kG_Flag = 1 << 2,
+ kB_Flag = 1 << 3,
+ };
+ unsigned flags = 0;
+ uint8_t f_storage[4 * kColorTableBufferLength];
+ uint8_t* dst = f_storage;
+
+ if (table__color_filter.has_table_a()) {
+ memcpy(dst, ColorTableToArray(table__color_filter.table_a()),
+ kColorTableBufferLength);
+
+ dst += kColorTableBufferLength;
+ flags |= kA_Flag;
+ }
+ if (table__color_filter.has_table_r()) {
+ memcpy(dst, ColorTableToArray(table__color_filter.table_r()),
+ kColorTableBufferLength);
+
+ dst += kColorTableBufferLength;
+ flags |= kR_Flag;
+ }
+ if (table__color_filter.has_table_g()) {
+ memcpy(dst, ColorTableToArray(table__color_filter.table_g()),
+ kColorTableBufferLength);
+
+ dst += kColorTableBufferLength;
+ flags |= kG_Flag;
+ }
+ if (table__color_filter.has_table_b()) {
+ memcpy(dst, ColorTableToArray(table__color_filter.table_b()),
+ kColorTableBufferLength);
+
+ dst += kColorTableBufferLength;
+ flags |= kB_Flag;
+ }
+ uint8_t storage[5 * kColorTableBufferLength];
+ const int count = kCountNibBits[flags & 0xF];
+ const size_t size = pack8(f_storage, count * kColorTableBufferLength, storage,
+ sizeof(storage));
+
+ CHECK_LE(flags, UINT32_MAX);
+ const uint32_t flags_32 = (uint32_t)flags;
+ WriteNum(flags_32);
+ WriteNum((uint32_t)size);
+ for (size_t idx = 0; idx < size; idx++)
+ output_.push_back(storage[idx]);
+ Pad(output_.size());
+}
+
+void Converter::Visit(const ComposeColorFilter& compose_color_filter) {
+ CHECK(!in_compose_color_filter_);
+ in_compose_color_filter_ = true;
+ Visit(compose_color_filter.outer());
+ Visit(compose_color_filter.inner());
+ in_compose_color_filter_ = false;
+}
+
+void Converter::Visit(const OverdrawColorFilter& overdraw_color_filter) {
+ // This is written as a byte array (length-in-bytes followed by data).
+ const uint32_t num_fields = 6;
+ const uint32_t arr_size = num_fields * sizeof(uint32_t);
+ WriteNum(arr_size);
+ WriteFields(overdraw_color_filter);
+}
+
+void Converter::Visit(
+ const ColorMatrixFilterRowMajor255& color_matrix_filter_row_major_255) {
+ Visit(color_matrix_filter_row_major_255.color_filter_matrix());
+}
+
+void Converter::Visit(const ColorFilterMatrix& color_filter_matrix) {
+ static const int kColorFilterMatrixNumFields = 20;
+ WriteNum(kColorFilterMatrixNumFields);
+ WriteFields(color_filter_matrix);
+}
+
+void Converter::Visit(const LayerDrawLooper& layer_draw_looper) {
+ WriteNum(layer_draw_looper.layer_infos_size());
+ for (auto& layer_info : layer_draw_looper.layer_infos()) {
+ Visit(layer_info);
+#ifdef AVOID_MISBEHAVIOR
+ break; // Only write 1 to avoid timeouts.
+#endif
+ }
+}
+
+void Converter::Visit(const LayerInfo& layer_info) {
+ WriteNum(0);
+ // Don't mutate these enum values or else a crash will be caused
+ bool initial = dont_mutate_enum_;
+ dont_mutate_enum_ = true;
+ WriteFields(layer_info, 1, 4);
+ dont_mutate_enum_ = initial;
+ Visit(layer_info.paint());
+}
+
+void Converter::Visit(const PairPathEffect& pair) {
+ // Don't allow nesting of PairPathEffects for performance reasons
+ if (pair_path_effect_depth_ >= 1)
+ return;
+ if (flattenable_depth_ > kFlattenableDepthLimit)
+ return;
+ pair_path_effect_depth_ += 1;
+ flattenable_depth_ += 1;
+
+ std::string name;
+ if (pair.type() == PairPathEffect::SUM)
+ name = "SkSumPathEffect";
+ else
+ name = "SkComposePathEffect";
+ WriteString(name);
+ RecordSize();
+
+ Visit(pair.path_effect_1());
+ Visit(pair.path_effect_2());
+
+ WriteBytesWritten(); // Flattenable size.
+ CheckAlignment();
+ pair_path_effect_depth_ -= 1;
+ flattenable_depth_ -= 1;
+}
+
+// See SkPathRef::writeToBuffer
+void Converter::Visit(const PathRef& path_ref) {
+ // Bound segment_mask to avoid timeouts and for proper behavior.
+ const int32_t packed =
+ (((path_ref.is_finite() & 1) << kIsFinite_SerializationShift) |
+ (ToUInt8(path_ref.segment_mask()) << kSegmentMask_SerializationShift));
+
+ WriteNum(packed);
+ WriteNum(0);
+ std::vector<SkPoint> points;
+ if (path_ref.verbs_size()) {
+ WriteNum(path_ref.verbs_size() + 1);
+ uint32_t num_points = 1; // The last move will add 1 point.
+ uint32_t num_conics = 0;
+ for (auto& verb : path_ref.verbs()) {
+ switch (verb.value()) {
+ case ValidVerb::kMove_Verb:
+ case ValidVerb::kLine_Verb:
+ num_points += 1;
+ break;
+ case ValidVerb::kConic_Verb:
+ num_conics += 1;
+ FALLTHROUGH;
+ case ValidVerb::kQuad_Verb:
+ num_points += 2;
+ break;
+ case ValidVerb::kCubic_Verb:
+ num_points += 3;
+ break;
+ case ValidVerb::kClose_Verb:
+ break;
+ default:
+ NOTREACHED();
+ }
+ }
+ WriteNum(num_points);
+ WriteNum(num_conics);
+ } else {
+ WriteNum(0);
+ WriteNum(0);
+ WriteNum(0);
+ }
+
+ for (auto& verb : path_ref.verbs()) {
+ const uint8_t value = verb.value();
+ WriteNum(value);
+ }
+ // Verbs must start (they are written backwards) with kMove_Verb (0).
+ if (path_ref.verbs_size()) {
+ uint8_t value = ValidVerb::kMove_Verb;
+ WriteNum(value);
+ }
+
+ // Write points
+ for (auto& verb : path_ref.verbs()) {
+ switch (verb.value()) {
+ case ValidVerb::kMove_Verb:
+ case ValidVerb::kLine_Verb: {
+ Visit(verb.point1());
+ AppendAsSkPoint(points, verb.point1());
+ break;
+ }
+ case ValidVerb::kConic_Verb:
+ case ValidVerb::kQuad_Verb: {
+ Visit(verb.point1());
+ Visit(verb.point2());
+ AppendAsSkPoint(points, verb.point1());
+ AppendAsSkPoint(points, verb.point2());
+ break;
+ }
+ case ValidVerb::kCubic_Verb:
+ Visit(verb.point1());
+ Visit(verb.point2());
+ Visit(verb.point3());
+ AppendAsSkPoint(points, verb.point1());
+ AppendAsSkPoint(points, verb.point2());
+ AppendAsSkPoint(points, verb.point3());
+ break;
+ default:
+ break;
+ }
+ }
+ // Write point of the Move Verb we put at the end.
+ if (path_ref.verbs_size()) {
+ Visit(path_ref.first_verb().point1());
+ AppendAsSkPoint(points, path_ref.first_verb().point1());
+ }
+
+ // Write conic weights.
+ for (auto& verb : path_ref.verbs()) {
+ if (verb.value() == ValidVerb::kConic_Verb)
+ WriteNum(verb.conic_weight());
+ }
+
+ SkRect skrect;
+ skrect.setBoundsCheck(&points[0], points.size());
+ WriteNum(skrect.fLeft);
+ WriteNum(skrect.fTop);
+ WriteNum(skrect.fRight);
+ WriteNum(skrect.fBottom);
+}
+
+void Converter::AppendAsSkPoint(std::vector<SkPoint>& sk_points,
+ const Point& proto_point) const {
+ SkPoint sk_point;
+ sk_point.fX = proto_point.x();
+ sk_point.fY = proto_point.y();
+ sk_points.push_back(sk_point);
+}
+
+void Converter::Visit(const Path& path) {
+ enum SerializationVersions {
+ kPathPrivFirstDirection_Version = 1,
+ kPathPrivLastMoveToIndex_Version = 2,
+ kPathPrivTypeEnumVersion = 3,
+ kCurrent_Version = 3
+ };
+
+ enum FirstDirection {
+ kCW_FirstDirection,
+ kCCW_FirstDirection,
+ kUnknown_FirstDirection,
+ };
+
+ int32_t packed = (path.convexity() << kConvexity_SerializationShift) |
+ (path.fill_type() << kFillType_SerializationShift) |
+ (path.first_direction() << kDirection_SerializationShift) |
+ (path.is_volatile() << kIsVolatile_SerializationShift) |
+ kCurrent_Version;
+
+ // TODO(metzman): Allow writing as RRect.
+ WriteNum(packed);
+ WriteNum(path.last_move_to_index());
+ Visit(path.path_ref());
+ Pad(output_.size());
+ CheckAlignment();
+}
+
+void Converter::Visit(const RRectsGaussianEdgeMaskFilterImpl&
+ r_rects_gaussian_edge_mask_filter_impl) {
+ Visit(r_rects_gaussian_edge_mask_filter_impl.rect_1());
+ WriteFields(r_rects_gaussian_edge_mask_filter_impl, 2, 3);
+ Visit(r_rects_gaussian_edge_mask_filter_impl.rect_2());
+ WriteFields(r_rects_gaussian_edge_mask_filter_impl, 5);
+}
+
+void Converter::Visit(const BlurMaskFilter& blur_mask_filter) {
+ // Sigma must be a finite number <= 0.
+ float sigma = fabs(BoundFloat(blur_mask_filter.sigma()));
+ sigma = 1 ? sigma == 0 : sigma;
+ WriteNum(sigma);
+ const bool old_value = dont_mutate_enum_;
+ dont_mutate_enum_ = true;
+ WriteFields(blur_mask_filter, 2, 3);
+ dont_mutate_enum_ = old_value;
+ Visit(blur_mask_filter.occluder());
+}
+
+void Converter::CheckAlignment() const {
+ CHECK_EQ(output_.size() % 4, static_cast<size_t>(0));
+}
+
+void Converter::Visit(const ShaderChild& shader) {
+ bool flattenable_visited = false;
+ VISIT_ONEOF_FLATTENABLE(shader, color_4_shader);
+ VISIT_ONEOF_FLATTENABLE(shader, color_filter_shader);
+ VISIT_ONEOF_FLATTENABLE(shader, image_shader);
+ VISIT_ONEOF_FLATTENABLE(shader, compose_shader);
+ VISIT_ONEOF_FLATTENABLE(shader, empty_shader);
+ VISIT_ONEOF_FLATTENABLE(shader, picture_shader);
+ VISIT_ONEOF_FLATTENABLE(shader, perlin_noise_shader_impl);
+ VISIT_ONEOF_FLATTENABLE(shader, local_matrix_shader);
+ VISIT_ONEOF_FLATTENABLE(shader, linear_gradient);
+ VISIT_ONEOF_FLATTENABLE(shader, radial_gradient);
+ VISIT_ONEOF_FLATTENABLE(shader, sweep_gradient);
+ VISIT_ONEOF_FLATTENABLE(shader, two_point_conical_gradient);
+ VISIT_DEFAULT_FLATTENABLE(shader, color_shader);
+}
+
+void Converter::Visit(
+ const TwoPointConicalGradient& two_point_conical_gradient) {
+ Visit(two_point_conical_gradient.parent());
+ WriteFields(two_point_conical_gradient, 2, 5);
+}
+
+void Converter::Visit(const LinearGradient& linear_gradient) {
+ Visit(linear_gradient.parent());
+ WriteFields(linear_gradient, 2, 3);
+}
+
+void Converter::Visit(const SweepGradient& sweep_gradient) {
+ Visit(sweep_gradient.parent());
+ WriteFields(sweep_gradient, 2, 4);
+}
+
+void Converter::Visit(const RadialGradient& radial_gradient) {
+ Visit(radial_gradient.parent());
+ WriteFields(radial_gradient, 2, 3);
+}
+
+// Don't compile unfinished (dead) code in production.
+#ifdef DEVELOPMENT
+// ICC handling code is unfinished.
+// TODO(metzman): Finish implementing ICC.
+
+// Copied from https://goo.gl/j78F6Z
+static constexpr uint32_t kTAG_lut8Type = SET_FOUR_BYTE_TAG('m', 'f', 't', '1');
+static constexpr uint32_t kTAG_lut16Type =
+ SET_FOUR_BYTE_TAG('m', 'f', 't', '2');
+void Converter::Visit(const ICC& icc) {
+ icc_base_ = output_.size();
+ const uint32_t header_size = sizeof(uint8_t) * 4;
+ uint32_t tag_count = 0;
+ uint32_t tags_size = 0;
+ if (icc.color_space().has_a2b0()) {
+ if (icc.color_space().a2b0().has_lut8()) {
+ tags_size =
+ GetLut8Size(icc.color_space().a2b0().lut8()) + kICCTagTableEntrySize;
+ } else if (icc.color_space().a2b0().has_lut16()) {
+ tags_size = GetLut16Size(icc.color_space().a2b0().lut16()) +
+ kICCTagTableEntrySize;
+ } else {
+ NOTREACHED();
+ }
+ tag_count = 1;
+ } else {
+ NOTREACHED();
+ }
+
+ const uint32_t profile_size = sizeof(float) * 33 + tags_size;
+ const uint32_t size = profile_size + sizeof(profile_size) + header_size;
+ WriteNum(size);
+
+ // Header.
+ WriteColorSpaceVersion();
+ WriteNum(ToUInt8(icc.named()));
+ WriteNum(ToUInt8(GammaNamed::kNonStandard_SkGammaNamed));
+ WriteNum(kICC_Flag);
+
+ WriteNum(profile_size);
+ WriteBigEndian(profile_size);
+ WriteIgnoredFields(1);
+ uint32_t version = icc.version() % 5;
+ version <<= 24;
+ WriteBigEndian(version);
+ WriteBigEndian(kProfileLookupTable[icc.profile_class()]);
+ WriteBigEndian(kInputColorSpaceLookupTable[icc.input_color_space()]);
+ WriteBigEndian(kPCSLookupTable[icc.pcs()]);
+ WriteIgnoredFields(3);
+ WriteBigEndian(SET_FOUR_BYTE_TAG('a', 'c', 's', 'p'));
+ WriteIgnoredFields(6);
+ WriteBigEndian(icc.rendering_intent());
+ WriteBigEndian(BoundIlluminant(icc.illuminant_x(), 0.96420f));
+ WriteBigEndian(BoundIlluminant(icc.illuminant_y(), 1.00000f));
+ WriteBigEndian(BoundIlluminant(icc.illuminant_z(), 0.82491f));
+ WriteIgnoredFields(12);
+ Visit(icc.color_space());
+ const unsigned new_size = output_.size();
+ CHECK_EQ(static_cast<size_t>(new_size - icc_base_), size + sizeof(size));
+}
+
+void Converter::WriteTagSize(const char (&tag)[4], const size_t size) {
+ WriteNum(tag);
+ WriteNum(size);
+}
+
+// Writes num as a big endian number.
+template <typename T>
+void Converter::WriteBigEndian(const T num) {
+ CHECK_LE(sizeof(T), static_cast<size_t>(4));
+ uint8_t num_arr[sizeof(T)];
+ memcpy(num_arr, &num, sizeof(T));
+ uint8_t tmp1 = num_arr[0];
+ uint8_t tmp2 = num_arr[3];
+ num_arr[3] = tmp1;
+ num_arr[0] = tmp2;
+
+ tmp1 = num_arr[1];
+ tmp2 = num_arr[2];
+ num_arr[2] = tmp1;
+ num_arr[1] = tmp2;
+
+ for (size_t idx = 0; idx < sizeof(uint32_t); idx++)
+ output_.push_back(num_arr[idx]);
+}
+
+void Converter::Visit(const ICCColorSpace& icc_color_space) {
+ if (icc_color_space.has_xyz())
+ Visit(icc_color_space.xyz());
+ else if (icc_color_space.has_gray())
+ Visit(icc_color_space.gray());
+ else
+ Visit(icc_color_space.a2b0());
+}
+
+void Converter::Visit(const ICCXYZ& icc_xyz) {}
+
+void Converter::Visit(const ICCGray& icc_gray) {}
+
+void Converter::Visit(const ICCA2B0& icc_a2b0) {
+ if (icc_a2b0.has_lut8())
+ Visit(icc_a2b0.lut8());
+ else if (icc_a2b0.has_lut16())
+ Visit(icc_a2b0.lut16());
+ else
+ Visit(icc_a2b0.atob());
+}
+
+void Converter::Visit(const ICCA2B0AToB& icc_a2b0_atob) {}
+
+uint8_t Converter::GetClutGridPoints(const ICCA2B0Lut8& icc_a2b0_lut8) {
+ uint8_t clut_grid_points = icc_a2b0_lut8.clut_grid_points();
+ return clut_grid_points ? clut_grid_points > 1 : 2;
+}
+
+uint32_t Converter::GetLut8Size(const ICCA2B0Lut8& icc_a2b0_lut8) {
+ const uint32_t num_entries =
+ GetClutGridPoints(icc_a2b0_lut8) * icc_a2b0_lut8.output_channels();
+
+ const uint32_t clut_bytes = kLut8Precision * num_entries * 4;
+ const uint32_t gammas_size =
+ kOneChannelGammasSize * (3 + icc_a2b0_lut8.input_channels());
+ return kLut8InputSize + gammas_size + clut_bytes;
+}
+
+uint32_t Converter::GetLut16Size(const ICCA2B0Lut16& icc_a2b0_lut16) {
+ return 48;
+}
+
+void Converter::Visit(const ICCA2B0Lut8& icc_a2b0_lut8) {
+ // Write Header.
+ WriteA2B0TagCommon();
+
+ // Write length.
+ WriteBigEndian(GetLut8Size(icc_a2b0_lut8));
+ // Specify type.
+ WriteBigEndian(kTAG_lut8Type); // Bytes 0-3.
+ WriteLut8(icc_a2b0_lut8);
+ Visit(icc_a2b0_lut8.input_gammas_1());
+ if (icc_a2b0_lut8.input_channels() == 2) {
+ Visit(icc_a2b0_lut8.input_gammas_2());
+ } else if (icc_a2b0_lut8.input_channels() == 3) {
+ Visit(icc_a2b0_lut8.input_gammas_2());
+ Visit(icc_a2b0_lut8.input_gammas_3());
+ }
+
+ std::mt19937 gen(icc_a2b0_lut8.clut_bytes_seed());
+ const uint32_t clut_bytes = GetClutGridPoints(icc_a2b0_lut8) *
+ icc_a2b0_lut8.output_channels() * kLut8Precision *
+ 4;
+ for (uint32_t i = 0; i < clut_bytes; i++)
+ WriteUInt8(static_cast<uint8_t>(gen()));
+
+ Visit(icc_a2b0_lut8.output_gammas());
+}
+
+// Write the parts of a lut8 used by a lut16.
+void Converter::WriteLut8(const ICCA2B0Lut8& icc_a2b0_lut8) {
+ // Bytes 4-7 are ignored.
+ WriteUInt8(icc_a2b0_lut8.ignored_byte_4());
+ WriteUInt8(icc_a2b0_lut8.ignored_byte_5());
+ WriteUInt8(icc_a2b0_lut8.ignored_byte_6());
+ WriteUInt8(icc_a2b0_lut8.ignored_byte_7());
+ WriteUInt8(icc_a2b0_lut8.input_channels()); // Byte 8.
+ WriteUInt8(icc_a2b0_lut8.output_channels()); // Byte 9.
+ WriteUInt8(GetClutGridPoints(icc_a2b0_lut8)); // Byte 10.
+ WriteUInt8(icc_a2b0_lut8.ignored_byte_11());
+ Visit(icc_a2b0_lut8.matrix());
+}
+
+void Converter::WriteA2B0TagCommon() {
+ WriteBigEndian(1); // ICC Tag Count
+ WriteBigEndian(kTagLookupTable[ICCTag::kTAG_A2B0]);
+ WriteBigEndian(GetCurrentICCOffset() - 4); // Offset.
+}
+
+void Converter::WriteIgnoredFields(const int num_fields) {
+ CHECK_GE(num_fields, 1);
+ for (int counter = 0; counter < num_fields; counter++)
+ WriteNum(0);
+}
+
+int32_t Converter::BoundIlluminant(float illuminant, const float num) const {
+ while (fabs(illuminant) >= 1) {
+ illuminant /= 10;
+ }
+ const float result = num + 0.01f * illuminant;
+ CHECK_LT(fabs(num - result), .01f);
+ // 1.52587890625e-5f is a hardcoded value from SkFixed.h.
+ return round(result / 1.52587890625e-5f);
+}
+
+uint32_t Converter::GetCurrentICCOffset() {
+ return output_.size() - icc_base_;
+}
+
+void Converter::Visit(const ICCA2B0Lut16& icc_a2b0_lut16) {
+ // Write Tag Header
+ WriteA2B0TagCommon();
+
+ WriteBigEndian(GetLut16Size(icc_a2b0_lut16));
+ WriteBigEndian(kTAG_lut16Type); // Bytes 0-3.
+ WriteLut8(icc_a2b0_lut16.lut8());
+
+ uint16_t in_entries =
+ icc_a2b0_lut16.in_table_entries() % (kMaxLut16GammaEntries + 1);
+
+ in_entries = in_entries ? in_entries >= 1 : 2;
+
+ uint16_t out_entries =
+ icc_a2b0_lut16.out_table_entries() % (kMaxLut16GammaEntries + 1);
+
+ out_entries = out_entries ? out_entries >= 1 : 2;
+
+ WriteUInt16(static_cast<uint16_t>(in_entries));
+ WriteUInt16(static_cast<uint16_t>(out_entries));
+}
+
+void Converter::WriteTagHeader(const uint32_t tag, const uint32_t len) {
+ WriteBigEndian(kTagLookupTable[tag]);
+ WriteBigEndian(tag_offset_);
+ WriteBigEndian(len);
+ tag_offset_ += 12;
+}
+
+// ImageInfo related code.
+// Copied from SkImageInfo.h
+static int SkColorTypeBytesPerPixel(uint8_t ct) {
+ static const uint8_t gSize[] = {
+ 0, // Unknown
+ 1, // Alpha_8
+ 2, // RGB_565
+ 2, // ARGB_4444
+ 4, // RGBA_8888
+ 4, // BGRA_8888
+ 1, // kGray_8
+ 8, // kRGBA_F16
+ };
+ return gSize[ct];
+}
+
+size_t Converter::ComputeMinByteSize(int32_t width,
+ int32_t height,
+ ImageInfo::AlphaType alpha_type) const {
+ width = Abs(width);
+ height = Abs(height);
+
+ if (!height)
+ return 0;
+ uint32_t bytes_per_pixel = SkColorTypeBytesPerPixel(alpha_type);
+ uint64_t bytes_per_row_64 = width * bytes_per_pixel;
+ CHECK(bytes_per_row_64 <= INT32_MAX);
+ int32_t bytes_per_row = bytes_per_row_64;
+ size_t num_bytes = (height - 1) * bytes_per_row + bytes_per_pixel * width;
+ return num_bytes;
+}
+
+std::tuple<int32_t, int32_t, int32_t> Converter::GetNumPixelBytes(
+ const ImageInfo& image_info,
+ int32_t width,
+ int32_t height) {
+ // Returns a value for pixel bytes that is divisible by four by modifying
+ // image_info.width() as needed until the computed min byte size is divisible
+ // by four.
+ size_t num_bytes_64 =
+ ComputeMinByteSize(width, height, image_info.alpha_type());
+ CHECK(num_bytes_64 <= INT32_MAX);
+ int32_t num_bytes = num_bytes_64;
+ bool subtract = (num_bytes >= 5);
+ while (num_bytes % 4) {
+ if (subtract)
+ width -= 1;
+ else
+ width += 1;
+ num_bytes_64 = ComputeMinByteSize(width, height, image_info.alpha_type());
+ CHECK(num_bytes_64 <= INT32_MAX);
+ num_bytes = num_bytes_64;
+ }
+ return std::make_tuple(num_bytes, width, height);
+}
+
+void Converter::Visit(const ImageInfo& image_info,
+ const int32_t width,
+ const int32_t height) {
+ WriteNum(width);
+ WriteNum(height);
+ uint32_t packed = (image_info.alpha_type() << 8) | image_info.color_type();
+ WriteNum(packed);
+ Visit(image_info.color_space());
+}
+#endif // DEVELOPMENT
+
+void Converter::Visit(const ColorSpaceChild& color_space) {
+// ICC code is not finished.
+#ifdef DEVELOPMENT
+ if (color_space.has_icc())
+ Visit(color_space.icc());
+ else if (color_space.has_transfer_fn())
+#else
+ if (color_space.has_transfer_fn())
+#endif // DEVELOPMENT
+ Visit(color_space.transfer_fn());
+ else if (color_space.has_color_space__xyz())
+ Visit(color_space.color_space__xyz());
+ else
+ Visit(color_space.named());
+}
+
+template <typename T>
+void Converter::WriteUInt8(T num) {
+ CHECK_LT(num, 256);
+ output_.push_back(static_cast<uint8_t>(num));
+}
+
+void Converter::WriteUInt16(uint16_t num) {
+ char num_arr[2];
+ memcpy(num_arr, &num, 2);
+ for (size_t idx = 0; idx < 2; idx++)
+ output_.push_back(num_arr[idx]);
+}
+
+void Converter::Visit(const TransferFn& transfer_fn) {
+ const size_t size_64 =
+ (12 * sizeof(float) + 7 * sizeof(float) + 4 * sizeof(uint8_t));
+ CHECK_LT(size_64, UINT32_MAX);
+ WriteNum((uint32_t)size_64);
+ // Header
+ WriteColorSpaceVersion();
+ WriteNum(ToUInt8(transfer_fn.named()));
+ WriteNum(ToUInt8(GammaNamed::kNonStandard_SkGammaNamed));
+ WriteNum(ToUInt8(kTransferFn_Flag));
+
+ WriteFields(transfer_fn, 2);
+}
+
+void Converter::WriteColorSpaceVersion() {
+ // See SkColorSpace::writeToMemory for why this always writes k0_Version.
+ // TODO(metzman): Figure out how to keep this up to date.
+ WriteNum(k0_Version);
+}
+
+void Converter::Visit(const ColorSpace_XYZ& color_space__xyz) {
+ const uint32_t size = 12 * sizeof(float) + sizeof(uint8_t) * 4;
+ WriteNum(size);
+ // Header
+ WriteColorSpaceVersion();
+ WriteNum(ToUInt8(Named::kSRGB_Named));
+ WriteNum(ToUInt8(color_space__xyz.gamma_named()));
+ // See SkColorSpace.cpp:Deserialize (around here: https://goo.gl/R9xQ2B)
+ WriteNum(ToUInt8(kMatrix_Flag));
+
+ Visit(color_space__xyz.three_by_four());
+}
+
+void Converter::Visit(const ColorSpaceNamed& color_space_named) {
+ const uint32_t size = sizeof(uint8_t) * 4;
+ WriteNum(size);
+ // Header
+ WriteColorSpaceVersion();
+ WriteNum(ToUInt8(color_space_named.named()));
+ WriteNum(ToUInt8(color_space_named.gamma_named()));
+ WriteNum(ToUInt8(0));
+}
+
+void Converter::Visit(const ImageData& image_data) {
+ WriteNum(-4 * image_data.data_size());
+ for (uint32_t element : image_data.data())
+ WriteNum(element);
+}
+
+void Converter::Visit(const Image& image) {
+ // Width and height must be greater than 0.
+ WriteNum(std::max(1, BoundNum(Abs(image.width()))));
+ WriteNum(std::max(1, BoundNum(Abs(image.height()))));
+
+ Visit(image.data());
+ if (image.data().data_size()) {
+ // origin_x and origin_y need to be positive.
+ WriteNum(Abs(image.origin_x()));
+ WriteNum(Abs(image.origin_y()));
+ }
+}
+
+void Converter::Visit(const ImageShader& image_shader) {
+ WriteFields(image_shader, 1, 3);
+ Visit(image_shader.image());
+}
+
+void Converter::Visit(const ColorFilterShader& color_filter_shader) {
+ Visit(color_filter_shader.shader());
+ Visit(color_filter_shader.filter());
+}
+
+void Converter::Visit(const ComposeShader& compose_shader) {
+ if (flattenable_depth_ > kFlattenableDepthLimit)
+ return;
+ flattenable_depth_ += 1;
+ Visit(compose_shader.dst());
+ Visit(compose_shader.src());
+ WriteFields(compose_shader, 3, 4);
+ flattenable_depth_ -= 1;
+}
+
+void Converter::Visit(const LocalMatrixShader& local_matrix_shader) {
+ Visit(local_matrix_shader.matrix());
+ Visit(local_matrix_shader.proxy_shader());
+}
+
+void Converter::Visit(const Color4Shader& color_4_shader) {
+ WriteNum(color_4_shader.color());
+ // TODO(metzman): Implement ColorSpaces when skia does. See
+ // https://goo.gl/c6YAq7
+ WriteBool(false);
+}
+
+void Converter::Pad(const size_t write_size) {
+ if (write_size % 4 == 0)
+ return;
+ for (size_t padding_count = 0; (padding_count + write_size) % 4 != 0;
+ padding_count++)
+ output_.push_back('\0');
+}
+
+void Converter::Visit(const Path1DPathEffect& path_1d_path_effect) {
+ WriteNum(path_1d_path_effect.advance());
+ if (path_1d_path_effect.advance()) {
+ Visit(path_1d_path_effect.path());
+ WriteFields(path_1d_path_effect, 3, 4);
+ }
+}
+
+bool Converter::PreVisitFlattenable(const std::string& name) {
+ if (flattenable_depth_ > kFlattenableDepthLimit)
+ return false;
+ flattenable_depth_ += 1;
+ WriteString(name);
+ RecordSize();
+ return true;
+}
+
+void Converter::PostVisitFlattenable() {
+ WriteBytesWritten(); // Flattenable size.
+ CheckAlignment();
+ flattenable_depth_ -= 1;
+}
+
+void Converter::Visit(const DashImpl& dash_impl) {
+ WriteNum(BoundFloat(dash_impl.phase()));
+ int num_left = dash_impl.intervals_size();
+ int size = dash_impl.intervals_size() + 2;
+ if (size % 2) {
+ num_left = num_left - 1;
+ size = size - 1;
+ }
+ WriteNum(size);
+ WriteNum(fabs(BoundFloat(dash_impl.interval_1())));
+ WriteNum(fabs(BoundFloat(dash_impl.interval_2())));
+ for (int idx = 0; idx < num_left; idx++)
+ WriteNum(fabs(BoundFloat(dash_impl.intervals().Get(idx))));
+}
+
+void Converter::Visit(const Path2DPathEffect& path_2d_path_effect) {
+ Visit(path_2d_path_effect.matrix());
+ Visit(path_2d_path_effect.path());
+}
+
+void Converter::Visit(const PathEffectChild& path_effect) {
+ bool flattenable_visited = false;
+ // Visit(pair_path_effect) implements the functionality of
+ // VisitFlattenable by writing the correct names itself.
+ if (path_effect.has_pair_path_effect()) {
+ Visit(path_effect.pair_path_effect());
+ flattenable_visited = true;
+ }
+ VISIT_ONEOF_FLATTENABLE(path_effect, path_2d_path_effect);
+ VISIT_ONEOF_FLATTENABLE(path_effect, line_2d_path_effect);
+ VISIT_ONEOF_FLATTENABLE(path_effect, corner_path_effect);
+ VISIT_ONEOF_FLATTENABLE(path_effect, discrete_path_effect);
+ VISIT_ONEOF_FLATTENABLE(path_effect, path_1d_path_effect);
+ VISIT_DEFAULT_FLATTENABLE(path_effect, dash_impl);
+}
+
+void Converter::Visit(const DiscretePathEffect& discrete_path_effect) {
+ // Don't write seg_length because it causes too many timeouts.
+ // See SkScalar.h for why this value is picked
+ const float SK_ScalarNotNearlyZero = 1.0 / (1 << 11);
+ WriteNum(SK_ScalarNotNearlyZero);
+ // Found in testing to be a good value that is unlikely to cause timeouts.
+ float perterb = discrete_path_effect.perterb();
+ // Do this to avoid timeouts.
+ if (perterb < 1)
+ perterb += 1;
+ WriteNum(perterb);
+ WriteNum(discrete_path_effect.seed_assist());
+}
+
+void Converter::Visit(const MaskFilterChild& mask_filter) {
+ bool flattenable_visited = false;
+ VISIT_ONEOF_FLATTENABLE(mask_filter, emboss_mask_filter);
+ VISIT_ONEOF_FLATTENABLE(mask_filter, r_rects_gaussian_edge_mask_filter_impl);
+ VISIT_DEFAULT_FLATTENABLE(mask_filter, blur_mask_filter_impl);
+}
+
+template <typename T>
+uint8_t Converter::ToUInt8(const T input_num) const {
+ return input_num % (UINT8_MAX + 1);
+}
+
+void Converter::Visit(const EmbossMaskFilterLight& emboss_mask_filter_light) {
+ // This is written as a byte array, so first write its size, direction_* are
+ // floats, fPad is uint16_t and ambient and specular are uint8_ts.
+ const uint32_t byte_array_size =
+ (3 * sizeof(float) + sizeof(uint16_t) + (2 * sizeof(uint8_t)));
+ WriteNum(byte_array_size);
+ WriteFields(emboss_mask_filter_light, 1, 3);
+ const uint16_t pad = 0;
+ WriteNum(pad); // fPad = 0;
+ WriteNum(ToUInt8(emboss_mask_filter_light.ambient()));
+ WriteNum(ToUInt8(emboss_mask_filter_light.specular()));
+}
+
+void Converter::Visit(const EmbossMaskFilter& emboss_mask_filter) {
+ Visit(emboss_mask_filter.light());
+ WriteNum(emboss_mask_filter.blur_sigma());
+}
+
+void Converter::Visit(const RecordingData& recording_data) {
+ WriteNum(kSkPictReaderTag);
+ Visit(recording_data.paints());
+}
+
+void Converter::Visit(const PictureTagChild& picture_tag) {
+ VISIT_OPT_TAG(paint, SET_FOUR_BYTE_TAG('p', 'n', 't', ' '));
+ VISIT_OPT_TAG(path, SET_FOUR_BYTE_TAG('p', 't', 'h', ' '));
+ VISIT_OPT_TAG(image, SET_FOUR_BYTE_TAG('i', 'm', 'a', 'g'));
+ VISIT_OPT_TAG(vertices, SET_FOUR_BYTE_TAG('v', 'e', 'r', 't'));
+ VISIT_OPT_TAG(text_blob, SET_FOUR_BYTE_TAG('b', 'l', 'o', 'b'));
+}
+
+void Converter::Visit(const Picture& picture) {
+ Visit(picture.info());
+ WriteNum(1);
+ Visit(picture.data());
+}
+
+void Converter::Visit(const Matrix& matrix, bool is_local) {
+ // Avoid OOMs by making sure that matrix fields aren't tiny fractions.
+ WriteMatrixField(matrix.val1());
+ WriteMatrixField(matrix.val2());
+ WriteMatrixField(matrix.val3());
+ WriteMatrixField(matrix.val4());
+ WriteMatrixField(matrix.val5());
+ WriteMatrixField(matrix.val6());
+ // See SkLocalMatrixImageFilter.cpp:20
+ if (is_local)
+ WriteNum(0.0f);
+ else
+ WriteMatrixField(matrix.val7());
+ if (is_local)
+ WriteNum(0.0f);
+ else
+ WriteMatrixField(matrix.val8());
+ if (is_local)
+ WriteNum(1.0f);
+ else
+ WriteMatrixField(matrix.val9());
+}
+
+void Converter::WriteMatrixField(float field_value) {
+ // Don't let the field values be tiny fractions.
+ field_value = BoundFloat(field_value);
+ while ((field_value > 0 && field_value < 1e-5) ||
+ (field_value < 0 && field_value > -1e-5))
+ field_value /= 10.0;
+ WriteNum(field_value);
+}
+
+void Converter::Visit(const MatrixImageFilter& matrix_image_filter) {
+ Visit(matrix_image_filter.image_filter_parent(), 1);
+ Visit(matrix_image_filter.transform());
+ WriteNum(matrix_image_filter.filter_quality());
+}
+
+void Converter::Visit(const PaintImageFilter& paint_image_filter) {
+ Visit(paint_image_filter.image_filter_parent(), 0);
+ Visit(paint_image_filter.paint());
+}
+
+float Converter::GetRandomFloat(std::mt19937* gen_ptr) {
+ CHECK(gen_ptr);
+ std::mt19937 gen = *gen_ptr;
+ const float positive_random_float = gen();
+ const bool is_negative = gen() % 2 == 1;
+ if (is_negative)
+ return -positive_random_float;
+ return positive_random_float;
+}
+
+float Converter::GetRandomFloat(float seed, float min, float max) {
+ std::mt19937 gen(seed);
+ auto next_after_max = std::nextafter(max, std::numeric_limits<float>::max());
+ std::uniform_real_distribution<> distribution(min, next_after_max);
+ float result = distribution(gen);
+ CHECK_LE(result, 1.0);
+ CHECK_GE(result, -1.0);
+ return result;
+}
+
+void Converter::WriteFields(const Message& msg,
+ const unsigned start,
+ const unsigned end) {
+ // Do basic validation on start and end. If end == 0, then write all
+ // fields left in msg (after start).
+ CHECK_GE(start, static_cast<unsigned>(1));
+ CHECK_GE(end, static_cast<unsigned>(0));
+ CHECK(start <= end || end == 0);
+ const Descriptor* descriptor = msg.GetDescriptor();
+ CHECK(descriptor);
+ const Reflection* reflection = msg.GetReflection();
+ CHECK(reflection);
+ int field_count = descriptor->field_count();
+ CHECK_LE(end, static_cast<unsigned>(field_count));
+ const bool write_until_last = end == 0;
+ const unsigned last_field_to_write = write_until_last ? field_count : end;
+
+ for (auto field_num = start; field_num <= last_field_to_write; field_num++) {
+ const FieldDescriptor* field_descriptor =
+ descriptor->FindFieldByNumber(field_num);
+ CHECK(field_descriptor);
+ const auto& tp = field_descriptor->cpp_type();
+ if (field_descriptor->is_repeated()) {
+ switch (tp) {
+ case FieldDescriptor::CPPTYPE_UINT32: {
+ const size_t num_elements =
+ reflection->FieldSize(msg, field_descriptor);
+ for (size_t idx = 0; idx < num_elements; idx++) {
+ WriteNum(reflection->GetRepeatedUInt32(msg, field_descriptor, idx));
+ }
+ break;
+ }
+ case FieldDescriptor::CPPTYPE_FLOAT: {
+ const size_t num_elements =
+ reflection->FieldSize(msg, field_descriptor);
+ for (size_t idx = 0; idx < num_elements; idx++) {
+ WriteNum(reflection->GetRepeatedFloat(msg, field_descriptor, idx));
+ }
+ break;
+ }
+ case FieldDescriptor::CPPTYPE_MESSAGE: {
+ Visit(reflection->GetRepeatedPtrField<google::protobuf::Message>(
+ msg, field_descriptor));
+ break;
+ }
+ default: { NOTREACHED(); }
+ }
+ continue;
+ // Skip field if it is optional and it is unset.
+ } else if (!field_descriptor->is_required() &&
+ !reflection->HasField(msg, field_descriptor)) {
+ continue;
+ }
+
+ // Field is either required or it is optional but is set, so write it:
+ switch (tp) {
+ case FieldDescriptor::CPPTYPE_INT32:
+ WriteNum(BoundNum(reflection->GetInt32(msg, field_descriptor)));
+ break;
+ case FieldDescriptor::CPPTYPE_UINT32:
+ WriteNum(BoundNum(reflection->GetUInt32(msg, field_descriptor)));
+ break;
+ case FieldDescriptor::CPPTYPE_FLOAT:
+ WriteNum(BoundFloat(reflection->GetFloat(msg, field_descriptor)));
+ break;
+ case FieldDescriptor::CPPTYPE_BOOL:
+ WriteBool(reflection->GetBool(msg, field_descriptor));
+ break;
+ case FieldDescriptor::CPPTYPE_ENUM:
+ WriteEnum(msg, reflection, field_descriptor);
+ break;
+ case FieldDescriptor::CPPTYPE_STRING:
+ WriteString(reflection->GetString(msg, field_descriptor));
+ break;
+ case FieldDescriptor::CPPTYPE_MESSAGE:
+ Visit(reflection->GetMessage(msg, field_descriptor));
+ break;
+ default:
+ NOTREACHED();
+ }
+ }
+ CHECK(!write_until_last ||
+ !descriptor->FindFieldByNumber(last_field_to_write + 1));
+}
+
+void Converter::WriteEnum(const Message& msg,
+ const Reflection* reflection,
+ const FieldDescriptor* field_descriptor) {
+ enum MutationState {
+ MORE = 1,
+ LESS = 2,
+ };
+
+ const int value = reflection->GetEnumValue(msg, field_descriptor);
+ if (dont_mutate_enum_) {
+ WriteNum(value);
+ return;
+ }
+
+ const int should_mutate = enum_mutator_chance_distribution_(rand_gen_);
+ if (should_mutate != MORE && should_mutate != LESS) {
+ // Don't mutate, just write it.
+ WriteNum(value);
+ return;
+ }
+
+ const EnumDescriptor* enum_descriptor = field_descriptor->enum_type();
+ CHECK(enum_descriptor);
+
+ const EnumValueDescriptor* min_value_descriptor = enum_descriptor->value(0);
+ CHECK(min_value_descriptor);
+ const int min_value = min_value_descriptor->number();
+
+ const int num_values = enum_descriptor->value_count();
+ const EnumValueDescriptor* max_value_descriptor =
+ enum_descriptor->value(num_values - 1);
+ CHECK(max_value_descriptor);
+ const int max_value = max_value_descriptor->number();
+
+ // If we are trying to write less than the min value, but it is 0, just write
+ // than the max instead.
+ if (should_mutate == LESS && min_value != 0) {
+ std::uniform_int_distribution<> value_distribution(-min_value,
+ min_value - 1);
+
+ const int new_value = value_distribution(rand_gen_);
+ CHECK_EQ(enum_descriptor->FindValueByNumber(new_value), nullptr);
+ WriteNum(new_value);
+ // Don't also write an enum that is larger than it is supposed to be.
+ return;
+ }
+ const int distribution_lower_bound = max_value + 1;
+ CHECK_GT(distribution_lower_bound, max_value);
+ const int distribution_upper_bound = 2 * max_value;
+ CHECK_GE(distribution_upper_bound, distribution_lower_bound);
+ std::uniform_int_distribution<> value_distribution(distribution_lower_bound,
+ distribution_upper_bound);
+
+ const int new_value = value_distribution(rand_gen_);
+ CHECK_EQ(enum_descriptor->FindValueByNumber(new_value), nullptr);
+ WriteNum(new_value);
+}
+
+int Converter::Abs(const int val) const {
+ if (val == INT_MIN)
+ return abs(val + 1);
+ return abs(val);
+}
+
+void Converter::Visit(const Vertices& vertices) {
+ // Note that the size is only needed when this is deserialized as part of a
+ // picture image filter. Since this the only way our fuzzer can deserialize
+ // Vertices, we always write the size.
+ RecordSize();
+ int32_t packed = vertices.mode() | kMode_Mask;
+ packed = packed ? !vertices.has_texs() : packed | kHasTexs_Mask;
+ packed = packed ? !vertices.has_colors() : packed | kHasColors_Mask;
+ WriteNum(packed);
+ WriteNum(vertices.vertex_text_colors_size());
+ WriteNum(vertices.indices_size());
+ for (auto vertex_text_color : vertices.vertex_text_colors())
+ Visit(vertex_text_color.vertex());
+
+ if (vertices.has_texs()) {
+ for (auto vertex_text_color : vertices.vertex_text_colors())
+ Visit(vertex_text_color.tex());
+ }
+
+ if (vertices.has_colors()) {
+ for (auto vertex_text_color : vertices.vertex_text_colors())
+ Visit(vertex_text_color.color());
+ }
+ WriteBytesWritten();
+}
+
+void Converter::Visit(const TextBlob& text_blob) {
+ Visit(text_blob.bounds());
+ int num_glyphs = 2 + text_blob.glyph_pos_clusters_size();
+ if (num_glyphs % 2 != 0)
+ num_glyphs--;
+ CHECK_EQ(num_glyphs % 2, 0);
+
+ WriteNum(num_glyphs);
+ WriteUInt8(text_blob.glyph_positioning());
+ WriteUInt8(text_blob.extended());
+ WriteUInt16(0); // padding
+
+ if (text_blob.extended())
+ WriteNum(Abs(text_blob.text_size()));
+ Visit(text_blob.offset());
+
+ Paint paint;
+ paint.CopyFrom(text_blob.paint());
+ paint.set_text_encoding(Paint::kGlyphID_TextEncoding);
+ paint.set_text_size(text_blob.text_size());
+ Visit(paint);
+
+ // Byte array size.
+ WriteNum(sizeof(uint16_t) * num_glyphs);
+ WriteUInt16(text_blob.glyph_pos_cluster_1().glyph());
+ WriteUInt16(text_blob.glyph_pos_cluster_2().glyph());
+ // Ensure 4-byte alignment doesn't get messed up by writing an odd number of
+ // glyphs.
+ int idx = 2;
+ for (auto& glyph_pos_cluster : text_blob.glyph_pos_clusters()) {
+ if (idx++ == num_glyphs)
+ break;
+ WriteUInt16(glyph_pos_cluster.glyph());
+ }
+
+ WriteNum(sizeof(float) * num_glyphs * text_blob.glyph_positioning());
+ idx = 2;
+ if (text_blob.glyph_positioning() == TextBlob::kHorizontal_Positioning) {
+ WriteNum(text_blob.glyph_pos_cluster_1().position_1());
+ WriteNum(text_blob.glyph_pos_cluster_2().position_1());
+ } else if (text_blob.glyph_positioning() == TextBlob::kFull_Positioning) {
+ WriteNum(text_blob.glyph_pos_cluster_1().position_1());
+ WriteNum(text_blob.glyph_pos_cluster_1().position_2());
+ WriteNum(text_blob.glyph_pos_cluster_2().position_1());
+ WriteNum(text_blob.glyph_pos_cluster_2().position_2());
+ }
+ for (auto& glyph_pos_cluster : text_blob.glyph_pos_clusters()) {
+ if (idx++ == num_glyphs)
+ break;
+ if (text_blob.glyph_positioning() == TextBlob::kHorizontal_Positioning) {
+ WriteNum(glyph_pos_cluster.position_1());
+ } else if (text_blob.glyph_positioning() == TextBlob::kFull_Positioning) {
+ WriteNum(glyph_pos_cluster.position_1());
+ WriteNum(glyph_pos_cluster.position_2());
+ }
+ }
+
+ if (text_blob.extended()) {
+ // Write clusters.
+ WriteNum(text_blob.glyph_pos_cluster_1().cluster());
+ WriteNum(text_blob.glyph_pos_cluster_2().cluster());
+ WriteNum(sizeof(uint32_t) * num_glyphs);
+ idx = 2;
+ for (auto& glyph_pos_cluster : text_blob.glyph_pos_clusters()) {
+ if (idx++ == num_glyphs)
+ break;
+ WriteNum(glyph_pos_cluster.cluster());
+ }
+ WriteArray(text_blob.text(), text_blob.text_size());
+ }
+
+ // No more glyphs.
+ WriteNum(0);
+}
+
+bool Converter::IsBlacklisted(const std::string& field_name) const {
+#ifndef AVOID_MISBEHAVIOR
+ // Don't blacklist misbehaving flattenables.
+ return false;
+#else
+
+ return kMisbehavedFlattenableBlacklist.find(field_name) !=
+ kMisbehavedFlattenableBlacklist.end();
+#endif // AVOID_MISBEHAVIOR
+}
+}; // namespace skia_image_filter_proto_converter
diff --git a/chromium/testing/libfuzzer/proto/skia_image_filter_proto_converter.h b/chromium/testing/libfuzzer/proto/skia_image_filter_proto_converter.h
new file mode 100644
index 00000000000..9e9b6f1d90b
--- /dev/null
+++ b/chromium/testing/libfuzzer/proto/skia_image_filter_proto_converter.h
@@ -0,0 +1,449 @@
+// Copyright 2018 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.
+
+#ifndef TESTING_LIBFUZZER_PROTO_SKIA_IMAGE_FILTER_PROTO_CONVERTER_H_
+#define TESTING_LIBFUZZER_PROTO_SKIA_IMAGE_FILTER_PROTO_CONVERTER_H_
+
+#include <random>
+#include <set>
+#include <string>
+#include <tuple>
+#include <unordered_map>
+#include <vector>
+
+#include "third_party/skia/include/core/SkPoint.h"
+
+#include "testing/libfuzzer/proto/skia_image_filter.pb.h"
+
+using google::protobuf::FieldDescriptor;
+using google::protobuf::Message;
+using google::protobuf::Reflection;
+
+typedef std::unordered_map<std::string, std::string> string_map_t;
+
+namespace skia_image_filter_proto_converter {
+
+// Takes an Input proto as input and converts it to a string that will usually
+// be deserialized as a skia image filter.
+class Converter {
+ public:
+ Converter();
+ Converter(const Converter&);
+ ~Converter();
+
+ // Provides the public interface for this class's functionality by converting
+ // Input to a string representing a serialized image filter.
+ std::string Convert(const Input&);
+
+ private:
+ // These constexprs are copied from skia.
+ static constexpr uint8_t kICC_Flag = 1 << 1;
+ static constexpr size_t kICCTagTableEntrySize = 12;
+ static constexpr uint32_t kMatrix_Flag = 1 << 0;
+ static constexpr uint8_t k0_Version = 0;
+ static constexpr uint8_t kTransferFn_Flag = 1 << 3;
+ static constexpr size_t kLut8InputSize = 48;
+ static constexpr size_t kOneChannelGammasSize = 256;
+ static constexpr size_t kMaxLut16GammaEntries = 4096;
+ static constexpr uint8_t kLut8Precision = 1;
+ static const uint32_t kPictEofTag;
+ static const uint32_t kProfileLookupTable[];
+ static const uint32_t kInputColorSpaceLookupTable[];
+ static const uint32_t kPCSLookupTable[];
+ static const uint32_t kTagLookupTable[];
+ static const char kPictureMagicString[];
+ static const char kSkPictReaderTag[];
+ static const uint8_t kCountNibBits[];
+
+ // The size of kColorTableBuffer.
+ static const int kColorTableBufferLength;
+
+ // Used to bound flattenable_depth_.
+ static const int kFlattenableDepthLimit;
+
+ // Used to bound numeric fields.
+ static const int kNumBound;
+
+ // Used by ColorTableToArray to store a ColorTable Message as an array.
+ static uint8_t kColorTableBuffer[];
+
+ // There will be a 1/kMutateEnumDenominator chance that WriteEnum
+ // writes an invalid enum value instead of the one given to us by LPM.
+ // This must be greater than 1.
+ static const uint8_t kMutateEnumDenominator;
+
+ // Mapping of field names to types.
+ static const string_map_t kFieldToFlattenableName;
+
+ // Used by IsBlacklisted to determine which skia flattenable should not be
+ // serialized.
+ static const std::set<std::string> kMisbehavedFlattenableBlacklist;
+
+ // Probably the most important attribute, a char vector that contains
+ // serialized skia flattenable written by the Visit functions. The contents of
+ // output_ is returned by Convert().
+ std::vector<char> output_;
+
+ // Stores the size of output_ when a skia flattenable is being written (since
+ // they need to store their size).
+ std::vector<size_t> start_sizes_;
+
+ // Keep a record of whether we used kStrokeAndFill_Style or kStroke_Style
+ // already since using them multiple times increases the risk of OOMs and
+ // timeouts.
+ bool stroke_style_used_;
+
+ // Used to keep track of how nested are the skia flattenables we are writing.
+ // We use this to limit nesting to avoid OOMs and timeouts.
+ int flattenable_depth_;
+
+ // Nesting PairPathEffects is particularly likely to cause OOMs and timeouts
+ // so limit this even more than other skia flattenables.
+ int pair_path_effect_depth_;
+
+ // Don't allow ComposeColorFilters to contain themselves or else LPM will go
+ // crazy and nest them to the point that it causes OOMs and timeouts (these
+ // filters are more likely than other skia flattenables to cause these
+ // problems).
+ bool in_compose_color_filter_;
+
+ // Used to generate random numbers (for replacing NaNs, and mutating enum
+ // values).
+ std::mt19937 rand_gen_;
+
+ // A distribution from 2-kMutateEnumDenominator that will be used
+ // to generate a random value that we will use to decide if an enum value
+ // should be written as-is or if it should be mutated.
+ // The reason why there is a 2/kMutateEnumDenominator chance rather than a
+ // 1/kMutateEnumDenominator chance is because we treat making an enum value
+ // too small as a separate case from making it too big.
+ std::uniform_int_distribution<> enum_mutator_chance_distribution_;
+
+ // Prevents WriteEnum from writing an invalid enum value instead of the one it
+ // was given.
+ bool dont_mutate_enum_;
+
+ // BoundNum and BoundFloat will only return positive numbers when this is
+ // true.
+ bool bound_positive_;
+
+// In production we don't need attributes used by ICC code since it is not
+// built for production code.
+#ifdef DEVELOPMENT
+ uint32_t icc_base_;
+ int tag_offset_;
+#endif // DEVELOPMENT
+
+ // Reset any state used by the flattenable so that is can be used to convert
+ // another proto input.
+ void Reset();
+
+ void Visit(const PictureImageFilter&);
+ void Visit(const Picture&);
+ void Visit(const PictureTagChild&);
+
+ // The generic Visit function. The compiler will allow this to be called on
+ // any proto message, though this won't result in a correct conversion.
+ // However some very simple messages have contents that can pretty much be
+ // written as they are defined by the fields of msg (eg "DistantLight"). This
+ // method is intended for those messages and will properly convert
+ // those. Note that this method is viral in that any fields on msg that
+ // contain other messages will only be written using this method and
+ // WriteFields. For example, "DistantLight" has a field "direction" that
+ // contains a "Point3" message. It is OK to call this on "DistantLight"
+ // messages because it is OK to call this on "Point3". In essence, it
+ // is only correct to call this method on msg if it is correct to call this
+ // method on any fields (or fields of fields etc.) of msg that are also
+ // Messages. See the file comment on skia_image_filter.proto for an
+ // explanation of how this and WriteFields are used for autovisit.
+ void Visit(const Message& msg);
+
+ void Visit(const PictureData&);
+ void Visit(const RecordingData&);
+ void Visit(const LightParent&);
+ void Visit(const ImageFilterChild&);
+ void Visit(const ImageFilterParent&, const int num_inputs_required);
+ void Visit(const MatrixImageFilter&);
+ void Visit(const Matrix&, bool is_local = false);
+ void Visit(const SpecularLightingImageFilter&);
+ void Visit(const PaintImageFilter&);
+ void Visit(const Paint&);
+ void Visit(const PaintEffects&);
+ void Visit(const PathEffectChild&);
+ void Visit(const LooperChild&);
+ void Visit(const LayerDrawLooper&);
+ void Visit(const LayerInfo&);
+ void Visit(const ColorFilterChild&);
+ void Visit(const ComposeColorFilter&);
+ void Visit(const OverdrawColorFilter&);
+ void Visit(const ToSRGBColorFilter&);
+ void Visit(const ColorFilterMatrix&);
+ void Visit(const ColorMatrixFilterRowMajor255&);
+ void Visit(const MergeImageFilter&);
+ void Visit(const XfermodeImageFilter&);
+ void Visit(const DiffuseLightingImageFilter&);
+ void Visit(const XfermodeImageFilter_Base&);
+ void Visit(const TileImageFilter&);
+ void Visit(const OffsetImageFilter&);
+ void Visit(const ErodeImageFilter&);
+ void Visit(const DilateImageFilter&);
+ void Visit(const DiscretePathEffect&);
+ void Visit(const MatrixConvolutionImageFilter&);
+ void Visit(const MagnifierImageFilter&);
+ void Visit(const LocalMatrixImageFilter&);
+ void Visit(const ImageSource&);
+ void Visit(const Path&);
+ void Visit(const PathRef&);
+ void Visit(const DropShadowImageFilter&);
+ void Visit(const DisplacementMapEffect&);
+ void Visit(const ComposeImageFilter&);
+ void Visit(const ColorFilterImageFilter&);
+ void Visit(const BlurImageFilterImpl&);
+ void Visit(const AlphaThresholdFilterImpl&);
+ void Visit(const Region&);
+ void Visit(const Path1DPathEffect&);
+
+ // Writes the correct PairPathEffect skia flattenable (SkSumPathEffect or
+ // SkComposePathEffect) depending on pair.type(). Note that it writes the
+ // entire skia flattenable (including name and size) unlike most Visit
+ // functions and thus should not be be preceded by a call to
+ // PreVisitFlattenable, nor should it be followed by a call to
+ // PostVisitFlattenable.
+ void Visit(const PairPathEffect&);
+
+ void Visit(const ShaderChild&);
+ void Visit(const Color4Shader&);
+ void Visit(const GradientDescriptor&);
+ void Visit(const GradientParent&);
+ void Visit(const TwoPointConicalGradient&);
+ void Visit(const LinearGradient&);
+ void Visit(const SweepGradient&);
+ void Visit(const RadialGradient&);
+ void Visit(const PictureShader&);
+ void Visit(const LocalMatrixShader&);
+ void Visit(const ComposeShader&);
+ void Visit(const ColorFilterShader&);
+ void Visit(const ImageShader&);
+ void Visit(const Color4f&);
+ void Visit(const Image&);
+ void Visit(const ImageData&);
+ void Visit(const ColorSpaceChild&);
+ void Visit(const ColorSpace_XYZ&);
+ void Visit(const ColorSpaceNamed&);
+ void Visit(const TransferFn&);
+ void Visit(const MaskFilterChild&);
+ void Visit(const Table_ColorFilter&);
+ void Visit(const EmbossMaskFilter&);
+ void Visit(const EmbossMaskFilterLight&);
+ void Visit(const DashImpl&);
+ void Visit(const Path2DPathEffect&);
+ void Visit(const ArithmeticImageFilter&);
+ void Visit(const LightChild&);
+ void Visit(const CropRectangle&);
+ void Visit(const Rectangle&);
+ void Visit(const RRectsGaussianEdgeMaskFilterImpl&);
+ void Visit(const PictureInfo&);
+ void Visit(const BlurMaskFilter&);
+ void Visit(const HighContrast_Filter&);
+ void Visit(const ReaderPictureTag&);
+ void Visit(const Vertices&);
+ void Visit(const TextBlob&);
+ template <class T>
+ void Visit(const google::protobuf::RepeatedPtrField<T>& repeated_field);
+ void VisitPictureTag(const PathPictureTag& path_picture_tag, uint32_t tag);
+ void VisitPictureTag(const PaintPictureTag& paint_picture_tag, uint32_t tag);
+ template <class T>
+ void VisitPictureTag(const T& picture_tag_child, uint32_t tag);
+
+ // Returns false if there is too much nesting (determined by
+ // kFlattenableDepthLimit and flattenable_depth_). Writes name and reserves a
+ // space to write the size of the flattenable. Also increments
+ // flattenable_depth_.
+ bool PreVisitFlattenable(const std::string& name);
+
+ // Writes the size of the flattenable to the reserved space, ensures that
+ // output_ is four byte aligned and then decrements flattenable_depth_.
+ void PostVisitFlattenable();
+
+ std::tuple<int32_t, int32_t, int32_t, int32_t> WriteNonEmptyIRect(
+ const IRect& irect);
+
+ void WriteColorSpaceVersion();
+ // Write a string in the proper serialized format, padding if necessary.
+ void WriteString(std::string str);
+
+ // Get the size of a skia flattenable that was just written and insert it at
+ // the proper location. Every call to this method should have a corresponding
+ // call to RecordSize.
+ void WriteBytesWritten();
+
+ // Reserves space to write the size of what we are serializing and records
+ // info so that WriteBytesWritten can determine the size. Every call to this
+ // method should have a corresponding call to WriteBytesWritten that it is
+ // followed by.
+ void RecordSize();
+
+ // Write size to position in output_.
+ void InsertSize(const size_t size, const uint32_t position);
+
+ // Pops off the end of start_sizes_.
+ size_t PopStartSize();
+
+ // Pad the write_size bytes that were written with zeroes so that the
+ // write_size + number of padding bytes is divisible by four.
+ void Pad(const size_t write_size);
+
+ // Write size elements of RepeatedField of uint32_ts repeated_field as an
+ // array.
+ void WriteArray(
+ const google::protobuf::RepeatedField<uint32_t>& repeated_field,
+ const size_t size);
+
+ // Write size bytes of arr as an array and pad if necessary.
+ void WriteArray(const char* arr, const size_t size);
+
+ void WriteBool(const bool bool_val);
+
+ // Write the fields of msg starting with the field whose number is start and
+ // ending with the field whose number is end. If end is 0 then all fields
+ // until the last one will be written. start defaults to 1 and end defaults to
+ // 0. Note that not all possible (eg repeated bools) fields are supported,
+ // consult the code to determine if the field is supported (an error will be
+ // thrown if a field is unsupported). Note that WriteFields is viral in that
+ // if msg contains a field containing another Message, let's say msg2, then
+ // WriteFields(msg2) will be called (assuming that fields is in the range of
+ // msgs we are writing). If there is a method defined Visit(const Message2&
+ // msg2), it will not be called because there is no simple way to determine
+ // the type of msg2 using protobuf's reflection API. WriteFields will bound
+ // any numeric fields before writing them to avoid OOMs and timeouts. See the
+ // file comment on skia_image_filter.proto for an explanation of how this and
+ // the generic Visit function are used for autovisit. Note that this may write
+ // invalid enum values instead of the ones provided if dont_mutate_enum_ is
+ // true. Note that this method may not work if the max field of msg has number
+ // that is different than the number of fields. For example, if msg does not
+ // have a field with field number 1, but has fields with field numbers 2 and
+ // 3, calling WriteFields(msg, 2) will not write field 3.
+ void WriteFields(const Message& msg,
+ const unsigned start = 1,
+ const unsigned end = 0);
+
+ // Given the name of a proto field, field_name returns the name of the
+ // flattenable skia flattenable object it represents.
+ std::string FieldToFlattenableName(const std::string& field_name) const;
+
+ void CheckAlignment() const;
+ // Append our proto Message proto_point to sk_points as an SkPoint.
+ void AppendAsSkPoint(std::vector<SkPoint>& sk_points,
+ const Point& proto_point) const;
+
+ template <typename T>
+ void WriteUInt8(T num);
+ void WriteUInt16(uint16_t num);
+ void WriteNum(const char (&num_arr)[4]);
+ // Write num as a number. Assumes num is four bytes or less.
+ template <class T>
+ void WriteNum(T num);
+
+ // Write the enum value described by field descriptor and stored on message,
+ // or write an invalid enum value with some probability if dont_mutate_enums_
+ // is false.
+ void WriteEnum(const Message& msg,
+ const Reflection* reflection,
+ const FieldDescriptor* field_descriptor);
+
+ // Bound a num using num_bound so that it won't cause OOMs or timeouts.
+ template <typename T>
+ T BoundNum(T num, int upper_bound) const;
+
+ // kNumBound cant be a default parameter to BoundNum(T num, int upper_bound)
+ // so this function exists instead.
+ template <typename T>
+ T BoundNum(T num);
+
+ // kNumBound cant be a default parameter to BoundFloat(T num, int upper_bound)
+ // so this function exists instead.
+ float BoundFloat(float num);
+
+ // Bound a float num using kNumBound so that it won't cause OOMs or
+ // timeouts.
+ float BoundFloat(float num, const float num_bound);
+
+ // Convert input_num to a uint8_t.
+ template <typename T>
+ uint8_t ToUInt8(const T input_num) const;
+
+ float GetRandomFloat(std::mt19937* gen_ptr);
+ float GetRandomFloat(float seed, float min, float max);
+
+ // Write a sane value of field_value, which should be the value of a field of
+ // a matrix.
+ void WriteMatrixField(float field_value);
+
+ // Saturating wrapper for stdlib.h's abs, which has undefined behavior when
+ // given INT_MIN. This returns abs(INT_MIN+1) if val is INT_MIN.
+ int Abs(const int val) const;
+
+ // Writes the representation of a rectangle returned by GetValidRectangle.
+ template <typename T>
+ void WriteRectangle(std::tuple<T, T, T, T> rectangle);
+
+ // Bound the points making up a rectangle so that the returned tuple is a
+ // valid rectangle.
+ std::tuple<float, float, float, float> GetValidRectangle(float left,
+ float top,
+ float right,
+ float bottom);
+
+ std::tuple<int32_t, int32_t, int32_t, int32_t> GetValidIRect(int32_t left,
+ int32_t top,
+ int32_t right,
+ int32_t bottom);
+
+ bool IsFinite(float num) const;
+ bool IsBlacklisted(const std::string& field_name) const;
+
+ // Converts color_table from our proto Message format to a 256-byte array.
+ // Note that this function modifies kColorTableBuffer.
+ const uint8_t* ColorTableToArray(const ColorTable& color_table);
+
+#ifdef DEVELOPMENT
+ // ICC related functions
+ void Visit(const ICC&);
+ void Visit(const ICCColorSpace&);
+ void Visit(const ICCXYZ&);
+ void Visit(const ICCGray&);
+ void Visit(const ICCA2B0&);
+ void Visit(const ICCA2B0AToB&);
+ void Visit(const ICCA2B0Lut8&);
+ void Visit(const ICCA2B0Lut16&);
+ uint8_t GetClutGridPoints(const ICCA2B0Lut8&);
+ uint32_t GetCurrentICCOffset();
+ uint32_t GetLut8Size(const ICCA2B0Lut8&);
+ uint32_t GetLut16Size(const ICCA2B0Lut16&);
+ void WriteLut8(const ICCA2B0Lut8&);
+ void WriteA2B0TagCommon();
+ void WriteTagSize(const char (&tag)[4], const size_t size);
+ template <typename T>
+ void WriteBigEndian(const T num);
+ void WriteTagHeader(const uint32_t tag, const uint32_t len);
+
+ // Write num_fields zeroes to fill the space used by ignored or reserved
+ // fields in an ICC ColorSpace.
+ void WriteIgnoredFields(const int num_fields);
+
+ // Bound illuminant using num to avoid OOMs and timeouts.
+ int32_t BoundIlluminant(float illuminant, const float num) const;
+
+ // ImageInfo related functions
+ void Visit(const ImageInfo&, const int32_t width, const int32_t height);
+ std::tuple<int32_t, int32_t, int32_t>
+ GetNumPixelBytes(const ImageInfo& image_info, int32_t width, int32_t height);
+
+ size_t ComputeMinByteSize(int32_t width,
+ int32_t height,
+ ImageInfo::AlphaType alpha_type) const;
+#endif // DEVELOPMENT
+};
+}; // namespace skia_image_filter_proto_converter
+#endif // TESTING_LIBFUZZER_PROTO_SKIA_IMAGE_FILTER_PROTO_CONVERTER_H_
diff --git a/chromium/testing/scripts/check_network_annotations.py b/chromium/testing/scripts/check_network_annotations.py
index 2a3c9581b86..e4cdf089d87 100755
--- a/chromium/testing/scripts/check_network_annotations.py
+++ b/chromium/testing/scripts/check_network_annotations.py
@@ -31,7 +31,8 @@ def main_run(args):
def main_compile_targets(args):
- json.dump(['chrome'], args.output)
+ json.dump(['chrome', 'remoting/host:host', 'remoting/client:client'],
+ args.output)
if __name__ == '__main__':
diff --git a/chromium/testing/scripts/common.py b/chromium/testing/scripts/common.py
index d4364525f7d..e68537f3e5d 100644
--- a/chromium/testing/scripts/common.py
+++ b/chromium/testing/scripts/common.py
@@ -73,24 +73,6 @@ def run_command(argv, env=None, cwd=None):
return rc
-def run_command_with_output(argv, stdoutfile, env=None, cwd=None):
- """ Run command and stream its stdout/stderr to the console & |stdoutfile|.
- """
- print 'Running %r in %r (env: %r)' % (argv, cwd, env)
- assert stdoutfile
- with io.open(stdoutfile, 'w') as writer, io.open(stdoutfile, 'r', 1) as \
- reader:
- process = subprocess.Popen(argv, env=env, cwd=cwd, stdout=writer,
- stderr=subprocess.STDOUT)
- while process.poll() is None:
- sys.stdout.write(reader.read())
- time.sleep(0.1)
- # Read the remaining
- sys.stdout.write(reader.read())
- print 'Command %r returned exit code %d' % (argv, process.returncode)
- return process.returncode
-
-
def run_runtest(cmd_args, runtest_args):
env = os.environ.copy()
env['CHROME_HEADLESS'] = '1'
diff --git a/chromium/testing/scripts/run_gtest_perf_test.py b/chromium/testing/scripts/run_gtest_perf_test.py
index ce4397591cf..f2923c483bd 100755
--- a/chromium/testing/scripts/run_gtest_perf_test.py
+++ b/chromium/testing/scripts/run_gtest_perf_test.py
@@ -49,9 +49,10 @@ sys.path.append(GetPerfDir())
import generate_legacy_perf_dashboard_json
-# Add src/testing/ into sys.path for importing xvfb.
+# Add src/testing/ into sys.path for importing xvfb and test_env.
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
import xvfb
+import test_env
# Unfortunately we need to copy these variables from ../test_env.py.
# Importing it and using its get_sandbox_env breaks test runs on Linux
@@ -81,78 +82,70 @@ def main():
args, rest_args = parser.parse_known_args()
- xvfb_proc = None
- openbox_proc = None
- xcompmgr_proc = None
env = os.environ.copy()
# Assume we want to set up the sandbox environment variables all the
# time; doing so is harmless on non-Linux platforms and is needed
# all the time on Linux.
env[CHROME_SANDBOX_ENV] = CHROME_SANDBOX_PATH
- if args.xvfb and xvfb.should_start_xvfb(env):
- xvfb_proc, openbox_proc, xcompmgr_proc = xvfb.start_xvfb(env=env,
- build_dir='.')
- assert xvfb_proc and openbox_proc and xcompmgr_proc, 'Failed to start xvfb'
+ rc = 0
try:
- rc = 0
- try:
- executable = rest_args[0]
- extra_flags = []
- if len(rest_args) > 1:
- extra_flags = rest_args[1:]
-
- # These flags are to make sure that test output perf metrics in the log.
- if not '--verbose' in extra_flags:
- extra_flags.append('--verbose')
- if not '--test-launcher-print-test-stdio=always' in extra_flags:
- extra_flags.append('--test-launcher-print-test-stdio=always')
- if args.isolated_script_test_filter:
- filter_list = common.extract_filter_list(
- args.isolated_script_test_filter)
- extra_flags.append('--gtest_filter=' + ':'.join(filter_list))
-
- if IsWindows():
- executable = '.\%s.exe' % executable
+ executable = rest_args[0]
+ extra_flags = []
+ if len(rest_args) > 1:
+ extra_flags = rest_args[1:]
+
+ # These flags are to make sure that test output perf metrics in the log.
+ if not '--verbose' in extra_flags:
+ extra_flags.append('--verbose')
+ if not '--test-launcher-print-test-stdio=always' in extra_flags:
+ extra_flags.append('--test-launcher-print-test-stdio=always')
+ if args.isolated_script_test_filter:
+ filter_list = common.extract_filter_list(
+ args.isolated_script_test_filter)
+ extra_flags.append('--gtest_filter=' + ':'.join(filter_list))
+
+ if IsWindows():
+ executable = '.\%s.exe' % executable
+ else:
+ executable = './%s' % executable
+ with common.temporary_file() as tempfile_path:
+ env['CHROME_HEADLESS'] = '1'
+ cmd = [executable] + extra_flags
+
+ if args.xvfb:
+ rc = xvfb.run_executable(cmd, env, stdoutfile=tempfile_path)
else:
- executable = './%s' % executable
- with common.temporary_file() as tempfile_path:
- env['CHROME_HEADLESS'] = '1'
- rc = common.run_command_with_output([executable] + extra_flags,
- env=env, stdoutfile=tempfile_path)
- # Now get the correct json format from the stdout to write to the
- # perf results file
- results_processor = (
- generate_legacy_perf_dashboard_json.LegacyResultsProcessor())
- charts = results_processor.GenerateJsonResults(tempfile_path)
- # TODO(eakuefner): Make isolated_script_test_perf_output mandatory
- # after flipping flag in swarming.
- if args.isolated_script_test_perf_output:
- filename = args.isolated_script_test_perf_output
- else:
- filename = args.isolated_script_test_chartjson_output
- # Write the returned encoded json to a the charts output file
- with open(filename, 'w') as f:
- f.write(charts)
- except Exception:
- traceback.print_exc()
- rc = 1
-
- valid = (rc == 0)
- failures = [] if valid else ['(entire test suite)']
- with open(args.isolated_script_test_output, 'w') as fp:
- json.dump({
- 'valid': valid,
- 'failures': failures,
- }, fp)
-
- return rc
-
- finally:
- xvfb.kill(xvfb_proc)
- xvfb.kill(openbox_proc)
- xvfb.kill(xcompmgr_proc)
-
+ rc = test_env.run_command_with_output(cmd, env=env,
+ stdoutfile=tempfile_path)
+
+ # Now get the correct json format from the stdout to write to the perf
+ # results file
+ results_processor = (
+ generate_legacy_perf_dashboard_json.LegacyResultsProcessor())
+ charts = results_processor.GenerateJsonResults(tempfile_path)
+ # TODO(eakuefner): Make isolated_script_test_perf_output mandatory after
+ # flipping flag in swarming.
+ if args.isolated_script_test_perf_output:
+ filename = args.isolated_script_test_perf_output
+ else:
+ filename = args.isolated_script_test_chartjson_output
+ # Write the returned encoded json to a the charts output file
+ with open(filename, 'w') as f:
+ f.write(charts)
+ except Exception:
+ traceback.print_exc()
+ rc = 1
+
+ valid = (rc == 0)
+ failures = [] if valid else ['(entire test suite)']
+ with open(args.isolated_script_test_output, 'w') as fp:
+ json.dump({
+ 'valid': valid,
+ 'failures': failures,
+ }, fp)
+
+ return rc
# This is not really a "script test" so does not need to manually add
# any additional compile targets.
diff --git a/chromium/testing/scripts/run_performance_tests.py b/chromium/testing/scripts/run_performance_tests.py
index b2db0962aa2..f19dbb09574 100755
--- a/chromium/testing/scripts/run_performance_tests.py
+++ b/chromium/testing/scripts/run_performance_tests.py
@@ -27,7 +27,7 @@ followed by a subsequent Python script. It could be generalized to
invoke an arbitrary executable.
It currently runs several benchmarks. The benchmarks it will execute are
-determined by the --bot and sharding map location (see sharding_map_path()).
+based on the shard it is running on and the sharding_map_path(
The results of running the benchmark are put in separate directories per
benchmark. Two files will be present in each directory; perf_results.json, which
@@ -52,62 +52,136 @@ import common
import run_telemetry_benchmark_as_googletest
-
-def sharding_map_path():
- return os.path.join(
+# Current whitelist of benchmarks outputting histograms
+BENCHMARKS_TO_OUTPUT_HISTOGRAMS = [
+ 'dummy_benchmark.histogram_benchmark_1',
+]
+
+# We currently have two different sharding schemes for android
+# vs desktop.
+CURRENT_DESKTOP_NUM_SHARDS = 5
+CURRENT_ANDROID_NUM_SHARDS = 21
+
+def get_sharding_map_path(total_shards, testing):
+ # Determine if we want to do a test run of the benchmarks or run the
+ # full suite.
+ if not testing:
+ # Note: <= for testing purposes until we have all shards running
+ if int(total_shards) <= CURRENT_DESKTOP_NUM_SHARDS:
+ return os.path.join(
+ os.path.dirname(__file__), '..', '..', 'tools', 'perf', 'core',
+ 'benchmark_desktop_bot_map.json')
+ else:
+ return os.path.join(
+ os.path.dirname(__file__), '..', '..', 'tools', 'perf', 'core',
+ 'benchmark_android_bot_map.json')
+ else:
+ return os.path.join(
os.path.dirname(__file__), '..', '..', 'tools', 'perf', 'core',
'benchmark_bot_map.json')
+
+def execute_benchmark(benchmark, isolated_out_dir,
+ args, rest_args, is_reference):
+ # While we are between chartjson and histogram set we need
+ # to determine which output format to look for.
+ # We need to append this both to the args and the per benchmark
+ # args so the run_benchmark call knows what format it is
+ # as well as triggers the benchmark correctly.
+ output_format = None
+ is_histograms = False
+ if benchmark in BENCHMARKS_TO_OUTPUT_HISTOGRAMS:
+ output_format = '--output-format=histograms'
+ is_histograms = True
+ else:
+ output_format = '--output-format=chartjson'
+ # Need to run the benchmark twice on browser and reference build
+ # Insert benchmark name as first argument to run_benchmark call
+ # Need to append output format.
+ per_benchmark_args = (rest_args[:1] + [benchmark]
+ + rest_args[1:] + [output_format])
+ benchmark_path = None
+ if is_reference:
+ # Need to parse out the browser to replace browser flag with
+ # reference build so we run it reference build as well
+ browser_index = 0
+ for arg in per_benchmark_args:
+ if "browser" in arg:
+ break
+ browser_index = browser_index + 1
+ per_benchmark_args[browser_index] = '--browser=reference'
+ # Now we need to add in the rest of the reference build args
+ per_benchmark_args.append('--max-failures=5')
+ per_benchmark_args.append('--output-trace-tag=_ref')
+ benchmark_path = os.path.join(isolated_out_dir, benchmark + '.reference')
+ else:
+ benchmark_path = os.path.join(isolated_out_dir, benchmark)
+
+ # We don't care exactly what these are. In particular, the perf results
+ # could be any format (chartjson, legacy, histogram). We just pass these
+ # through, and expose these as results for this task.
+ rc, perf_results, json_test_results = (
+ run_telemetry_benchmark_as_googletest.run_benchmark(
+ args, per_benchmark_args, is_histograms))
+
+ os.makedirs(benchmark_path)
+ with open(os.path.join(benchmark_path, 'perf_results.json'), 'w') as f:
+ json.dump(perf_results, f)
+ with open(os.path.join(benchmark_path, 'test_results.json'), 'w') as f:
+ json.dump(json_test_results, f)
+ return rc
+
+
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
'--isolated-script-test-output', required=True)
+ # These two flags are passed in from the swarming recipe
+ # but will no longer be needed when we migrate to this new recipe.
+ # For now we need to recognize them so they don't get passed
+ # through to telemetry.
parser.add_argument(
'--isolated-script-test-chartjson-output', required=False)
parser.add_argument(
'--isolated-script-test-perf-output', required=False)
+
parser.add_argument(
'--isolated-script-test-filter', type=str, required=False)
parser.add_argument('--xvfb', help='Start xvfb.', action='store_true')
- parser.add_argument('--output-format', action='append')
- parser.add_argument('--builder', required=True)
- parser.add_argument('--bot', required=True,
- help='Bot ID to use to determine which tests to run. Will'
- ' use //tools/perf/core/benchmark_sharding_map.json'
- ' with this as a key to determine which benchmarks'
- ' to execute')
+ parser.add_argument('--testing', help='Testing instance',
+ type=bool, default=False)
args, rest_args = parser.parse_known_args()
- for output_format in args.output_format:
- rest_args.append('--output-format=' + output_format)
isolated_out_dir = os.path.dirname(args.isolated_script_test_output)
- with open(sharding_map_path()) as f:
+ # First determine what shard we are running on to know how to
+ # index into the bot map to get list of benchmarks to run.
+ total_shards = None
+ shard_index = None
+
+ env = os.environ.copy()
+ if 'GTEST_TOTAL_SHARDS' in env:
+ total_shards = env['GTEST_TOTAL_SHARDS']
+ if 'GTEST_SHARD_INDEX' in env:
+ shard_index = env['GTEST_SHARD_INDEX']
+
+ if not (total_shards or shard_index):
+ raise Exception('Shard indicators must be present for perf tests')
+
+ sharding_map_path = get_sharding_map_path(total_shards, args.testing or False)
+ with open(sharding_map_path) as f:
sharding_map = json.load(f)
- sharding = sharding_map[args.builder][args.bot]['benchmarks']
+ sharding = None
+ sharding = sharding_map[shard_index]['benchmarks']
return_code = 0
for benchmark in sharding:
- # Insert benchmark name as first argument to run_benchmark call
- per_benchmark_args = rest_args[:1] + [benchmark] + rest_args[1:]
- # We don't care exactly what these are. In particular, the perf results
- # could be any format (chartjson, legacy, histogram). We just pass these
- # through, and expose these as results for this task.
- rc, perf_results, json_test_results = (
- run_telemetry_benchmark_as_googletest.run_benchmark(
- args, per_benchmark_args))
-
- return_code = return_code or rc
- benchmark_path = os.path.join(isolated_out_dir, benchmark)
- os.makedirs(benchmark_path)
- with open(os.path.join(benchmark_path, 'perf_results.json'), 'w') as f:
- json.dump(perf_results, f)
- with open(os.path.join(benchmark_path, 'test_results.json'), 'w') as f:
- json.dump(json_test_results, f)
-
+ return_code = (execute_benchmark(
+ benchmark, isolated_out_dir, args, rest_args, False) or return_code)
+ return_code = (execute_benchmark(
+ benchmark, isolated_out_dir, args, rest_args, True) or return_code)
return return_code
-
# This is not really a "script test" so does not need to manually add
# any additional compile targets.
def main_compile_targets(args):
diff --git a/chromium/testing/scripts/run_telemetry_benchmark_as_googletest.py b/chromium/testing/scripts/run_telemetry_benchmark_as_googletest.py
index cbde77b32f3..f41c849a63e 100755
--- a/chromium/testing/scripts/run_telemetry_benchmark_as_googletest.py
+++ b/chromium/testing/scripts/run_telemetry_benchmark_as_googletest.py
@@ -65,7 +65,8 @@ def main():
for output_format in args.output_format:
rest_args.append('--output-format=' + output_format)
- rc, perf_results, json_test_results = run_benchmark(args, rest_args)
+ rc, perf_results, json_test_results = run_benchmark(args, rest_args,
+ 'histograms' in args.output_format)
if perf_results:
if args.isolated_script_test_perf_output:
@@ -83,7 +84,7 @@ def main():
return rc
-def run_benchmark(args, rest_args):
+def run_benchmark(args, rest_args, histogram_results):
env = os.environ.copy()
env['CHROME_HEADLESS'] = '1'
@@ -94,8 +95,6 @@ def run_benchmark(args, rest_args):
tempfile_dir = tempfile.mkdtemp('telemetry')
valid = True
num_failures = 0
- histogram_results_present = 'histograms' in args.output_format
- chartjson_results_present = 'chartjson' in args.output_format
perf_results = None
json_test_results = None
@@ -121,12 +120,11 @@ def run_benchmark(args, rest_args):
# If we have also output chartjson read it in and return it.
# results-chart.json is the file name output by telemetry when the
# chartjson output format is included
- if histogram_results_present:
+ tempfile_name = None
+ if histogram_results:
tempfile_name = os.path.join(tempfile_dir, 'histograms.json')
- elif chartjson_results_present:
- tempfile_name = os.path.join(tempfile_dir, 'results-chart.json')
else:
- tempfile_name = None
+ tempfile_name = os.path.join(tempfile_dir, 'results-chart.json')
if tempfile_name is not None:
with open(tempfile_name) as f:
diff --git a/chromium/testing/test.gni b/chromium/testing/test.gni
index bfcc4379a27..f9f8c911cab 100644
--- a/chromium/testing/test.gni
+++ b/chromium/testing/test.gni
@@ -13,7 +13,9 @@ if (is_android) {
}
if (is_fuchsia) {
+ import("//build/config/chromecast_build.gni")
import("//build/config/fuchsia/rules.gni")
+ import("//build/config/fuchsia/package.gni")
}
# Define a test as an executable (or apk on Android) with the "testonly" flag
@@ -226,29 +228,50 @@ template("test") {
}
} else if (is_fuchsia) {
_output_name = invoker.target_name
- _test_runner_target = "${_output_name}__test_runner_script"
+ _pkg_target = "${_output_name}_pkg"
+ _gen_runner_target = "${_output_name}_runner"
+ _exec_target = "${_output_name}__exec"
- test_runner_script(_test_runner_target) {
- forward_variables_from(invoker,
- [
- "data",
- "data_deps",
- "deps",
- "public_deps",
- "use_test_server",
- ])
+ group(target_name) {
+ testonly = true
+ deps = [
+ ":$_gen_runner_target",
+ ":$_pkg_target",
+ ]
+
+ # Disable packaging for Chromecast builds. (https://crbug.com/810069)
+ if (is_chromecast) {
+ deps -= [ ":${_pkg_target}" ]
+ }
+ }
+
+ # Makes the script which invokes the executable.
+ test_runner_script(_gen_runner_target) {
+ forward_variables_from(invoker, [ "use_test_server" ])
+ deps = [
+ ":$_exec_target",
+ ]
test_name = _output_name
+ exe_path =
+ "$root_out_dir/exe.unstripped/" + get_label_info(_exec_target, "name")
+ package_name = _output_name
}
- executable(target_name) {
+ executable(_exec_target) {
testonly = true
forward_variables_from(invoker, "*")
- if (!defined(data_deps)) {
- data_deps = []
- }
- data_deps += [ ":$_test_runner_target" ]
+ output_name = _exec_target
deps += [ "//build/config:exe_and_shlib_deps" ]
}
+
+ package(_pkg_target) {
+ testonly = true
+ package_name = _output_name
+ binary = get_label_info(_exec_target, "name")
+ deps = [
+ ":$_exec_target",
+ ]
+ }
} else if (is_ios) {
import("//build/config/ios/ios_sdk.gni")
import("//build/config/ios/rules.gni")
diff --git a/chromium/testing/test_env.py b/chromium/testing/test_env.py
index 76192d318b2..51de8faf5a3 100755
--- a/chromium/testing/test_env.py
+++ b/chromium/testing/test_env.py
@@ -5,10 +5,12 @@
"""Sets environment variables needed to run a chromium unit test."""
+import io
import os
import stat
import subprocess
import sys
+import time
# This is hardcoded to be src/ relative to this script.
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -170,7 +172,25 @@ def symbolize_snippets_in_json(cmd, env):
raise subprocess.CalledProcessError(p.returncode, symbolize_command)
-def run_executable(cmd, env):
+def run_command_with_output(argv, stdoutfile, env=None, cwd=None):
+ """ Run command and stream its stdout/stderr to the console & |stdoutfile|.
+ """
+ print('Running %r in %r (env: %r)' % (argv, cwd, env))
+ assert stdoutfile
+ with io.open(stdoutfile, 'w') as writer, io.open(stdoutfile, 'r', 1) as \
+ reader:
+ process = subprocess.Popen(argv, env=env, cwd=cwd, stdout=writer,
+ stderr=subprocess.STDOUT)
+ while process.poll() is None:
+ sys.stdout.write(reader.read())
+ time.sleep(0.1)
+ # Read the remaining.
+ sys.stdout.write(reader.read())
+ print('Command %r returned exit code %d' % (argv, process.returncode))
+ return process.returncode
+
+
+def run_executable(cmd, env, stdoutfile=None):
"""Runs an executable with:
- CHROME_HEADLESS set to indicate that the test is running on a
bot and shouldn't do anything interactive like show modal dialogs.
@@ -199,7 +219,7 @@ def run_executable(cmd, env):
msan = '--msan=1' in cmd
tsan = '--tsan=1' in cmd
cfi_diag = '--cfi-diag=1' in cmd
- if sys.platform in ['win32', 'cygwin']:
+ if stdoutfile or sys.platform in ['win32', 'cygwin']:
# Symbolization works in-process on Windows even when sandboxed.
use_symbolization_script = False
else:
@@ -228,8 +248,11 @@ def run_executable(cmd, env):
sys.stdout.flush()
env.update(extra_env or {})
try:
- # See above comment regarding offline symbolization.
- if use_symbolization_script:
+ if stdoutfile:
+ # Write to stdoutfile and poll to produce terminal output.
+ return run_command_with_output(cmd, env=env, stdoutfile=stdoutfile)
+ elif use_symbolization_script:
+ # See above comment regarding offline symbolization.
# Need to pipe to the symbolizer script.
p1 = subprocess.Popen(cmd, env=env, stdout=subprocess.PIPE,
stderr=sys.stdout)
diff --git a/chromium/testing/trigger_scripts/OWNERS b/chromium/testing/trigger_scripts/OWNERS
new file mode 100644
index 00000000000..be9b88d78a3
--- /dev/null
+++ b/chromium/testing/trigger_scripts/OWNERS
@@ -0,0 +1,5 @@
+eyaich@chromium.org
+jbudorick@chromium.org
+kbr@chromium.org
+martiniss@chromium.org
+maruel@chromium.org
diff --git a/chromium/testing/trigger_scripts/PRESUBMIT.py b/chromium/testing/trigger_scripts/PRESUBMIT.py
new file mode 100644
index 00000000000..c666fc5e18c
--- /dev/null
+++ b/chromium/testing/trigger_scripts/PRESUBMIT.py
@@ -0,0 +1,47 @@
+# 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.
+
+"""Top-level presubmit script for testing/trigger_scripts.
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
+for more details about the presubmit API built into depot_tools.
+"""
+
+def CommonChecks(input_api, output_api):
+ commands = [
+ input_api.Command(
+ name='trigger_multiple_dimensions_unittest', cmd=[
+ input_api.python_executable, 'trigger_multiple_dimensions_unittest.py'],
+ kwargs={}, message=output_api.PresubmitError),
+ ]
+ return input_api.RunTests(commands)
+
+def CheckChangeOnUpload(input_api, output_api):
+ return CommonChecks(input_api, output_api)
+
+def CheckChangeOnCommit(input_api, output_api):
+ return CommonChecks(input_api, output_api)
+
+def PostUploadHook(cl, change, output_api):
+ """git cl upload will call this hook after the issue is created/modified.
+
+ This hook modifies the CL description in order to run extra GPU
+ tests (in particular, the WebGL 2.0 conformance tests) in addition
+ to the regular CQ try bots. This test suite is too large to run
+ against all Chromium commits, but should be run against changes
+ likely to affect these tests. The trigger_multiple_dimensions script
+ is used on the chromium.gpu.fyi waterfall and associated optional
+ tryservers, so it's desired to run extra tests when modifying these
+ scripts.
+ """
+ del change # for pylint
+ return output_api.EnsureCQIncludeTrybotsAreAdded(
+ cl,
+ [
+ 'master.tryserver.chromium.linux:linux_optional_gpu_tests_rel',
+ 'master.tryserver.chromium.mac:mac_optional_gpu_tests_rel',
+ 'master.tryserver.chromium.win:win_optional_gpu_tests_rel',
+ 'master.tryserver.chromium.android:android_optional_gpu_tests_rel',
+ ],
+ 'Automatically added optional GPU tests to run on CQ.')
diff --git a/chromium/testing/trigger_scripts/README.md b/chromium/testing/trigger_scripts/README.md
new file mode 100644
index 00000000000..836328ffc5a
--- /dev/null
+++ b/chromium/testing/trigger_scripts/README.md
@@ -0,0 +1,9 @@
+# Swarming trigger scripts
+
+This directory contains Swarming trigger scripts, which are trampoline
+scripts that provide finer-grained control of how tests are sharded
+and distributed on the Swarming fleet.
+
+Trigger scripts are documented here:
+
+https://cs.chromium.org/chromium/build/scripts/slave/recipe_modules/swarming/api.py?l=1292
diff --git a/chromium/testing/trigger_scripts/base_test_triggerer.py b/chromium/testing/trigger_scripts/base_test_triggerer.py
new file mode 100755
index 00000000000..98f19e0edc9
--- /dev/null
+++ b/chromium/testing/trigger_scripts/base_test_triggerer.py
@@ -0,0 +1,287 @@
+#!/usr/bin/env python
+# Copyright 2018 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.
+"""Custom swarming base trigger class.
+
+This base class consolidates custom swarming triggering logic, to allow one bot
+to conceptually span multiple Swarming configurations, while lumping all trigger
+calls under one logical step. It also gives the subclasses the ability to
+define their own logic for pruning the configurations they want to trigger
+jobs on and what configurations to use.
+
+See trigger_multiple_dimensions.py for an example of how to use this base class.
+
+"""
+
+import argparse
+import copy
+import json
+import os
+import random
+import subprocess
+import sys
+import tempfile
+import urllib
+
+
+SRC_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(
+ __file__))))
+
+SWARMING_PY = os.path.join(SRC_DIR, 'tools', 'swarming_client', 'swarming.py')
+
+
+def strip_unicode(obj):
+ """Recursively re-encodes strings as utf-8 inside |obj|. Returns the result.
+ """
+ if isinstance(obj, unicode):
+ return obj.encode('utf-8', 'replace')
+
+ if isinstance(obj, list):
+ return list(map(strip_unicode, obj))
+
+ if isinstance(obj, dict):
+ new_obj = type(obj)(
+ (strip_unicode(k), strip_unicode(v)) for k, v in obj.iteritems() )
+ return new_obj
+
+ return obj
+
+
+class BaseTestTriggerer(object):
+ def __init__(self):
+ self._bot_configs = None
+ self._bot_statuses = []
+ self._total_bots = 0
+
+
+ def modify_args(self, all_args, bot_index, shard_index, total_shards,
+ temp_file):
+ """Modifies the given argument list.
+
+ Specifically, it does the following:
+ * Adds a --dump_json argument, to read in the results of the
+ individual trigger command.
+ * Adds the dimensions associated with the bot config at the given index.
+ * If the number of shards is greater than one, adds --env
+ arguments to set the GTEST_SHARD_INDEX and GTEST_TOTAL_SHARDS
+ environment variables to _shard_index_ and _total_shards_,
+ respectively.
+
+ The arguments are structured like this:
+ <args to swarming.py trigger> -- <args to bot running isolate>
+ This means we have to add arguments to specific locations in the argument
+ list, to either affect the trigger command, or what the bot runs.
+
+ """
+
+
+
+ assert '--' in all_args, (
+ 'Malformed trigger command; -- argument expected but not found')
+ dash_ind = all_args.index('--')
+ bot_args = ['--dump-json', temp_file]
+ if total_shards > 1:
+ bot_args.append('--env')
+ bot_args.append('GTEST_SHARD_INDEX')
+ bot_args.append(str(shard_index))
+ bot_args.append('--env')
+ bot_args.append('GTEST_TOTAL_SHARDS')
+ bot_args.append(str(total_shards))
+ for key, val in sorted(self._bot_configs[bot_index].iteritems()):
+ bot_args.append('--dimension')
+ bot_args.append(key)
+ bot_args.append(val)
+ return self.append_additional_args(
+ all_args[:dash_ind] + bot_args + all_args[dash_ind:])
+
+ def append_additional_args(self, args):
+ """ Gives subclasses ability to append additional args if necessary
+
+ Base class just returns given get."""
+ return args
+
+ def parse_bot_configs(self, args):
+ try:
+ self._bot_configs = strip_unicode(json.loads(
+ args.multiple_trigger_configs))
+ except Exception as e:
+ raise ValueError('Error while parsing JSON from bot config string %s: %s'
+ % (args.multiple_trigger_configs, str(e)))
+ # Validate the input.
+ if not isinstance(self._bot_configs, list):
+ raise ValueError('Bot configurations must be a list, were: %s' %
+ args.multiple_trigger_configs)
+ if len(self._bot_configs) < 1:
+ raise ValueError('Bot configuration list must have at least one entry')
+ if not all(isinstance(entry, dict) for entry in self._bot_configs):
+ raise ValueError('Bot configurations must all be dictionaries')
+
+ def query_swarming_for_bot_configs(self, verbose):
+ # Query Swarming to figure out which bots are available.
+ for config in self._bot_configs:
+ values = []
+ for key, value in sorted(config.iteritems()):
+ values.append(('dimensions', '%s:%s' % (key, value)))
+ # Ignore dead and quarantined bots.
+ values.append(('is_dead', 'FALSE'))
+ values.append(('quarantined', 'FALSE'))
+ query_arg = urllib.urlencode(values)
+
+ temp_file = self.make_temp_file(prefix='base_trigger_dimensions',
+ suffix='.json')
+ try:
+ ret = self.run_swarming(['query',
+ '-S',
+ 'chromium-swarm.appspot.com',
+ '--limit',
+ '0',
+ '--json',
+ temp_file,
+ ('bots/count?%s' % query_arg)],
+ verbose)
+ if ret:
+ raise Exception('Error running swarming.py')
+ with open(temp_file) as fp:
+ query_result = strip_unicode(json.load(fp))
+ # Summarize number of available bots per configuration.
+ count = int(query_result['count'])
+ # Be robust against errors in computation.
+ available = max(0, count - int(query_result['busy']))
+ self._bot_statuses.append({'total': count, 'available': available})
+ if verbose:
+ idx = len(self._bot_statuses) - 1
+ print 'Bot config %d: %s' % (idx, str(self._bot_statuses[idx]))
+ finally:
+ self.delete_temp_file(temp_file)
+ # Sum up the total count of all bots.
+ self._total_bots = sum(x['total'] for x in self._bot_statuses)
+ if verbose:
+ print 'Total bots: %d' % (self._total_bots)
+
+ def remove_swarming_dimension(self, args, dimension):
+ for i in xrange(len(args)):
+ if args[i] == '--dimension' and args[i+1] == dimension:
+ return args[:i] + args[i+3:]
+ return args
+
+ def make_temp_file(self, prefix=None, suffix=None):
+ # This trick of closing the file handle is needed on Windows in order to
+ # make the file writeable.
+ h, temp_file = tempfile.mkstemp(prefix=prefix, suffix=suffix)
+ os.close(h)
+ return temp_file
+
+ def delete_temp_file(self, temp_file):
+ os.remove(temp_file)
+
+ def read_json_from_temp_file(self, temp_file):
+ with open(temp_file) as f:
+ return json.load(f)
+
+ def write_json_to_file(self, merged_json, output_file):
+ with open(output_file, 'w') as f:
+ json.dump(merged_json, f)
+
+ def run_swarming(self, args, verbose):
+ if verbose:
+ print 'Running Swarming with args:'
+ print str(args)
+ return subprocess.call([sys.executable, SWARMING_PY] + args)
+
+ def prune_test_specific_configs(self, args, verbose):
+ # Ability for base class to further prune configs to
+ # run tests on.
+ pass
+
+ def select_config_indices(self, args, verbose):
+ # Main implementation for base class to determine what
+ # configs to trigger jobs on from self._bot_configs.
+ # Returns a list of indices into the self._bot_configs and
+ # len(args.shards) == len(selected_indices).
+ pass
+
+ def trigger_tasks(self, args, remaining):
+ """Triggers tasks for each bot.
+
+ Args:
+ args: Parsed arguments which we need to use.
+ remaining: The remainder of the arguments, which should be passed to
+ swarming.py calls.
+
+ Returns:
+ Exit code for the script.
+ """
+ verbose = args.multiple_dimension_script_verbose
+ self.parse_bot_configs(args)
+ # Prunes config list to the exact set of configurations to trigger jobs on.
+ # This logic is specific to the base class if they want to prune list
+ # further.
+ self.prune_test_specific_configs(args, verbose)
+
+ # In the remaining arguments, find the Swarming dimensions that are
+ # specified by the bot configs and remove them, because for each shard,
+ # we're going to select one of the bot configs and put all of its Swarming
+ # dimensions on the command line.
+ filtered_remaining_args = copy.deepcopy(remaining)
+ for config in self._bot_configs:
+ for k in config.iterkeys():
+ filtered_remaining_args = self.remove_swarming_dimension(
+ filtered_remaining_args, k)
+
+ merged_json = {}
+
+ # Choose selected configs for this run of the test suite.
+ selected_configs = self.select_config_indices(args, verbose)
+ for i in xrange(args.shards):
+ # For each shard that we're going to distribute, do the following:
+ # 1. Pick which bot configuration to use.
+ # 2. Insert that bot configuration's dimensions as command line
+ # arguments, and invoke "swarming.py trigger".
+ bot_index = selected_configs[i]
+ # Holds the results of the swarming.py trigger call.
+ try:
+ json_temp = self.make_temp_file(prefix='base_trigger_dimensions',
+ suffix='.json')
+ args_to_pass = self.modify_args(filtered_remaining_args, bot_index, i,
+ args.shards, json_temp)
+ ret = self.run_swarming(args_to_pass, verbose)
+ if ret:
+ sys.stderr.write('Failed to trigger a task, aborting\n')
+ return ret
+ result_json = self.read_json_from_temp_file(json_temp)
+ if i == 0:
+ # Copy the entire JSON -- in particular, the "request"
+ # dictionary -- from shard 0. "swarming.py collect" uses
+ # some keys from this dictionary, in particular related to
+ # expiration. It also contains useful debugging information.
+ merged_json = copy.deepcopy(result_json)
+ # However, reset the "tasks" entry to an empty dictionary,
+ # which will be handled specially.
+ merged_json['tasks'] = {}
+ for k, v in result_json['tasks'].items():
+ v['shard_index'] = i
+ merged_json['tasks'][k + ':%d:%d' % (i, args.shards)] = v
+ finally:
+ self.delete_temp_file(json_temp)
+ self.write_json_to_file(merged_json, args.dump_json)
+ return 0
+
+
+ def setup_parser_contract(self, parser):
+ parser.add_argument('--multiple-trigger-configs', type=str, required=True,
+ help='The Swarming configurations to trigger tasks on, '
+ 'in the form of a JSON array of dictionaries (these are'
+ ' Swarming dimension_sets). At least one entry in this '
+ 'dictionary is required.')
+ parser.add_argument('--multiple-dimension-script-verbose', type=bool,
+ default=False, help='Turn on verbose logging')
+ parser.add_argument('--dump-json', required=True,
+ help='(Swarming Trigger Script API) Where to dump the'
+ ' resulting json which indicates which tasks were'
+ ' triggered for which shards.')
+ parser.add_argument('--shards', type=int, default=1,
+ help='How many shards to trigger. Duplicated from the'
+ ' `swarming.py trigger` command.')
+ return parser
+
diff --git a/chromium/testing/trigger_scripts/perf_device_trigger.py b/chromium/testing/trigger_scripts/perf_device_trigger.py
new file mode 100755
index 00000000000..f24670cf262
--- /dev/null
+++ b/chromium/testing/trigger_scripts/perf_device_trigger.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+# Copyright 2017 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.
+"""Custom swarming triggering script.
+
+This script does custom swarming triggering logic, to enable device affinity
+for our bots, while lumping all trigger calls under one logical step.
+
+This script receives multiple machine configurations on the command line in the
+form of quoted strings. These strings are JSON dictionaries that represent
+entries in the "dimensions" array of the "swarming" dictionary in the
+src/testing/buildbot JSON files.
+
+Scripts inheriting must have roughly the same command line interface as
+swarming.py trigger. It modifies it in the following ways:
+ * Intercepts the dump-json argument, and creates its own by combining the
+ results from each trigger call.
+ * Scans through the multiple-trigger-configs dictionaries. For any key found,
+ deletes that dimension from the originally triggered task's dimensions. This
+ is what allows the Swarming dimensions to be replaced. Must contain the
+ dimension "id" for the perf device affinity use case.
+ * On a per-shard basis, adds the Swarming dimensions chosen from the
+ multiple-trigger-configs list to the dimensions for the shard.
+
+This script is normally called from the swarming recipe module in tools/build.
+
+"""
+
+import argparse
+import json
+import os
+import subprocess
+import sys
+import tempfile
+
+import base_test_triggerer
+
+class PerfDeviceTriggerer(base_test_triggerer.BaseTestTriggerer):
+ def __init__(self):
+ super(PerfDeviceTriggerer, self).__init__()
+
+ def append_additional_args(self, args):
+ return args
+
+ def select_config_indices(self, args, verbose):
+ # For perf we want to trigger a job for every valid config since
+ # each config represents exactly one bot in the perf swarming pool,
+ selected_configs = []
+ for i in xrange(args.shards):
+ selected_configs.append(i)
+ return selected_configs
+
+
+def main():
+ triggerer = PerfDeviceTriggerer()
+ # Setup args for common contract of base class
+ parser = triggerer.setup_parser_contract(
+ argparse.ArgumentParser(description=__doc__))
+ args, remaining = parser.parse_known_args()
+ return triggerer.trigger_tasks(args, remaining)
+
+if __name__ == '__main__':
+ sys.exit(main())
+
diff --git a/chromium/testing/trigger_scripts/perf_device_trigger_unittest.py b/chromium/testing/trigger_scripts/perf_device_trigger_unittest.py
new file mode 100755
index 00000000000..f6eb576b442
--- /dev/null
+++ b/chromium/testing/trigger_scripts/perf_device_trigger_unittest.py
@@ -0,0 +1,146 @@
+#!/usr/bin/python
+# Copyright 2018 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.
+
+"""Tests for perf_device_trigger_unittest.py."""
+
+import unittest
+
+import perf_device_trigger
+
+class Args(object):
+ def __init__(self):
+ self.shards = 1
+ self.dump_json = ''
+ self.multiple_trigger_configs = []
+ self.multiple_dimension_script_verbose = False
+
+
+class FakeTriggerer(perf_device_trigger.PerfDeviceTriggerer):
+ def __init__(self, bot_configs):
+ super(FakeTriggerer, self).__init__()
+ self._bot_configs = bot_configs
+ self._bot_statuses = []
+ self._swarming_runs = []
+ self._files = {}
+ self._temp_file_id = 0
+
+ def set_files(self, files):
+ self._files = files
+
+ def make_temp_file(self, prefix=None, suffix=None):
+ result = prefix + str(self._temp_file_id) + suffix
+ self._temp_file_id += 1
+ return result
+
+ def delete_temp_file(self, temp_file):
+ pass
+
+ def read_json_from_temp_file(self, temp_file):
+ return self._files[temp_file]
+
+ def write_json_to_file(self, merged_json, output_file):
+ self._files[output_file] = merged_json
+
+ def parse_bot_configs(self, args):
+ pass
+
+ def run_swarming(self, args, verbose):
+ self._swarming_runs.append(args)
+
+
+PERF_BOT1 = {
+ 'pool': 'Chrome-perf-fyi',
+ 'id': 'build1'
+}
+
+PERF_BOT2 = {
+ 'pool': 'Chrome-perf-fyi',
+ 'id': 'build2'
+}
+
+class UnitTest(unittest.TestCase):
+ def basic_setup(self):
+ triggerer = FakeTriggerer(
+ [
+ PERF_BOT1,
+ PERF_BOT2
+ ]
+ )
+ # Note: the contents of these JSON files don't accurately reflect
+ # that produced by "swarming.py trigger". The unit tests only
+ # verify that shard 0's JSON is preserved.
+ triggerer.set_files({
+ 'base_trigger_dimensions0.json': {
+ 'base_task_name': 'webgl_conformance_tests',
+ 'request': {
+ 'expiration_secs': 3600,
+ 'properties': {
+ 'execution_timeout_secs': 3600,
+ },
+ },
+ 'tasks': {
+ 'webgl_conformance_tests on NVIDIA GPU on Windows': {
+ 'task_id': 'f001',
+ },
+ },
+ },
+ 'base_trigger_dimensions1.json': {
+ 'tasks': {
+ 'webgl_conformance_tests on NVIDIA GPU on Windows': {
+ 'task_id': 'f002',
+ },
+ },
+ },
+ })
+ args = Args()
+ args.shards = 2
+ args.dump_json = 'output.json'
+ args.multiple_dimension_script_verbose = False
+ triggerer.trigger_tasks(
+ args,
+ [
+ 'trigger',
+ '--dimension',
+ 'pool',
+ 'chrome-perf-fyi',
+ '--dimension',
+ 'id',
+ 'build1',
+ '--',
+ 'benchmark1',
+ ])
+ return triggerer
+
+ def list_contains_sublist(self, main_list, sub_list):
+ return any(sub_list == main_list[offset:offset + len(sub_list)]
+ for offset in xrange(len(main_list) - (len(sub_list) - 1)))
+
+ def test_shard_env_vars_and_bot_id(self):
+ triggerer = self.basic_setup()
+ self.assertTrue(self.list_contains_sublist(
+ triggerer._swarming_runs[0], ['--bot', 'build1']))
+ self.assertTrue(self.list_contains_sublist(
+ triggerer._swarming_runs[1], ['--bot', 'build2']))
+ self.assertTrue(self.list_contains_sublist(
+ triggerer._swarming_runs[0], ['--env', 'GTEST_SHARD_INDEX', '0']))
+ self.assertTrue(self.list_contains_sublist(
+ triggerer._swarming_runs[1], ['--env', 'GTEST_SHARD_INDEX', '1']))
+ self.assertTrue(self.list_contains_sublist(
+ triggerer._swarming_runs[0], ['--env', 'GTEST_TOTAL_SHARDS', '2']))
+ self.assertTrue(self.list_contains_sublist(
+ triggerer._swarming_runs[1], ['--env', 'GTEST_TOTAL_SHARDS', '2']))
+
+ def test_json_merging(self):
+ triggerer = self.basic_setup()
+ self.assertTrue('output.json' in triggerer._files)
+ output_json = triggerer._files['output.json']
+ self.assertTrue('base_task_name' in output_json)
+ self.assertTrue('request' in output_json)
+ self.assertEqual(output_json['request']['expiration_secs'], 3600)
+ self.assertEqual(
+ output_json['request']['properties']['execution_timeout_secs'], 3600)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/chromium/testing/trigger_scripts/trigger_multiple_dimensions.py b/chromium/testing/trigger_scripts/trigger_multiple_dimensions.py
new file mode 100755
index 00000000000..c17984a3838
--- /dev/null
+++ b/chromium/testing/trigger_scripts/trigger_multiple_dimensions.py
@@ -0,0 +1,133 @@
+#!/usr/bin/env python
+# Copyright 2018 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.
+"""Custom swarming triggering script.
+
+This script does custom swarming triggering logic, to allow one bot to
+conceptually span multiple Swarming configurations, while lumping all trigger
+calls under one logical step.
+
+The reason this script is needed is to allow seamless upgrades of the GPU, OS
+version, or graphics driver. Most Chromium tests, GPU tests in particular, are
+triggered with precise values for all of these Swarming dimensions. This ensures
+that if a machine is added to the Swarming pool with a slightly different
+configuration, tests don't fail for unexpected reasons.
+
+During an upgrade of the fleet, it's not feasible to take half of the machines
+offline. Some experience was gained with this during a recent upgrade of the
+GPUs in Chromium's main Windows and Linux NVIDIA bots. In the middle of the
+upgrade, only 50% of the capacity was available, and CQ jobs started to time
+out. Once the hurdle had been passed in the middle of the upgrade, capacity was
+sufficient, but it's crucial that this process remain seamless.
+
+This script receives multiple machine configurations on the command line in the
+form of quoted strings. These strings are JSON dictionaries that represent
+entries in the "dimensions" array of the "swarming" dictionary in the
+src/testing/buildbot JSON files. The script queries the Swarming pool for the
+number of machines of each configuration, and distributes work (shards) among
+them using the following algorithm:
+
+1. If either configuration has machines available (online, not busy at the time
+of the query) then distribute shards to them first.
+
+2. Compute the relative fractions of all of the live (online, not quarantined,
+not dead) machines of all configurations.
+
+3. Distribute the remaining shards probabilistically among these configurations.
+
+The use of random numbers attempts to avoid the pathology where one
+configuration only has a couple of machines, and work is never distributed to it
+once all machines are busy.
+
+This script must have roughly the same command line interface as swarming.py
+trigger. It modifies it in the following ways:
+ * Intercepts the dump-json argument, and creates its own by combining the
+ results from each trigger call.
+ * Scans through the multiple-trigger-configs dictionaries. For any key found,
+ deletes that dimension from the originally triggered task's dimensions. This
+ is what allows the Swarming dimensions to be replaced.
+ * On a per-shard basis, adds the Swarming dimensions chosen from the
+ multiple-trigger-configs list to the dimensions for the shard.
+
+This script is normally called from the swarming recipe module in tools/build.
+
+"""
+
+import argparse
+import copy
+import json
+import os
+import random
+import subprocess
+import sys
+import tempfile
+import urllib
+
+import base_test_triggerer
+
+
+class MultiDimensionTestTriggerer(base_test_triggerer.BaseTestTriggerer):
+ def __init__(self):
+ super(MultiDimensionTestTriggerer, self).__init__()
+
+ def choose_random_int(self, max_num):
+ return random.randint(1, max_num)
+
+ def pick_bot_configuration(self, verbose):
+ # These are the rules used:
+ # 1. If any configuration has bots available, pick the configuration with
+ # the most bots available.
+ # 2. If no configuration has bots available, pick a random configuration
+ # based on the total number of bots in each configuration.
+ #
+ # This method updates bot_statuses_ in case (1), and in both cases, returns
+ # the index into bot_configs_ that should be used.
+ if any(status['available'] > 0 for status in self._bot_statuses):
+ # Case 1.
+ max_index = 0
+ max_val = self._bot_statuses[0]['available']
+ for i in xrange(1, len(self._bot_statuses)):
+ avail = self._bot_statuses[i]['available']
+ if avail > max_val:
+ max_index = i
+ max_val = avail
+ self._bot_statuses[max_index]['available'] -= 1
+ assert self._bot_statuses[max_index]['available'] >= 0
+ if verbose:
+ print 'Chose bot config %d because bots were available' % (max_index)
+ return max_index
+ # Case 2.
+ # We want to choose a bot uniformly at random from all of the bots specified
+ # in the bot configs. To do this, we conceptually group the bots into
+ # buckets, pick a random number between 1 and the total number of bots, and
+ # figure out which bucket of bots it landed in.
+ r = self.choose_random_int(self._total_bots)
+ for i, status in enumerate(self._bot_statuses):
+ if r <= status['total']:
+ if verbose:
+ print 'Chose bot config %d stochastically' % (i)
+ return i
+ r -= status['total']
+ raise Exception('Should not reach here')
+
+ def select_config_indices(self, args, verbose):
+ selected_indices = []
+ for _ in xrange(args.shards):
+ selected_indices.append(self.pick_bot_configuration(verbose))
+ return selected_indices
+
+ def prune_test_specific_configs(self, args, verbose):
+ self.query_swarming_for_bot_configs(verbose)
+
+def main():
+ triggerer = MultiDimensionTestTriggerer()
+ # setup args for common contract of base class
+ parser = triggerer.setup_parser_contract(
+ argparse.ArgumentParser(description=__doc__))
+ args, remaining = parser.parse_known_args()
+ return triggerer.trigger_tasks(args, remaining)
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/chromium/testing/trigger_scripts/trigger_multiple_dimensions_unittest.py b/chromium/testing/trigger_scripts/trigger_multiple_dimensions_unittest.py
new file mode 100755
index 00000000000..1cd2a27316e
--- /dev/null
+++ b/chromium/testing/trigger_scripts/trigger_multiple_dimensions_unittest.py
@@ -0,0 +1,330 @@
+#!/usr/bin/python
+# Copyright 2018 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.
+
+"""Tests for trigger_multiple_dimensions.py."""
+
+import unittest
+
+import trigger_multiple_dimensions
+
+class Args(object):
+ def __init__(self):
+ self.shards = 1
+ self.dump_json = ''
+ self.multiple_trigger_configs = []
+ self.multiple_dimension_script_verbose = False
+
+
+class FakeTriggerer(trigger_multiple_dimensions.MultiDimensionTestTriggerer):
+ def __init__(self, bot_configs, bot_statuses,
+ use_superclass_random_number_generator, first_random_number):
+ super(FakeTriggerer, self).__init__()
+ self._bot_configs = bot_configs
+ self._bot_statuses = bot_statuses
+ self._swarming_runs = []
+ self._files = {}
+ self._temp_file_id = 0
+ self._use_superclass_rng = use_superclass_random_number_generator
+ self._last_random_number = first_random_number
+
+ def set_files(self, files):
+ self._files = files
+
+ def choose_random_int(self, max_num):
+ if self._use_superclass_rng:
+ return super(FakeTriggerer, self).choose_random_int(max_num)
+ if self._last_random_number > max_num:
+ self._last_random_number = 1
+ result = self._last_random_number
+ self._last_random_number += 1
+ return result
+
+ def make_temp_file(self, prefix=None, suffix=None):
+ result = prefix + str(self._temp_file_id) + suffix
+ self._temp_file_id += 1
+ return result
+
+ def delete_temp_file(self, temp_file):
+ pass
+
+ def read_json_from_temp_file(self, temp_file):
+ return self._files[temp_file]
+
+ def write_json_to_file(self, merged_json, output_file):
+ self._files[output_file] = merged_json
+
+ def parse_bot_configs(self, args):
+ pass
+
+ def query_swarming_for_bot_configs(self, verbose):
+ # Sum up the total count of all bots.
+ self._total_bots = sum(x['total'] for x in self._bot_statuses)
+
+ def run_swarming(self, args, verbose):
+ self._swarming_runs.append(args)
+
+
+WIN_NVIDIA_QUADRO_P400_STABLE_DRIVER = '10de:1cb3-23.21.13.8792'
+WIN7 = 'Windows-2008ServerR2-SP1'
+WIN10 = 'Windows-10'
+
+WIN7_NVIDIA = {
+ 'gpu': WIN_NVIDIA_QUADRO_P400_STABLE_DRIVER,
+ 'os': WIN7,
+ 'pool': 'Chrome-GPU',
+}
+
+WIN10_NVIDIA = {
+ 'gpu': WIN_NVIDIA_QUADRO_P400_STABLE_DRIVER,
+ 'os': WIN10,
+ 'pool': 'Chrome-GPU',
+}
+
+class UnitTest(unittest.TestCase):
+ def basic_win7_win10_setup(self, bot_statuses,
+ use_superclass_random_number_generator=False,
+ first_random_number=1):
+ triggerer = FakeTriggerer(
+ [
+ WIN7_NVIDIA,
+ WIN10_NVIDIA
+ ],
+ bot_statuses,
+ use_superclass_random_number_generator,
+ first_random_number
+ )
+ # Note: the contents of these JSON files don't accurately reflect
+ # that produced by "swarming.py trigger". The unit tests only
+ # verify that shard 0's JSON is preserved.
+ triggerer.set_files({
+ 'base_trigger_dimensions0.json': {
+ 'base_task_name': 'webgl_conformance_tests',
+ 'request': {
+ 'expiration_secs': 3600,
+ 'properties': {
+ 'execution_timeout_secs': 3600,
+ },
+ },
+ 'tasks': {
+ 'webgl_conformance_tests on NVIDIA GPU on Windows': {
+ 'task_id': 'f001',
+ },
+ },
+ },
+ 'base_trigger_dimensions1.json': {
+ 'tasks': {
+ 'webgl_conformance_tests on NVIDIA GPU on Windows': {
+ 'task_id': 'f002',
+ },
+ },
+ },
+ })
+ args = Args()
+ args.shards = 2
+ args.dump_json = 'output.json'
+ args.multiple_dimension_script_verbose = False
+ triggerer.trigger_tasks(
+ args,
+ [
+ 'trigger',
+ '--dimension',
+ 'gpu',
+ WIN_NVIDIA_QUADRO_P400_STABLE_DRIVER,
+ '--dimension',
+ 'os',
+ WIN7,
+ '--dimension',
+ 'pool',
+ 'Chrome-GPU',
+ '--',
+ 'webgl_conformance',
+ ])
+ return triggerer
+
+ def list_contains_sublist(self, main_list, sub_list):
+ return any(sub_list == main_list[offset:offset + len(sub_list)]
+ for offset in xrange(len(main_list) - (len(sub_list) - 1)))
+
+ def shard_runs_on_os(self, triggerer, shard_index, os):
+ return self.list_contains_sublist(triggerer._swarming_runs[shard_index],
+ ['--dimension', 'os', os])
+
+ def test_parse_bot_configs(self):
+ triggerer = trigger_multiple_dimensions.MultiDimensionTestTriggerer()
+ args = Args()
+ args.multiple_trigger_configs = "{ foo }"
+ self.assertRaisesRegexp(ValueError, "Error while parsing JSON.*",
+ triggerer.parse_bot_configs, args)
+ args.multiple_trigger_configs = "{ \"foo\": \"bar\" }"
+ self.assertRaisesRegexp(ValueError, "Bot configurations must be a list.*",
+ triggerer.parse_bot_configs, args)
+ args.multiple_trigger_configs = "[]"
+ self.assertRaisesRegexp(ValueError,
+ "Bot configuration list must have at least.*",
+ triggerer.parse_bot_configs, args)
+ args.multiple_trigger_configs = "[{}, \"\"]"
+ self.assertRaisesRegexp(ValueError,
+ "Bot configurations must all be.*",
+ triggerer.parse_bot_configs, args)
+ args.multiple_trigger_configs = "[{}]"
+ triggerer.parse_bot_configs(args)
+ self.assertEqual(triggerer._bot_configs, [{}])
+
+ def test_split_with_available_machines(self):
+ triggerer = self.basic_win7_win10_setup(
+ [
+ {
+ 'total': 1,
+ 'available': 1,
+ },
+ {
+ 'total': 1,
+ 'available': 1,
+ },
+ ],
+ )
+ # First shard should run on Win7.
+ self.assertTrue(self.shard_runs_on_os(triggerer, 0, WIN7))
+ # Second shard should run on Win10.
+ self.assertTrue(self.shard_runs_on_os(triggerer, 1, WIN10))
+ # And not vice versa.
+ self.assertFalse(self.shard_runs_on_os(triggerer, 0, WIN10))
+ self.assertFalse(self.shard_runs_on_os(triggerer, 1, WIN7))
+
+ def test_shard_env_vars(self):
+ triggerer = self.basic_win7_win10_setup(
+ [
+ {
+ 'total': 2,
+ 'available': 2,
+ },
+ {
+ 'total': 2,
+ 'available': 0,
+ },
+ ],
+ )
+ self.assertTrue(self.list_contains_sublist(
+ triggerer._swarming_runs[0], ['--env', 'GTEST_SHARD_INDEX', '0']))
+ self.assertTrue(self.list_contains_sublist(
+ triggerer._swarming_runs[1], ['--env', 'GTEST_SHARD_INDEX', '1']))
+ self.assertTrue(self.list_contains_sublist(
+ triggerer._swarming_runs[0], ['--env', 'GTEST_TOTAL_SHARDS', '2']))
+ self.assertTrue(self.list_contains_sublist(
+ triggerer._swarming_runs[1], ['--env', 'GTEST_TOTAL_SHARDS', '2']))
+
+ def test_json_merging(self):
+ triggerer = self.basic_win7_win10_setup(
+ [
+ {
+ 'total': 1,
+ 'available': 1,
+ },
+ {
+ 'total': 1,
+ 'available': 1,
+ },
+ ],
+ )
+ self.assertTrue('output.json' in triggerer._files)
+ output_json = triggerer._files['output.json']
+ self.assertTrue('base_task_name' in output_json)
+ self.assertTrue('request' in output_json)
+ self.assertEqual(output_json['request']['expiration_secs'], 3600)
+ self.assertEqual(
+ output_json['request']['properties']['execution_timeout_secs'], 3600)
+
+ def test_split_with_only_one_config_available(self):
+ triggerer = self.basic_win7_win10_setup(
+ [
+ {
+ 'total': 2,
+ 'available': 2,
+ },
+ {
+ 'total': 2,
+ 'available': 0,
+ },
+ ],
+ )
+ # Both shards should run on Win7.
+ self.assertTrue(self.shard_runs_on_os(triggerer, 0, WIN7))
+ self.assertTrue(self.shard_runs_on_os(triggerer, 1, WIN7))
+ # Redo with only Win10 bots available.
+ triggerer = self.basic_win7_win10_setup(
+ [
+ {
+ 'total': 2,
+ 'available': 0,
+ },
+ {
+ 'total': 2,
+ 'available': 2,
+ },
+ ],
+ )
+ self.assertTrue(self.shard_runs_on_os(triggerer, 0, WIN10))
+ self.assertTrue(self.shard_runs_on_os(triggerer, 1, WIN10))
+
+ def test_split_with_no_bots_available(self):
+ triggerer = self.basic_win7_win10_setup(
+ [
+ {
+ 'total': 1,
+ 'available': 0,
+ },
+ {
+ 'total': 1,
+ 'available': 0,
+ },
+ ],
+ )
+ # Given the fake random number generator above, first shard should
+ # run on Win7.
+ self.assertTrue(self.shard_runs_on_os(triggerer, 0, WIN7))
+ # Second shard should run on Win10.
+ self.assertTrue(self.shard_runs_on_os(triggerer, 1, WIN10))
+ # Try again with different bot distribution and random numbers.
+ triggerer = self.basic_win7_win10_setup(
+ [
+ {
+ 'total': 2,
+ 'available': 0,
+ },
+ {
+ 'total': 2,
+ 'available': 0,
+ },
+ ],
+ first_random_number=3,
+ )
+ self.assertTrue(self.shard_runs_on_os(triggerer, 0, WIN10))
+ self.assertTrue(self.shard_runs_on_os(triggerer, 1, WIN10))
+
+ def test_superclass_random_number_generator_works(self):
+ # Probe randomly a certain number of times.
+ num_runs = 0
+ for _ in xrange(100):
+ triggerer = self.basic_win7_win10_setup(
+ [
+ {
+ 'total': 2,
+ 'available': 0,
+ },
+ {
+ 'total': 2,
+ 'available': 0,
+ },
+ ],
+ use_superclass_random_number_generator=True
+ )
+ for _ in xrange(2):
+ self.assertTrue(self.shard_runs_on_os(triggerer, 0, WIN7) or
+ self.shard_runs_on_os(triggerer, 0, WIN10))
+ num_runs += 1
+ self.assertEqual(num_runs, 200)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/chromium/testing/variations/fieldtrial_testing_config.json b/chromium/testing/variations/fieldtrial_testing_config.json
index b1b4de30922..2bc941dff8a 100644
--- a/chromium/testing/variations/fieldtrial_testing_config.json
+++ b/chromium/testing/variations/fieldtrial_testing_config.json
@@ -238,12 +238,14 @@
],
"experiments": [
{
- "name": "Tracking",
+ "name": "EnabledTracking",
"params": {
"availability": "any",
- "event_trigger": "name:data_saver_detail_iph_trigger;comparator:==0;window:90;storage:360",
- "event_used": "name:data_saver_overview_opened;comparator:<=1;window:90;storage:360",
- "session_rate": "<0"
+ "event_data_saved": "name:data_saved_page_load;comparator:>=1;window:90;storage:360",
+ "event_trigger": "name:data_saver_detail_iph_would_have_triggered;comparator:==0;window:90;storage:360",
+ "event_used": "name:data_saver_overview_opened;comparator:==0;window:90;storage:360",
+ "session_rate": "==0",
+ "tracking_only": "true"
},
"enable_features": [
"IPH_DataSaverDetail"
@@ -324,13 +326,14 @@
],
"experiments": [
{
- "name": "Tracking",
+ "name": "EnabledTracking",
"params": {
- "availability": ">=14",
- "event_1": "name:screenshot_taken_chrome_in_foreground;comparator:>=1;window:135;storage:360",
- "event_trigger": "name:download_page_iph_trigger;comparator:==0;window:90;storage:360",
- "event_used": "name:download_page_started;comparator:==0;window:135;storage:360",
- "session_rate": "<0"
+ "availability": "any",
+ "event_screenshot_taken": "name:screenshot_taken_chrome_in_foreground;comparator:>=1;window:90;storage:360",
+ "event_trigger": "name:download_page_iph_would_have_triggered;comparator:==0;window:90;storage:360",
+ "event_used": "name:download_page_started;comparator:==0;window:90;storage:360",
+ "session_rate": "<=1",
+ "tracking_only": "true"
},
"enable_features": [
"IPH_DownloadPageScreenshot"
@@ -339,6 +342,21 @@
]
}
],
+ "AndroidLanguageSettings": [
+ {
+ "platforms": [
+ "android"
+ ],
+ "experiments": [
+ {
+ "name": "Enabled",
+ "enable_features": [
+ "LanguagesPreference"
+ ]
+ }
+ ]
+ }
+ ],
"AndroidSpellChecker": [
{
"platforms": [
@@ -646,6 +664,45 @@
]
}
],
+ "BlinkSchedulerHighPriorityInput": [
+ {
+ "platforms": [
+ "android",
+ "chromeos",
+ "ios",
+ "linux",
+ "mac",
+ "win"
+ ],
+ "experiments": [
+ {
+ "name": "BlinkSchedulerHighPriorityInput",
+ "enable_features": [
+ "BlinkSchedulerHighPriorityInput"
+ ]
+ }
+ ]
+ }
+ ],
+ "BlockTabUnders": [
+ {
+ "platforms": [
+ "android",
+ "chromeos",
+ "linux",
+ "mac",
+ "win"
+ ],
+ "experiments": [
+ {
+ "name": "Enabled",
+ "enable_features": [
+ "BlockTabUnders"
+ ]
+ }
+ ]
+ }
+ ],
"BookmarkInProductHelp": [
{
"platforms": [
@@ -909,7 +966,7 @@
],
"experiments": [
{
- "name": "Expected",
+ "name": "SuppressionExperiment",
"params": {
"contextual-search-ranker-model-url": "https://www.gstatic.com/chrome/intelligence/assist/ranker/models/contextual_search/test_ranker_model_20171109_short_words_v2.pb.bin",
"enable_bar_overlap_suppression": "true"
@@ -938,6 +995,21 @@
]
}
],
+ "Crostini": [
+ {
+ "platforms": [
+ "chromeos"
+ ],
+ "experiments": [
+ {
+ "name": "Crostini",
+ "enable_features": [
+ "Crostini"
+ ]
+ }
+ ]
+ }
+ ],
"CustomFeedbackUi": [
{
"platforms": [
@@ -1230,21 +1302,6 @@
]
}
],
- "DownloadableStrings": [
- {
- "platforms": [
- "android"
- ],
- "experiments": [
- {
- "name": "Enabled",
- "enable_features": [
- "DownloadableStrings"
- ]
- }
- ]
- }
- ],
"DownloadsUi": [
{
"platforms": [
@@ -1472,6 +1529,16 @@
],
"experiments": [
{
+ "name": "SofterWarningWarnOnLowReputation",
+ "params": {
+ "softer_warning": "true",
+ "warn_on_low_reputation": "true"
+ },
+ "enable_features": [
+ "PasswordProtectionGoogleBrandedPhishingWarning"
+ ]
+ },
+ {
"name": "SofterWarning",
"params": {
"softer_warning": "true",
@@ -1548,6 +1615,29 @@
]
}
],
+ "HTTPBadPhase3": [
+ {
+ "platforms": [
+ "android",
+ "chromeos",
+ "ios",
+ "linux",
+ "mac",
+ "win"
+ ],
+ "experiments": [
+ {
+ "name": "NotSecureWarning",
+ "params": {
+ "treatment": "warning"
+ },
+ "enable_features": [
+ "MarkHttpAs"
+ ]
+ }
+ ]
+ }
+ ],
"Html5ByDefault": [
{
"platforms": [
@@ -1837,6 +1927,21 @@
]
}
],
+ "MacMDDownloadShelf": [
+ {
+ "platforms": [
+ "mac"
+ ],
+ "experiments": [
+ {
+ "name": "Enabled",
+ "enable_features": [
+ "MacMDDownloadShelf"
+ ]
+ }
+ ]
+ }
+ ],
"MacV2Sandbox": [
{
"platforms": [
@@ -1934,25 +2039,6 @@
]
}
],
- "NTPCaptureThumbnail": [
- {
- "platforms": [
- "chromeos",
- "linux",
- "mac",
- "win"
- ],
- "experiments": [
- {
- "name": "Enabled",
- "enable_features": [
- "CaptureThumbnailDependingOnTransitionType",
- "CaptureThumbnailOnNavigatingAway"
- ]
- }
- ]
- }
- ],
"NTPCondensedLayout": [
{
"platforms": [
@@ -2033,30 +2119,30 @@
"NTPPopularSites": [
{
"platforms": [
- "android"
- ],
- "experiments": [
- {
- "name": "EnabledFull"
- }
- ]
- }
- ],
- "NTPPopularSitesWithShortNames": [
- {
- "platforms": [
"android",
"ios"
],
"experiments": [
{
- "name": "HoldbackControl",
+ "name": "Enabled",
+ "enable_features": [
+ "UsePopularSitesSuggestions"
+ ]
+ },
+ {
+ "name": "Disabled",
+ "disable_features": [
+ "UsePopularSitesSuggestions"
+ ]
+ },
+ {
+ "name": "EnabledShortNamesHoldbackControl",
"params": {
"version": "5_shortname"
}
},
{
- "name": "Holdback",
+ "name": "EnabledShortNamesHoldback",
"params": {
"version": "5_shortname-control"
}
@@ -2089,57 +2175,19 @@
"NetAdaptiveProxyConnectionTimeout": [
{
"platforms": [
- "android"
- ],
- "experiments": [
- {
- "name": "Enabled_1_m5",
- "params": {
- "transport_rtt_multiplier": "5"
- }
- }
- ]
- }
- ],
- "NetDelayableH2AndQuicRequests": [
- {
- "platforms": [
- "win"
- ],
- "experiments": [
- {
- "name": "Enabled3",
- "enable_features": [
- "PrioritySupportedRequestsDelayable"
- ]
- }
- ]
- },
- {
- "platforms": [
- "mac"
- ],
- "experiments": [
- {
- "name": "EnabledHead3",
- "enable_features": [
- "HeadPriorityRequestsDelayable"
- ]
- }
- ]
- },
- {
- "platforms": [
+ "android",
"chromeos",
"linux",
- "android"
+ "mac",
+ "win"
],
"experiments": [
{
- "name": "Yielding3",
- "enable_features": [
- "NetworkSchedulerYielding"
- ]
+ "name": "Enabled_Forced",
+ "params": {
+ "non_ssl_http_rtt_multiplier": "6",
+ "ssl_http_rtt_multiplier": "8"
+ }
}
]
}
@@ -2304,10 +2352,14 @@
],
"experiments": [
{
- "name": "HideSuggestionUrlSchemeAndTrivialSubdomains",
+ "name": "UIExperiments",
+ "params": {
+ "UIVerticalMargin": "8"
+ },
"enable_features": [
- "OmniboxUIExperimentHideSuggestionUrlScheme",
- "OmniboxUIExperimentHideSuggestionUrlTrivialSubdomains"
+ "OmniboxUIExperimentShowSuggestionFavicons",
+ "OmniboxUIExperimentSwapTitleAndUrl",
+ "OmniboxUIExperimentVerticalMargin"
]
}
]
@@ -2331,6 +2383,24 @@
]
}
],
+ "OomIntervention": [
+ {
+ "platforms": [
+ "android"
+ ],
+ "experiments": [
+ {
+ "name": "Intervention_R2",
+ "params": {
+ "pause_renderer": "true"
+ },
+ "enable_features": [
+ "OomIntervention"
+ ]
+ }
+ ]
+ }
+ ],
"OutOfProcessPac": [
{
"platforms": [
@@ -2365,6 +2435,25 @@
]
}
],
+ "PDFClickToOpen": [
+ {
+ "platforms": [
+ "android",
+ "chromeos",
+ "linux",
+ "mac",
+ "win"
+ ],
+ "experiments": [
+ {
+ "name": "Enabled",
+ "enable_features": [
+ "ClickToOpenPDFPlaceholder"
+ ]
+ }
+ ]
+ }
+ ],
"PWAFullCodeCache": [
{
"platforms": [
@@ -2489,6 +2578,24 @@
]
}
],
+ "PasswordProtectionForEnterprise": [
+ {
+ "platforms": [
+ "chromeos",
+ "linux",
+ "mac",
+ "win"
+ ],
+ "experiments": [
+ {
+ "name": "V1Enabled",
+ "enable_features": [
+ "EnterprisePasswordProtectionV1"
+ ]
+ }
+ ]
+ }
+ ],
"PasswordSeparatedSigninFlow": [
{
"platforms": [
@@ -2738,21 +2845,6 @@
]
}
],
- "ProgressBarCompletion": [
- {
- "platforms": [
- "android"
- ],
- "experiments": [
- {
- "name": "progress-bar-completion-resources-before-domContentLoaded",
- "enable_features": [
- "progress-bar-completion-resources-before-domContentLoaded"
- ]
- }
- ]
- }
- ],
"QUIC": [
{
"platforms": [
@@ -2789,6 +2881,21 @@
]
}
],
+ "ReaderForAccessibility": [
+ {
+ "platforms": [
+ "android"
+ ],
+ "experiments": [
+ {
+ "name": "Enabled",
+ "enable_features": [
+ "AllowReaderForAccessibility"
+ ]
+ }
+ ]
+ }
+ ],
"ReaderModeUI": [
{
"platforms": [
@@ -2870,10 +2977,10 @@
],
"experiments": [
{
- "name": "Enabled_3_1024",
+ "name": "Enabled_2_1024",
"params": {
"limit": "1024",
- "tight_limit": "3"
+ "tight_limit": "2"
},
"enable_features": [
"RendererSideResourceScheduler"
@@ -3491,6 +3598,27 @@
]
}
],
+ "SpeculativePreconnectValidation": [
+ {
+ "platforms": [
+ "win",
+ "mac",
+ "linux",
+ "android"
+ ],
+ "experiments": [
+ {
+ "name": "Preconnect2",
+ "params": {
+ "mode": "preconnect"
+ },
+ "enable_features": [
+ "SpeculativePreconnect"
+ ]
+ }
+ ]
+ }
+ ],
"StabilityDebugging": [
{
"platforms": [
@@ -3567,26 +3695,6 @@
]
}
],
- "SyncUSSTypedURL": [
- {
- "platforms": [
- "android",
- "chromeos",
- "ios",
- "linux",
- "mac",
- "win"
- ],
- "experiments": [
- {
- "name": "Enabled",
- "enable_features": [
- "SyncUSSTypedURL"
- ]
- }
- ]
- }
- ],
"TLS13Variant": [
{
"platforms": [
@@ -3603,18 +3711,6 @@
"params": {
"variant": "draft23"
}
- },
- {
- "name": "Experiment2V4",
- "params": {
- "variant": "experiment2"
- }
- },
- {
- "name": "Draft22V4",
- "params": {
- "variant": "draft22"
- }
}
]
}
@@ -3714,9 +3810,9 @@
],
"experiments": [
{
- "name": "Enforcement20180102",
+ "name": "20180123_launch",
"params": {
- "translate-ranker-model-url": "https://www.gstatic.com/chrome/intelligence/assist/ranker/models/translate/translate_ranker_autoblacklist_20180102.model"
+ "translate-ranker-model-url": "https://www.gstatic.com/chrome/intelligence/assist/ranker/models/translate/translate_ranker_20180123.model"
},
"enable_features": [
"TranslateRankerAutoBlacklistOverride",
@@ -3857,6 +3953,26 @@
]
}
],
+ "UMAHttpRetry": [
+ {
+ "platforms": [
+ "win",
+ "mac",
+ "chromeos",
+ "linux",
+ "ios",
+ "android"
+ ],
+ "experiments": [
+ {
+ "name": "UMAHttpRetryEnabled",
+ "enable_features": [
+ "UMAHttpRetry"
+ ]
+ }
+ ]
+ }
+ ],
"UpdateMenuItem": [
{
"platforms": [
@@ -3894,6 +4010,12 @@
"enable_features": [
"UseHeuristicLanguageModel"
]
+ },
+ {
+ "name": "Stable_Experiment",
+ "enable_features": [
+ "UseHeuristicLanguageModel"
+ ]
}
]
}
@@ -3932,6 +4054,21 @@
]
}
],
+ "UserInitiatedChromeCleanupsFieldTrial": [
+ {
+ "platforms": [
+ "win"
+ ],
+ "experiments": [
+ {
+ "name": "On",
+ "enable_features": [
+ "UserInitiatedChromeCleanups"
+ ]
+ }
+ ]
+ }
+ ],
"V8AsmJSToWasm": [
{
"platforms": [
@@ -4225,24 +4362,6 @@
]
}
],
- "YieldBetweenContentScriptRuns": [
- {
- "platforms": [
- "chromeos",
- "linux",
- "mac",
- "win"
- ],
- "experiments": [
- {
- "name": "Enabled",
- "enable_features": [
- "YieldBetweenContentScriptRuns"
- ]
- }
- ]
- }
- ],
"use-new-media-cache": [
{
"platforms": [
diff --git a/chromium/testing/xvfb.py b/chromium/testing/xvfb.py
index 086ba65dc78..1a170cb040a 100755
--- a/chromium/testing/xvfb.py
+++ b/chromium/testing/xvfb.py
@@ -43,9 +43,12 @@ def kill(proc, timeout_in_seconds=10):
print >> sys.stderr, 'Xvfb running after SIGTERM and SIGKILL; good luck!'
-def run_executable(cmd, env):
+def run_executable(cmd, env, stdoutfile=None):
"""Runs an executable within Xvfb on Linux or normally on other platforms.
+ If |stdoutfile| is provided, symbolization via script is disabled and stdout
+ is written to this file as well as to stdout.
+
Returns the exit code of the specified commandline, or 1 on failure.
"""
@@ -72,7 +75,7 @@ def run_executable(cmd, env):
xcompmgr_proc = subprocess.Popen('xcompmgr', stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, env=env)
- return test_env.run_executable(cmd, env)
+ return test_env.run_executable(cmd, env, stdoutfile)
except OSError as e:
print >> sys.stderr, 'Failed to start Xvfb or Openbox: %s' % str(e)
return 1
@@ -89,7 +92,7 @@ def run_executable(cmd, env):
"+extension RANDR",
xvfb_script] + cmd, env=env)
else:
- return test_env.run_executable(cmd, env)
+ return test_env.run_executable(cmd, env, stdoutfile)
def main():